| #!/usr/bin/env python3 |
| # Copyright 2020 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import copy |
| import json |
| |
| from writers import template_writer |
| |
| |
| def GetWriter(config): |
| '''Factory method for creating JamfWriter objects. |
| See the constructor of TemplateWriter for description of |
| arguments. |
| ''' |
| return JamfWriter(['mac', 'ios'], config) |
| |
| |
| class JamfWriter(template_writer.TemplateWriter): |
| '''Simple writer that writes a jamf.json file. |
| ''' |
| MAX_RECURSIVE_FIELDS_DEPTH = 5 |
| TYPE_TO_INPUT = { |
| 'string': 'string', |
| 'int': 'integer', |
| 'int-enum': 'integer', |
| 'string-enum': 'string', |
| 'string-enum-list': 'array', |
| 'main': 'boolean', |
| 'list': 'array', |
| 'dict': 'object', |
| 'external': 'object', |
| } |
| |
| # Some policies are forced to a certain schema, so they bypass TYPE_TO_INPUT |
| POLICY_ID_TO_INPUT = { |
| 227: 'string', # ManagedBookmarks |
| 278: 'string', # ExtensionSettings |
| } |
| |
| |
| def WriteTemplate(self, template): |
| '''Writes the given template definition. |
| |
| Args: |
| template: Template definition to write. |
| |
| Returns: |
| Generated output for the passed template definition. |
| ''' |
| self.messages = template['messages'] |
| # Keep track of all items that can be referred to by an id. |
| # This is used for '$ref' fields in the policy templates. |
| ref_ids_schemas = {} |
| policies = [] |
| for policy_def in template['policy_definitions']: |
| # Iterate over all policies, even the policies contained inside a policy |
| # group. |
| if policy_def['type'] == 'group': |
| policies += policy_def['policies'] |
| else: |
| policies += [policy_def] |
| |
| for policy in policies: |
| if policy['type'] == 'int-enum' or policy['type'] == 'string-enum': |
| self.RecordEnumIds(policy, ref_ids_schemas) |
| elif 'schema' in policy: |
| if 'id' in policy['schema']: |
| self.RecordKnownPropertyIds(policy['schema'], ref_ids_schemas) |
| if 'items' in policy['schema']: |
| self.RecordKnownPropertyIds(policy['schema']['items'], |
| ref_ids_schemas) |
| if 'properties' in policy['schema']: |
| self.RecordKnownPropertyIds(policy['schema']['properties'], |
| ref_ids_schemas) |
| if 'patternProperties' in policy['schema']: |
| self.RecordKnownPropertyIds(policy['schema']['patternProperties'], |
| ref_ids_schemas) |
| |
| policies = [policy for policy in policies if self.IsPolicySupported(policy)] |
| output = { |
| 'title': self.config['bundle_id'], |
| 'version': self.config['version'].split(".", 1)[0], |
| 'description': self.config['app_name'], |
| 'options': { |
| 'remove_empty_properties': True |
| }, |
| 'properties': {} |
| } |
| |
| for policy in policies: |
| output['properties'][policy['name']] = { |
| 'title': |
| policy['name'], |
| 'description': |
| policy['caption'], |
| 'type': |
| self.TYPE_TO_INPUT[policy['type']], |
| 'links': [{ |
| 'rel': self.messages['doc_policy_documentation']['text'], |
| 'href': self.config['doc_url'] + '#' + policy['name'] |
| }] |
| } |
| |
| policy_output = output['properties'][policy['name']] |
| if policy['id'] in self.POLICY_ID_TO_INPUT: |
| policy_output['type'] = self.POLICY_ID_TO_INPUT[policy['id']] |
| |
| policy_type = policy_output['type'] |
| if policy['type'] == 'int-enum' or policy['type'] == 'string-enum': |
| policy_output['options'] = { |
| 'enum_titles': [item['name'] for item in policy['items']] |
| } |
| policy_output['enum'] = [item['value'] for item in policy['items']] |
| elif policy['type'] == 'int' and 'schema' in policy: |
| if 'minimum' in policy['schema']: |
| policy_output['minimum'] = policy['schema']['minimum'] |
| if 'maximum' in policy['schema']: |
| policy_output['maximum'] = policy['schema']['maximum'] |
| elif policy['type'] == 'list': |
| policy_output['items'] = policy['schema']['items'] |
| elif policy['type'] == 'string-enum-list' or policy[ |
| 'type'] == 'int-enum-list': |
| policy_output['items'] = { |
| 'type': policy['schema']['items']['type'], |
| 'options': { |
| 'enum_titles': [item['name'] for item in policy['items']] |
| }, |
| 'enum': [item['value'] for item in policy['items']] |
| } |
| elif policy_output['type'] == 'object' and policy['type'] != 'external': |
| policy_output['type'] = policy['schema']['type'] |
| if policy_output['type'] == 'array': |
| policy_output['items'] = policy['schema']['items'] |
| self.WriteRefItems(policy_output['items'], policy_output['items'], [], |
| ref_ids_schemas, set()) |
| elif policy_output['type'] == 'object': |
| policy_output['properties'] = policy['schema']['properties'] |
| self.WriteRefItems(policy_output['properties'], |
| policy_output['properties'], [], ref_ids_schemas, |
| set()) |
| |
| return json.dumps(output, indent=2, sort_keys=True, separators=(',', ': ')) |
| |
| def RecordEnumIds(self, policy, known_ids): |
| '''Writes the a dictionary mapping ids of enums that can be referred to by |
| '$ref' to their schema. |
| |
| Args: |
| policy: The policy to scan for refs. |
| known_ids: The dictionary and output of all the known ids. |
| ''' |
| if 'id' in policy['schema']: |
| known_ids[policy['schema']['id']] = { |
| 'type': policy['schema']['type'], |
| 'options': { |
| 'enum_titles': [item['name'] for item in policy['items']] |
| }, |
| 'enum': [item['value'] for item in policy['items']] |
| } |
| |
| def RecordKnownPropertyIds(self, obj, known_ids): |
| '''Writes the a dictionary mapping ids of schemas properties that can be |
| referred to by '$ref' to their schema. |
| |
| Args: |
| obj: The object to scan for refs. |
| known_ids: The dictionary and output of all the known ids. |
| ''' |
| if type(obj) is not dict: |
| return |
| if 'id' in obj: |
| known_ids[obj['id']] = obj |
| for value in obj.values(): |
| self.RecordKnownPropertyIds(value, known_ids) |
| |
| def WriteRefItems(self, root, obj, path_to_obj_parent, known_ids, |
| ids_in_ancestry): |
| '''Replaces all the '$ref' items by their actual value. Nested properties |
| are limited to a depth of MAX_RECURSIVE_FIELDS_DEPTH, after which the |
| recursive field is removed. |
| |
| Args: |
| root: The root of the object tree to scan for refs. |
| obj: The current object being checked for ids. |
| path_to_obj_parent: A array of all the keys leading to the parent of |obj| |
| starting at |root|. |
| known_ids: The dictionary of all the known ids. |
| ids_in_ancestry: A list of ids found in the tree starting at root. Use to |
| keep nested fields in check. |
| ''' |
| if type(obj) is not dict: |
| return |
| if 'id' in obj: |
| ids_in_ancestry.add(obj['id']) |
| # Make a copy of items since we are going to change |obj|. |
| for key, value in list(obj.items()): |
| if type(value) is not dict: |
| continue |
| if '$ref' in value: |
| # If the id is an ancestor, we have a nested field. |
| if value['$ref'] in ids_in_ancestry: |
| id = value['$ref'] |
| |
| last_obj = None |
| parent = root |
| grandparent = root |
| |
| # Find the parent and grandparent of obj to create the |last_obj| |
| # which is the field where the nesting stops. |
| for i in range(0, len(path_to_obj_parent)): |
| if i + 1 < len(path_to_obj_parent): |
| grandparent = grandparent[path_to_obj_parent[i]] |
| else: |
| parent = grandparent[path_to_obj_parent[i]] |
| # Remove the link between grand parent and parent so we can have a |
| # copy of the object without nesting. |
| grandparent[path_to_obj_parent[i]] = None |
| del grandparent[path_to_obj_parent[i]] |
| # last_obj is a copy of the reference object without nesting. |
| last_obj = copy.deepcopy(known_ids[id]) |
| # Re-establish the link between grand parent and parent. |
| grandparent[path_to_obj_parent[i]] = parent |
| |
| del obj[key] |
| obj[key] = last_obj |
| # Create nested '$ref' objects with |last_obj| as the last object. |
| for count in range(1, self.MAX_RECURSIVE_FIELDS_DEPTH): |
| obj[key] = copy.deepcopy(known_ids[id]) |
| obj_grandparent_ref = path_to_obj_parent[len(path_to_obj_parent) - 1] |
| else: |
| # If no nested field, simply assign the '$ref'. |
| obj[key] = dict(known_ids[value['$ref']]) |
| self.WriteRefItems(root, obj[key], path_to_obj_parent + [key], |
| known_ids, ids_in_ancestry) |
| else: |
| self.WriteRefItems(root, value, path_to_obj_parent + [key], known_ids, |
| ids_in_ancestry) |