File Coverage

blib/lib/XML/AppleConfigProfile/Payload/Common.pm
Criterion Covered Total %
statement 34 36 94.4
branch n/a
condition n/a
subroutine 12 12 100.0
pod n/a
total 46 48 95.8


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