File Coverage

blib/lib/Config/Apple/Profile/Payload/Common.pm
Criterion Covered Total %
statement 84 125 67.2
branch 6 44 13.6
condition 22 65 33.8
subroutine 23 27 85.1
pod 7 7 100.0
total 142 268 52.9


line stmt bran cond sub pod time code
1             # This is the code for Config::Apple::Profile::Payload::Common.
2             # For Copyright, please see the bottom of the file.
3              
4             package Config::Apple::Profile::Payload::Common;
5              
6 7     7   53690 use 5.14.4;
  7         25  
  7         263  
7 7     7   39 use strict;
  7         12  
  7         231  
8 7     7   35 use warnings FATAL => 'all';
  7         13  
  7         404  
9              
10             our $VERSION = '0.55';
11              
12 7     7   5240 use Data::GUID;
  7         148678  
  7         56  
13 7     7   9315 use Encode;
  7         89173  
  7         803  
14 7     7   7162 use Mac::PropertyList;
  7         310417  
  7         479  
15 7     7   940 use Readonly;
  7         3190  
  7         411  
16 7     7   46 use Scalar::Util;
  7         15  
  7         277  
17 7     7   7388 use Tie::Hash; # Also gives us Tie::StdHash
  7         6648  
  7         207  
18 7     7   6153 use Try::Tiny;
  7         28189  
  7         555  
19 7     7   6324 use version 0.77;
  7         15911  
  7         47  
20 7     7   5758 use Config::Apple::Profile::Payload::Tie::Root;
  7         29  
  7         288  
21 7     7   49 use Config::Apple::Profile::Payload::Types qw(:all);
  7         15  
  7         1461  
22 7     7   5498 use Config::Apple::Profile::Payload::Types::Serialize qw(serialize);
  7         21  
  7         493  
23 7     7   46 use Config::Apple::Profile::Payload::Types::Validation;
  7         13  
  7         304  
24 7     7   4481 use Config::Apple::Profile::Targets qw(:all);
  7         24  
  7         1266  
25              
26              
27             =encoding utf8
28              
29             =head1 NAME
30              
31             Config::Apple::Profile::Payload::Common - Base class for almost all payload
32             types, with common payload keys.
33              
34             =head1 DESCRIPTION
35              
36             This module serves two purposes.
37              
38             First, this module contains code to store payload data, to validate
39             client-provided data, and to export payload data as a plist.
40              
41             Second, this module defines the payload keys that are common to almost all
42             payload types. Specific payload types (classes) will include these common
43             payload types in their own payload type-specific list.
44              
45             Ideally, each payload type will be implemented as a class that subclasses this
46             one, using this class to do all of the work, leaving the subclass to
47             define the payload keys specific to that subclass.
48              
49             =head1 CLASS METHODS
50              
51             =head2 new()
52              
53             Returns a new object.
54              
55             =cut
56              
57             sub new {
58 222     222 1 253202 my ($self) = @_;
59 222   33     1301 my $class = ref($self) || $self;
60            
61             # The list of payload keys is defined in the class. Let's make it easy
62             # to reference for the instance.
63             ## no critic (ProhibitNoStrict)
64 7     7   49 no strict 'refs';
  7         16  
  7         375  
65             ## use critic
66 222         337 my $keys = \%{"${class}::payloadKeys"};
  222         988  
67 7     7   43 use strict 'refs';
  7         16  
  7         10816  
68            
69             # We're going to leverage Perl's hash-handling code to make payload keys
70             # easy for the Perl programmer to access.
71 222         445 my %payload;
72            
73             # Prepare out object
74 222         1026 my $object = bless {
75             'keys' => $keys,
76             'payload' => \%payload,
77             }, $class;
78            
79             # Now that have the object, we can tie up the hash. YES, this will create
80             # a circular reference, which TIEHASH will deal with.
81 222         1603 tie %payload, 'Config::Apple::Profile::Payload::Tie::Root', $object;
82            
83             # Return the prepared object!
84 222         784 return $object;
85             }
86              
87              
88             =head1 INSTANCE METHODS
89              
90             =head2 keys()
91              
92             Returns a hashref that contains the list of payload keys recognized by this
93             instance.
94              
95             B The hashref points to a hash that is read-only. Any attempts to
96             modify will cause your code to die. You probably want to look at C.
97              
98             =cut
99              
100             sub keys {
101 1350     1350 1 1921 my ($self) = @_;
102 1350         9189 return $self->{keys};
103             }
104              
105              
106             =head2 payload()
107              
108             Returns a hashref that can be used to access the contents of the payload. This
109             is a reference to a tied hash, so you can not use it as liberally as a normal
110             hash. The only keys that will be accepted are those listed under
111             L.
112              
113             The following exception may be thrown when accessing a hash key:
114              
115             =over 4
116              
117             =item Config::Apple::Profile::Payload::Common::KeyInvalid
118              
119             Thrown when attempting to access a payload key that is not valid for this
120             payload.
121              
122             =back
123              
124             If a payload key is valid, but has not been set, then C is returned.
125              
126             The following exceptions may be thrown when setting a hash key:
127              
128             =over 4
129              
130             =item Config::Apple::Profile::Payload::Common::KeyInvalid
131              
132             Thrown when attempting to access a payload key that is not valid for this
133             payload.
134              
135             =item Config::Apple::Profile::Payload::Common::ValueInvalid
136              
137             Thrown when attempting to set an invalid value for this payload key.
138              
139             =back
140              
141             You can use C to test if a particular key is valid for this payload, and
142             you can use C to test if a particular key actually has a value defined.
143             Take note that calling C with an invalid key name will always return
144             false.
145              
146             You can use C to delete a key.
147              
148             =cut
149              
150             sub payload {
151 312     312 1 1594 my ($self) = @_;
152 312         2862 return $self->{payload};
153             }
154              
155              
156             =head2 plist([C => C, ...])
157              
158             Return a copy of this payload, represented as a L object.
159             All strings will be in UTF-8, and all Data entries will be Base64-encoded.
160             This method is used when assembling payloads into a profile.
161              
162             There are two ways to get string output from the plist object:
163              
164             # First, get your plist object from the payload
165             my $plist = $payload->plist();
166            
167             # If you just want the XML element and its contents, do this...
168             my $dict_element = $plist->write;
169            
170             # If you want the complete XML plist, with headers, do this...
171             use Mac::PropertyList;
172             my $complete_plist = Mac::PropertyList::plist_as_string($plist);
173              
174             Several parameters can be provided, which will influence how this method runs.
175              
176             =over 4
177              
178             =item target
179              
180             If C (a value from L) is provided,
181             then this will be taken into account when exporting. Only payload keys that
182             are used on the specified target will be included in the output.
183              
184             The C option controls what happens if keys are excluded.
185              
186             =item version
187              
188             If C (a version string) is provided, then only payload keys that work
189             on the specified version will be included in the output.
190              
191             If C is provided, then C must also be set, but C can
192             be set without setting C.
193              
194             The C option controls what happens if keys are excluded.
195              
196             =item completeness
197              
198             If C is set to a true value, and keys are excluded because of
199             C or C, then C will throw an exception. If set to a
200             false value, or if not set at all, then no exceptions will be thrown, and a
201             less-than-complete (but still valid) plist will be returned.
202              
203             =back
204              
205             The following exceptions may be thrown:
206              
207             =over 4
208              
209             =item Config::Apple::Profile::Exception::KeyRequired
210              
211             Thrown if a required key has not been set.
212              
213             =item Config::Apple::Profile::Exception::Incomplete
214              
215             Thrown if payload keys are being excluded from the output because of C
216             or C.
217              
218             =back
219              
220             =cut
221              
222             sub plist {
223 90     90 1 16068 my $self = $_[0];
224            
225             # Process parameters
226 90         159 my %params;
227 90         410 for (my $i = 1; $i < scalar(@_); $i += 2) {
228 0         0 my ($name, $value) = @_[$i,$i+1];
229            
230             # We have three parameters possible. Process each one
231 0 0       0 if ($name eq 'target') {
    0          
    0          
232 0 0 0     0 unless ( ($value == $TargetIOS)
233             || ($value == $TargetMACOSX)
234             ) {
235 0         0 die "Invalid target $value";
236             }
237 0         0 $params{target} = $value;
238             }
239            
240             elsif ($name eq 'version') {
241             try {
242 0     0   0 $params{version} = version->parse($value);
243             }
244             catch {
245 0     0   0 die "Failed to parse version $value";
246             }
247 0         0 }
248            
249             elsif ($name eq 'completeness') {
250 0 0       0 $params{completeness} = ($value ? 1 : 0);
251             }
252             } # Done inputting parameters
253            
254             # Catch someone setting version without setting target
255 90 50 33     337 if ( (exists $params{version})
256             && (!exists $params{target})
257             ) {
258 0         0 die "Version has been set, but no target was provided";
259             }
260            
261             # We're done with parameter processing and validation; do some work!
262            
263             # Prepare a hash that will be turned into the dictionary
264 90         130 my %dict;
265            
266             # Go through each key that could exist, and skip the ones that are undef.
267 90         457 Readonly my $keys => $self->keys();
268 90         11140 Readonly my $payload => $self->payload();
269 90         9155 foreach my $key (CORE::keys(%$keys)) {
270             # If the key isn't set, then skip it
271 199 100       2697 next unless defined($payload->{$key});
272            
273             # If target has been set, check it against the key's target
274 196 50       663 if (exists $params{target}) {
275 0 0       0 if (!exists($keys->{$key}->{targets}->{$params{target}})) {
276             # This key isn't used on this target, should we die?
277 0 0 0     0 if ( (exists($params{completeness}))
278             && ($params{completeness})
279             ) {
280 0         0 die "Key $key has been set, but isn't supported on this target";
281             }
282            
283             # If we're here, this key isn't used on this target, but we
284             # shouldn't die, so just skip the key.
285 0         0 next;
286             }
287            
288             # If we're here, this key is used on this target; check the version!
289 0         0 my $key_version = $keys->{$key}->{targets}->{$params{target}};
290 0 0 0     0 if ( (exists($params{version}))
291             && ($params{version} < version->parse($key_version))
292             ) {
293             # This key is too new for us, should we die?
294 0 0 0     0 if ( (exists($params{completeness}))
295             && ($params{completeness})
296             ) {
297 0         0 die "Key $key is only supported in newer OS versions";
298             }
299            
300             # If we're here, this key is too new for us, but we shouldn't
301             # die, so just skip the key.
302 0         0 next;
303             }
304            
305             # If we're here, then the version isn't set, or we're new enough!
306             } # Done checking target & version
307            
308             # Serialize the payload contents as a plist fragment, and store
309 196   100     561 $dict{$key} = serialize($keys->{$key}->{type},
310             $payload->{$key},
311             $keys->{$key}->{subtype} || undef
312             );
313             } # Done going through each payload key
314            
315             # Now that we have a populated $dict, make our final plist object!
316 90         552 my $plist = Mac::PropertyList::dict->new(\%dict);
317 90         1960 return $plist;
318             }
319              
320              
321             =head2 populate_id()
322              
323             Populates the C and C fields, if they are not
324             already set. In addition, if the payload has any keys of type
325             C<$PayloadClass>, then C will also be called on them.
326              
327             Sub-classes may decide to override this, so as to add extra functionality.
328              
329             =cut
330              
331             sub populate_id {
332 0     0 1 0 my ($self) = @_;
333            
334 0         0 my $keys = $self->keys;
335 0         0 my $payload = $self->payload;
336            
337             # Go through each key, and check the type
338 0         0 foreach my $key (CORE::keys %$keys) {
339 0         0 my $type = $keys->{$key}->{type};
340            
341             # We can call this method on other classes
342 0 0 0     0 if ( ($type !~ m/^\d+$/)
    0 0        
    0 0        
    0 0        
    0 0        
343             || ($type == $ProfileClass)
344             ) {
345             # Only populate IDs on objects that exist
346 0 0       0 if (defined $payload->{$key}) {
347 0         0 my $object = $payload->{$key};
348 0         0 $object->populate_id();
349             }
350             }
351              
352             # We can fill in UUIDs
353             elsif ($type == $ProfileUUID) {
354 0 0       0 if (!defined $payload->{$key}) {
355             # Make a new (random) GUID
356 0         0 $payload->{$key} = new Data::GUID;
357             }
358             }
359            
360             # We can fill in identifiers
361             elsif ($type == $ProfileIdentifier) {
362 0 0       0 if (!defined $payload->{$key}) {
363             # Just make some simple random identifier
364 0         0 $payload->{$key} = 'payload' . int(rand(2**30));
365             }
366             }
367            
368             # If we have an array of objects, we can do them, too!
369             elsif ( ($type == $ProfileArray)
370             && ( $keys->{$key}->{subtype} !~ m/^\d+$/
371             || $keys->{$key}->{subtype} == $ProfileClass
372             )
373             ) {
374 0         0 foreach my $item (@{$payload->{$key}}) {
  0         0  
375 0         0 $item->populate_id();
376             }
377             }
378            
379             # If we have an dictionary of objects, we can do them, also!
380             elsif ( ($type == $ProfileDict)
381             && ( $keys->{$key}->{subtype} !~ m/^\d+$/
382             || $keys->{$key}->{subtype} == $ProfileClass
383             )
384             ) {
385 0         0 foreach my $item (CORE::keys %{$payload->{$key}}) {
  0         0  
386 0         0 $payload->{$key}->{$item}->populate_id();
387             }
388             }
389            
390             # That's it! Move on to the next key
391             }
392             }
393              
394              
395              
396             =head2 exportable([C])
397              
398             Returns true if the payload is complete enough to be exported. In other words,
399             all required keys must have values provided.
400              
401             If C (a value from L) is provided,
402             then this will be taken into account. For example, a I payload
403             will never be exportable to iOS.
404              
405             =cut
406              
407             sub exportable {
408             ...
409 0     0 1 0 }
410              
411             =head2 validate_key($key, $value)
412              
413             Confirm the value provided is valid for the given payload key, and return a
414             de-tained copy of C<$value>, or C if the provided value is invalid.
415              
416             C<$key> is the name of the payload key being set, and C<$value> is the proposed
417             new value. This class will perform checking for all payload types except for
418             Data payloads. The checks performed will be very basic.
419              
420             Subclasses should override this method to check their keys, and then call
421             SUPER::validate_key($self, $key, $value) to check the remaining keys.
422              
423             The following exceptions may be thrown:
424              
425             =over 4
426              
427             =item Config::Apple::Profile::Payload::Common::KeyUnknown
428              
429             Thrown if the payload key referenced is unknown.
430              
431             =back
432              
433             B An exception will B be thrown if the value is found to be
434             invalid. This is intentional!
435              
436             B To test the result of this method, you should use C,
437             because it's possible for a valid value to be false (for example, 0)!
438              
439             =cut
440              
441             sub validate_key {
442 235     235 1 562 my ($self, $key, $value) = @_;
443            
444             # Does our payload key exist?
445 235 50       1138 if (!exists $self->keys()->{$key}) {
446 0         0 die "Payload key $key is unknown";
447             }
448            
449             # At this point, we would have checks for specific payload keys,
450             # but our common keys don't need any special checking.
451            
452             # Get our payload key's value type
453 235         2226 my $type = $self->keys()->{$key}->{type};
454            
455             # If we are working with a basic type, then call the basic validator!
456 235 50 100     3413 if ( ($type == $ProfileString)
      100        
      66        
      66        
      33        
      33        
      33        
      33        
      66        
      66        
457             || ($type == $ProfileNumber)
458             || ($type == $ProfileReal)
459             || ($type == $ProfileBool)
460             || ($type == $ProfileData)
461             || ($type == $ProfileDate)
462             || ($type == $ProfileNSDataBlob)
463             || ($type == $ProfileDict)
464             || ($type == $ProfileArray)
465             || ($type == $ProfileIdentifier)
466             || ($type == $ProfileUUID)
467             ) {
468 235         14675 return Config::Apple::Profile::Payload::Types::Validation::validate($type, $value);
469             }
470            
471             # If we're still here, then something's wrong, so fail.
472             ## no critic (ProhibitExplicitReturnUndef)
473 0           return undef;
474             ## use critic
475             }
476              
477              
478             =head1 PAYLOAD KEYS
479              
480             Every payload type has a certain number of common keys. Those common keys are
481             defined (not stored) in C<%payloadKeys>.
482              
483             For general information on payload types, see
484             L.
485              
486             =head2 C
487              
488             A C for this specific payload. It must be unique.
489              
490             =head2 C
491              
492             A C for this specific payload. It must be unique.
493              
494             =head2 C
495              
496             I
497              
498             A C that the user will be able to see when looking at the details of the
499             profile that is about to be installed.
500              
501             =head2 C
502              
503             I
504              
505             A C that the user will be able to see when looking at the details of the
506             profile that is about to be installed.
507              
508             =head2 C
509              
510             I
511              
512             A C that the user will be able to see when looking at the details of the
513             profile that is about to be installed.
514              
515             =head2 C
516              
517             A C that identifies which type of payload this is. This value may
518             not be set by the client. Instead, the value is automatically determined based
519             on which C class is being used.
520              
521             =head2 C
522              
523             A C that identifies the version of the payload type. This specifies the
524             version of the standard, not the client's own revision number. Right now, all
525             payload types have a version number of C<1>.
526              
527             =cut
528              
529             Readonly our %payloadKeys => (
530             'PayloadIdentifier' => {
531             type => $ProfileIdentifier,
532             description => ('A Java-style reversed-domain-name identifier for'
533             . 'this payload.'),
534             targets => {
535             $TargetIOS => '5.0',
536             $TargetMACOSX => '10.7',
537             },
538             unique => 1,
539             },
540             'PayloadUUID' => {
541             type => $ProfileUUID,
542             description => 'A GUID for this payload.',
543             targets => {
544             $TargetIOS => '5.0',
545             $TargetMACOSX => '10.7',
546             },
547             unique => 1,
548             },
549             'PayloadDisplayName' => {
550             type => $ProfileString,
551             description => ('A short string that the user will see when '
552             . 'installing the profile.'),
553             targets => {
554             $TargetIOS => '5.0',
555             $TargetMACOSX => '10.7',
556             },
557             optional => 1,
558             },
559             'PayloadDescription' => {
560             type => $ProfileString,
561             description => "A longer description of the payload's purpose.",
562             targets => {
563             $TargetIOS => '5.0',
564             $TargetMACOSX => '10.7',
565             },
566             optional => 1,
567             },
568             'PayloadOrganization' => {
569             type => $ProfileString,
570             description => "The name of the payload's creator.",
571             targets => {
572             $TargetIOS => '5.0',
573             $TargetMACOSX => '10.7',
574             },
575             optional => 1,
576             },
577             ); # End of %payloadKeys
578              
579              
580             =head1 DEVELOPER NOTES
581              
582             The following sections have information that will be useful to people working
583             on the code that makes up this release.
584              
585             =head2 C<%payloadKeys> contents
586              
587             The C<%payloadKeys> hash is critical, so it is important to know how it is
588             constructed. To start, each key in the hash is a key that appears in a payload.
589             The value corresponding to the key is a hashref, which can contain the following
590             keys:
591              
592             =over 4
593              
594             =item C
595              
596             This key's value is a value from L.
597             It is used to specify the type of data the profile key contains.
598              
599             The type is used when creating L objects, and when doing
600             value-checking.
601              
602             If a payload class uses <$ProfileClass> as a type, then the payload class is
603             responsible for providing an instance method named C, which takes
604             the payload key name as its only parameter, and returns a new object.
605              
606             This key must be present.
607              
608             =item C
609              
610             This key is required when C is set to C<$ProfileDict> or C<$ProfileArray>.
611              
612             If C is set to C<$ProfileDict>, then C contains the type of
613             data stored as values. That data type will be used for validation, when
614             entries are added to the Perl hash representing the dictionary.
615              
616             If C is set to C<$ProfileArray>, then C contains the type of
617             data stored in the array. That data type will be used for validation, when
618             entries are added to the Perl array.
619              
620             If a payload class uses <$ProfileClass> as a subtype, then the payload class is
621             responsible for providing an instance method named C, which takes
622             the payload key name as its only parameter, and returns a new object.
623              
624             For other values of the C key, this key must I be present.
625              
626             =item C
627              
628             This key's value contains a human-readable description of the profile key. The
629             purpose of this is so that client software can easily enumerate profile keys,
630             such as when making a web application.
631              
632             This key must be present.
633              
634             =item C
635              
636             This key's value is a hashref. Within the hashref, the keys are platform
637             identifiers, scalars taken from C. The value
638             for each key is a version object representing the earliest version of the
639             named platform's OS which supports this payload key.
640              
641             If a platform does not support a particular key at all, that platform should not
642             be included in the hashref.
643              
644             This key must be present, and the hashref must contain at least one entry.
645              
646             =item C
647              
648             If this key is present, then this payload key does not have to be present.
649             This might mean that a key is completely optional (like C),
650             or it might mean that the value will be auto-generated (like C).
651              
652             It doesn't matter what this key is set to, its presence is what's important.
653              
654             Optional checks are done when L is run, at the very least.
655              
656             =item C
657              
658             If this key is present, then this payload key's value needs to be unique across
659             all payloads in the profile. This is normally used for things like the UUID and
660             the payload identifier, which need to be unique.
661              
662             It doesn't matter what this key is set to, its presence is what's important.
663              
664             Uniqueness checks are done during profile creation time only.
665              
666             =item C
667              
668             If this key is present, then this payload key's value is something that should
669             only be transmitted when the profile is encrypted. This is meant for things
670             like passwords, which should not be sent in the clear.
671              
672             Right now, none of the code in this release does anything with this key. It is
673             provided solely for future use.
674              
675             =item C
676              
677             If this key is present, then the corresponding value will be used as the value
678             for the payload key. Any attempts by the user to change the payload key's value
679             will throw an exception.
680              
681             This is used for things like PayloadVersion and PayloadType, which are fixed.
682              
683             =back
684              
685             =head1 ACKNOWLEDGEMENTS
686              
687             Refer to the L for acknowledgements.
688              
689             =head1 AUTHOR
690              
691             A. Karl Kornel, C<< >>
692              
693             =head1 COPYRIGHT AND LICENSE
694              
695             Copyright © 2014 A. Karl Kornel.
696              
697             This program is free software; you can redistribute it and/or modify it
698             under the terms of either: the GNU General Public License as published
699             by the Free Software Foundation; or the Artistic License.
700              
701             See L for more information.
702              
703             =cut
704              
705             1;