| #!/usr/bin/env python |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| '''python %prog [options] platform chromium_os_flag template |
| |
| platform specifies which platform source is being generated for |
| and can be one of (win, mac, linux) |
| chromium_os_flag should be 1 if this is a Chromium OS build |
| template is the path to a .json policy template file.''' |
| |
| from __future__ import with_statement |
| from collections import OrderedDict |
| from functools import partial |
| import json |
| from optparse import OptionParser |
| import re |
| import sys |
| import textwrap |
| import types |
| from xml.sax.saxutils import escape as xml_escape |
| |
| |
| CHROME_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Google\\\\Chrome' |
| CHROMIUM_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Chromium' |
| |
| |
| class PolicyDetails: |
| """Parses a policy template and caches all its details.""" |
| |
| # Maps policy types to a tuple with 4 other types: |
| # - the equivalent base::Value::Type or 'TYPE_EXTERNAL' if the policy |
| # references external data |
| # - the equivalent Protobuf field type |
| # - the name of one of the protobufs for shared policy types |
| # - the equivalent type in Android's App Restriction Schema |
| # TODO(joaodasilva): refactor the 'dict' type into a more generic 'json' type |
| # that can also be used to represent lists of other JSON objects. |
| TYPE_MAP = { |
| 'dict': ('TYPE_DICTIONARY', 'string', 'String', |
| 'string'), |
| 'external': ('TYPE_EXTERNAL', 'string', 'String', |
| 'invalid'), |
| 'int': ('TYPE_INTEGER', 'int64', 'Integer', |
| 'integer'), |
| 'int-enum': ('TYPE_INTEGER', 'int64', 'Integer', |
| 'choice'), |
| 'list': ('TYPE_LIST', 'StringList', 'StringList', |
| 'string'), |
| 'main': ('TYPE_BOOLEAN', 'bool', 'Boolean', |
| 'bool'), |
| 'string': ('TYPE_STRING', 'string', 'String', |
| 'string'), |
| 'string-enum': ('TYPE_STRING', 'string', 'String', |
| 'choice'), |
| 'string-enum-list': ('TYPE_LIST', 'StringList', 'StringList', |
| 'multi-select'), |
| } |
| |
| class EnumItem: |
| def __init__(self, item): |
| self.caption = PolicyDetails._RemovePlaceholders(item['caption']) |
| self.value = item['value'] |
| |
| def __init__(self, policy, chrome_major_version, os, is_chromium_os, |
| valid_tags): |
| self.id = policy['id'] |
| self.name = policy['name'] |
| self.tags = policy.get('tags', None) |
| self._CheckTagsValidity(valid_tags) |
| features = policy.get('features', {}) |
| self.can_be_recommended = features.get('can_be_recommended', False) |
| self.can_be_mandatory = features.get('can_be_mandatory', True) |
| self.is_deprecated = policy.get('deprecated', False) |
| self.is_device_only = policy.get('device_only', False) |
| self.schema = policy.get('schema', {}) |
| self.has_enterprise_default = 'default_for_enterprise_users' in policy |
| if self.has_enterprise_default: |
| self.enterprise_default = policy['default_for_enterprise_users'] |
| |
| expected_platform = 'chrome_os' if is_chromium_os else os.lower() |
| self.platforms = [] |
| for platform, version_range in [ p.split(':') |
| for p in policy['supported_on'] ]: |
| if self.is_device_only and platform != 'chrome_os': |
| raise RuntimeError('is_device_only is only allowed for Chrome OS: "%s"' |
| % p) |
| |
| split_result = version_range.split('-') |
| if len(split_result) != 2: |
| raise RuntimeError('supported_on must have exactly one dash: "%s"' % p) |
| (version_min, version_max) = split_result |
| if version_min == '': |
| raise RuntimeError('supported_on must define a start version: "%s"' % p) |
| |
| # Skip if the current Chromium version does not support the policy. |
| if (int(version_min) > chrome_major_version or |
| version_max != '' and int(version_max) < chrome_major_version): |
| continue |
| |
| if platform.startswith('chrome.'): |
| platform_sub = platform[7:] |
| if platform_sub == '*': |
| self.platforms.extend(['win', 'mac', 'linux']) |
| else: |
| self.platforms.append(platform_sub) |
| else: |
| self.platforms.append(platform) |
| |
| self.platforms.sort() |
| self.is_supported = expected_platform in self.platforms |
| |
| if not PolicyDetails.TYPE_MAP.has_key(policy['type']): |
| raise NotImplementedError('Unknown policy type for %s: %s' % |
| (policy['name'], policy['type'])) |
| self.policy_type, self.protobuf_type, self.policy_protobuf_type, \ |
| self.restriction_type = PolicyDetails.TYPE_MAP[policy['type']] |
| self.schema = policy['schema'] |
| |
| self.desc = '\n'.join( |
| map(str.strip, |
| PolicyDetails._RemovePlaceholders(policy['desc']).splitlines())) |
| self.caption = PolicyDetails._RemovePlaceholders(policy['caption']) |
| self.max_size = policy.get('max_size', 0) |
| |
| items = policy.get('items') |
| if items is None: |
| self.items = None |
| else: |
| self.items = [ PolicyDetails.EnumItem(entry) for entry in items ] |
| |
| PH_PATTERN = re.compile('<ph[^>]*>([^<]*|[^<]*<ex>([^<]*)</ex>[^<]*)</ph>') |
| |
| def _CheckTagsValidity(self, valid_tags): |
| if self.tags == None: |
| raise RuntimeError('Policy ' + self.name + ' has to contain a list of ' |
| 'tags!\n An empty list is also valid but means ' |
| 'setting this policy can never harm the user\'s ' |
| 'privacy or security.\n'); |
| for tag in self.tags: |
| if not tag in valid_tags: |
| raise RuntimeError('Invalid Tag:' + tag + '!\n' |
| 'Chose a valid tag from \'risk_tag_definitions\' (a ' |
| 'subproperty of root in policy_templates.json)!') |
| |
| # Simplistic grit placeholder stripper. |
| @staticmethod |
| def _RemovePlaceholders(text): |
| result = '' |
| pos = 0 |
| for m in PolicyDetails.PH_PATTERN.finditer(text): |
| result += text[pos:m.start(0)] |
| result += m.group(2) or m.group(1) |
| pos = m.end(0) |
| result += text[pos:] |
| return result |
| |
| |
| def ParseVersionFile(version_path): |
| major_version = None |
| for line in open(version_path, 'r').readlines(): |
| key, val = line.rstrip('\r\n').split('=', 1) |
| if key == 'MAJOR': |
| major_version = val |
| break |
| if major_version is None: |
| raise RuntimeError('VERSION file does not contain major version.') |
| return int(major_version) |
| |
| |
| def main(): |
| parser = OptionParser(usage=__doc__) |
| parser.add_option('--pch', '--policy-constants-header', dest='header_path', |
| help='generate header file of policy constants', |
| metavar='FILE') |
| parser.add_option('--pcc', '--policy-constants-source', dest='source_path', |
| help='generate source file of policy constants', |
| metavar='FILE') |
| parser.add_option('--cpp', '--cloud-policy-protobuf', |
| dest='cloud_policy_proto_path', |
| help='generate cloud policy protobuf file', |
| metavar='FILE') |
| parser.add_option('--csp', '--chrome-settings-protobuf', |
| dest='chrome_settings_proto_path', |
| help='generate chrome settings protobuf file', |
| metavar='FILE') |
| parser.add_option('--cpd', '--cloud-policy-decoder', |
| dest='cloud_policy_decoder_path', |
| help='generate C++ code decoding the cloud policy protobuf', |
| metavar='FILE') |
| parser.add_option('--ard', '--app-restrictions-definition', |
| dest='app_restrictions_path', |
| help='generate an XML file as specified by ' |
| 'Android\'s App Restriction Schema', |
| metavar='FILE') |
| parser.add_option('--rth', '--risk-tag-header', |
| dest='risk_header_path', |
| help='generate header file for policy risk tags', |
| metavar='FILE') |
| (opts, args) = parser.parse_args() |
| |
| if len(args) != 4: |
| print('Please specify path to src/chrome/VERSION, platform, ' |
| 'chromium_os flag and input file as positional parameters.') |
| parser.print_help() |
| return 2 |
| |
| version_path = args[0] |
| os = args[1] |
| is_chromium_os = args[2] == '1' |
| template_file_name = args[3] |
| |
| major_version = ParseVersionFile(version_path) |
| template_file_contents = _LoadJSONFile(template_file_name) |
| riskTags = RiskTags(template_file_contents) |
| policy_details = [ PolicyDetails(policy, major_version, os, is_chromium_os, |
| riskTags.GetValidTags()) |
| for policy in _Flatten(template_file_contents) ] |
| riskTags.ComputeMaxTags(policy_details) |
| sorted_policy_details = sorted(policy_details, key=lambda policy: policy.name) |
| |
| def GenerateFile(path, writer, sorted=False, xml=False): |
| if path: |
| with open(path, 'w') as f: |
| _OutputGeneratedWarningHeader(f, template_file_name, xml) |
| writer(sorted and sorted_policy_details or policy_details, |
| os, f, riskTags) |
| |
| GenerateFile(opts.header_path, _WritePolicyConstantHeader, sorted=True) |
| GenerateFile(opts.source_path, _WritePolicyConstantSource, sorted=True) |
| GenerateFile(opts.risk_header_path, _WritePolicyRiskTagHeader) |
| GenerateFile(opts.cloud_policy_proto_path, _WriteCloudPolicyProtobuf) |
| GenerateFile(opts.chrome_settings_proto_path, _WriteChromeSettingsProtobuf) |
| GenerateFile(opts.cloud_policy_decoder_path, _WriteCloudPolicyDecoder) |
| |
| if os == 'android': |
| GenerateFile(opts.app_restrictions_path, _WriteAppRestrictions, xml=True) |
| |
| return 0 |
| |
| |
| #------------------ shared helpers ---------------------------------# |
| |
| def _OutputGeneratedWarningHeader(f, template_file_path, xml_style): |
| left_margin = '//' |
| if xml_style: |
| left_margin = ' ' |
| f.write('<?xml version="1.0" encoding="utf-8"?>\n' |
| '<!--\n') |
| else: |
| f.write('//\n') |
| |
| f.write(left_margin + ' DO NOT MODIFY THIS FILE DIRECTLY!\n') |
| f.write(left_margin + ' IT IS GENERATED BY generate_policy_source.py\n') |
| f.write(left_margin + ' FROM ' + template_file_path + '\n') |
| |
| if xml_style: |
| f.write('-->\n\n') |
| else: |
| f.write(left_margin + '\n\n') |
| |
| |
| COMMENT_WRAPPER = textwrap.TextWrapper() |
| COMMENT_WRAPPER.width = 80 |
| COMMENT_WRAPPER.initial_indent = '// ' |
| COMMENT_WRAPPER.subsequent_indent = '// ' |
| COMMENT_WRAPPER.replace_whitespace = False |
| |
| |
| # Writes a comment, each line prefixed by // and wrapped to 80 spaces. |
| def _OutputComment(f, comment): |
| for line in comment.splitlines(): |
| if len(line) == 0: |
| f.write('//') |
| else: |
| f.write(COMMENT_WRAPPER.fill(line)) |
| f.write('\n') |
| |
| |
| # Returns an iterator over all the policies in |template_file_contents|. |
| def _Flatten(template_file_contents): |
| for policy in template_file_contents['policy_definitions']: |
| if policy['type'] == 'group': |
| for sub_policy in policy['policies']: |
| yield sub_policy |
| else: |
| yield policy |
| |
| |
| def _LoadJSONFile(json_file): |
| with open(json_file, 'r') as f: |
| text = f.read() |
| return eval(text) |
| |
| |
| #------------------ policy constants header ------------------------# |
| |
| def _WritePolicyConstantHeader(policies, os, f, riskTags): |
| f.write('#ifndef CHROME_COMMON_POLICY_CONSTANTS_H_\n' |
| '#define CHROME_COMMON_POLICY_CONSTANTS_H_\n' |
| '\n' |
| '#include <string>\n' |
| '\n' |
| '#include "base/values.h"\n' |
| '#include "components/policy/core/common/policy_details.h"\n' |
| '#include "components/policy/core/common/policy_map.h"\n' |
| '\n' |
| 'namespace policy {\n' |
| '\n' |
| 'namespace internal {\n' |
| 'struct SchemaData;\n' |
| '}\n\n') |
| |
| if os == 'win': |
| f.write('// The windows registry path where Chrome policy ' |
| 'configuration resides.\n' |
| 'extern const wchar_t kRegistryChromePolicyKey[];\n') |
| |
| f.write('#if defined (OS_CHROMEOS)\n' |
| '// Sets default values for enterprise users.\n' |
| 'void SetEnterpriseUsersDefaults(PolicyMap* policy_map);\n' |
| '#endif\n' |
| '\n' |
| '// Returns the PolicyDetails for |policy| if |policy| is a known\n' |
| '// Chrome policy, otherwise returns NULL.\n' |
| 'const PolicyDetails* GetChromePolicyDetails(' |
| 'const std::string& policy);\n' |
| '\n' |
| '// Returns the schema data of the Chrome policy schema.\n' |
| 'const internal::SchemaData* GetChromeSchemaData();\n' |
| '\n') |
| f.write('// Key names for the policy settings.\n' |
| 'namespace key {\n\n') |
| for policy in policies: |
| # TODO(joaodasilva): Include only supported policies in |
| # configuration_policy_handler.cc and configuration_policy_handler_list.cc |
| # so that these names can be conditional on 'policy.is_supported'. |
| # http://crbug.com/223616 |
| f.write('extern const char k' + policy.name + '[];\n') |
| f.write('\n} // namespace key\n\n' |
| '} // namespace policy\n\n' |
| '#endif // CHROME_COMMON_POLICY_CONSTANTS_H_\n') |
| |
| |
| #------------------ policy constants source ------------------------# |
| |
| # A mapping of the simple schema types to base::Value::Types. |
| SIMPLE_SCHEMA_NAME_MAP = { |
| 'boolean': 'TYPE_BOOLEAN', |
| 'integer': 'TYPE_INTEGER', |
| 'null' : 'TYPE_NULL', |
| 'number' : 'TYPE_DOUBLE', |
| 'string' : 'TYPE_STRING', |
| } |
| |
| class SchemaNodesGenerator: |
| """Builds the internal structs to represent a JSON schema.""" |
| |
| def __init__(self, shared_strings): |
| """Creates a new generator. |
| |
| |shared_strings| is a map of strings to a C expression that evaluates to |
| that string at runtime. This mapping can be used to reuse existing string |
| constants.""" |
| self.shared_strings = shared_strings |
| self.schema_nodes = [] |
| self.property_nodes = [] |
| self.properties_nodes = [] |
| self.restriction_nodes = [] |
| self.int_enums = [] |
| self.string_enums = [] |
| self.simple_types = { |
| 'boolean': None, |
| 'integer': None, |
| 'null': None, |
| 'number': None, |
| 'string': None, |
| } |
| self.stringlist_type = None |
| self.ranges = {} |
| self.id_map = {} |
| |
| def GetString(self, s): |
| if s in self.shared_strings: |
| return self.shared_strings[s] |
| # Generate JSON escaped string, which is slightly different from desired |
| # C/C++ escaped string. Known differences includes unicode escaping format. |
| return json.dumps(s) |
| |
| def AppendSchema(self, type, extra, comment=''): |
| index = len(self.schema_nodes) |
| self.schema_nodes.append((type, extra, comment)) |
| return index |
| |
| def AppendRestriction(self, first, second): |
| r = (str(first), str(second)) |
| if not r in self.ranges: |
| self.ranges[r] = len(self.restriction_nodes) |
| self.restriction_nodes.append(r) |
| return self.ranges[r] |
| |
| def GetSimpleType(self, name): |
| if self.simple_types[name] == None: |
| self.simple_types[name] = self.AppendSchema( |
| SIMPLE_SCHEMA_NAME_MAP[name], |
| -1, |
| 'simple type: ' + name) |
| return self.simple_types[name] |
| |
| def GetStringList(self): |
| if self.stringlist_type == None: |
| self.stringlist_type = self.AppendSchema( |
| 'TYPE_LIST', |
| self.GetSimpleType('string'), |
| 'simple type: stringlist') |
| return self.stringlist_type |
| |
| def SchemaHaveRestriction(self, schema): |
| return any(keyword in schema for keyword in |
| ['minimum', 'maximum', 'enum', 'pattern']) |
| |
| def IsConsecutiveInterval(self, seq): |
| sortedSeq = sorted(seq) |
| return all(sortedSeq[i] + 1 == sortedSeq[i + 1] |
| for i in xrange(len(sortedSeq) - 1)) |
| |
| def GetEnumIntegerType(self, schema, name): |
| assert all(type(x) == int for x in schema['enum']) |
| possible_values = schema['enum'] |
| if self.IsConsecutiveInterval(possible_values): |
| index = self.AppendRestriction(max(possible_values), min(possible_values)) |
| return self.AppendSchema('TYPE_INTEGER', index, |
| 'integer with enumeration restriction (use range instead): %s' % name) |
| offset_begin = len(self.int_enums) |
| self.int_enums += possible_values |
| offset_end = len(self.int_enums) |
| return self.AppendSchema('TYPE_INTEGER', |
| self.AppendRestriction(offset_begin, offset_end), |
| 'integer with enumeration restriction: %s' % name) |
| |
| def GetEnumStringType(self, schema, name): |
| assert all(type(x) == str for x in schema['enum']) |
| offset_begin = len(self.string_enums) |
| self.string_enums += schema['enum'] |
| offset_end = len(self.string_enums) |
| return self.AppendSchema('TYPE_STRING', |
| self.AppendRestriction(offset_begin, offset_end), |
| 'string with enumeration restriction: %s' % name) |
| |
| def GetEnumType(self, schema, name): |
| if len(schema['enum']) == 0: |
| raise RuntimeError('Empty enumeration in %s' % name) |
| elif schema['type'] == 'integer': |
| return self.GetEnumIntegerType(schema, name) |
| elif schema['type'] == 'string': |
| return self.GetEnumStringType(schema, name) |
| else: |
| raise RuntimeError('Unknown enumeration type in %s' % name) |
| |
| def GetPatternType(self, schema, name): |
| if schema['type'] != 'string': |
| raise RuntimeError('Unknown pattern type in %s' % name) |
| pattern = schema['pattern'] |
| # Try to compile the pattern to validate it, note that the syntax used |
| # here might be slightly different from re2. |
| # TODO(binjin): Add a python wrapper of re2 and use it here. |
| re.compile(pattern) |
| index = len(self.string_enums); |
| self.string_enums.append(pattern); |
| return self.AppendSchema('TYPE_STRING', |
| self.AppendRestriction(index, index), |
| 'string with pattern restriction: %s' % name); |
| |
| def GetRangedType(self, schema, name): |
| if schema['type'] != 'integer': |
| raise RuntimeError('Unknown ranged type in %s' % name) |
| min_value_set, max_value_set = False, False |
| if 'minimum' in schema: |
| min_value = int(schema['minimum']) |
| min_value_set = True |
| if 'maximum' in schema: |
| max_value = int(schema['minimum']) |
| max_value_set = True |
| if min_value_set and max_value_set and min_value > max_value: |
| raise RuntimeError('Invalid ranged type in %s' % name) |
| index = self.AppendRestriction( |
| str(max_value) if max_value_set else 'INT_MAX', |
| str(min_value) if min_value_set else 'INT_MIN') |
| return self.AppendSchema('TYPE_INTEGER', |
| index, |
| 'integer with ranged restriction: %s' % name) |
| |
| def Generate(self, schema, name): |
| """Generates the structs for the given schema. |
| |
| |schema|: a valid JSON schema in a dictionary. |
| |name|: the name of the current node, for the generated comments.""" |
| if schema.has_key('$ref'): |
| if schema.has_key('id'): |
| raise RuntimeError("Schemas with a $ref can't have an id") |
| if not isinstance(schema['$ref'], types.StringTypes): |
| raise RuntimeError("$ref attribute must be a string") |
| return schema['$ref'] |
| if schema['type'] in self.simple_types: |
| if not self.SchemaHaveRestriction(schema): |
| # Simple types use shared nodes. |
| return self.GetSimpleType(schema['type']) |
| elif 'enum' in schema: |
| return self.GetEnumType(schema, name) |
| elif 'pattern' in schema: |
| return self.GetPatternType(schema, name) |
| else: |
| return self.GetRangedType(schema, name) |
| |
| if schema['type'] == 'array': |
| # Special case for lists of strings, which is a common policy type. |
| # The 'type' may be missing if the schema has a '$ref' attribute. |
| if schema['items'].get('type', '') == 'string': |
| return self.GetStringList() |
| return self.AppendSchema('TYPE_LIST', |
| self.GenerateAndCollectID(schema['items'], 'items of ' + name)) |
| elif schema['type'] == 'object': |
| # Reserve an index first, so that dictionaries come before their |
| # properties. This makes sure that the root node is the first in the |
| # SchemaNodes array. |
| index = self.AppendSchema('TYPE_DICTIONARY', -1) |
| |
| if 'additionalProperties' in schema: |
| additionalProperties = self.GenerateAndCollectID( |
| schema['additionalProperties'], |
| 'additionalProperties of ' + name) |
| else: |
| additionalProperties = -1 |
| |
| # Properties must be sorted by name, for the binary search lookup. |
| # Note that |properties| must be evaluated immediately, so that all the |
| # recursive calls to Generate() append the necessary child nodes; if |
| # |properties| were a generator then this wouldn't work. |
| sorted_properties = sorted(schema.get('properties', {}).items()) |
| properties = [ |
| (self.GetString(key), self.GenerateAndCollectID(subschema, key)) |
| for key, subschema in sorted_properties ] |
| |
| pattern_properties = [] |
| for pattern, subschema in schema.get('patternProperties', {}).items(): |
| pattern_properties.append((self.GetString(pattern), |
| self.GenerateAndCollectID(subschema, pattern))); |
| |
| begin = len(self.property_nodes) |
| self.property_nodes += properties |
| end = len(self.property_nodes) |
| self.property_nodes += pattern_properties |
| pattern_end = len(self.property_nodes) |
| |
| if index == 0: |
| self.root_properties_begin = begin |
| self.root_properties_end = end |
| |
| extra = len(self.properties_nodes) |
| self.properties_nodes.append((begin, end, pattern_end, |
| additionalProperties, name)) |
| |
| # Set the right data at |index| now. |
| self.schema_nodes[index] = ('TYPE_DICTIONARY', extra, name) |
| return index |
| else: |
| assert False |
| |
| def GenerateAndCollectID(self, schema, name): |
| """A wrapper of Generate(), will take the return value, check and add 'id' |
| attribute to self.id_map. The wrapper needs to be used for every call to |
| Generate(). |
| """ |
| index = self.Generate(schema, name) |
| if not schema.has_key('id'): |
| return index |
| id_str = schema['id'] |
| if self.id_map.has_key(id_str): |
| raise RuntimeError('Duplicated id: ' + id_str) |
| self.id_map[id_str] = index |
| return index |
| |
| def Write(self, f): |
| """Writes the generated structs to the given file. |
| |
| |f| an open file to write to.""" |
| f.write('const internal::SchemaNode kSchemas[] = {\n' |
| '// Type Extra\n') |
| for type, extra, comment in self.schema_nodes: |
| type += ',' |
| f.write(' { base::Value::%-18s %3d }, // %s\n' % (type, extra, comment)) |
| f.write('};\n\n') |
| |
| if self.property_nodes: |
| f.write('const internal::PropertyNode kPropertyNodes[] = {\n' |
| '// Property #Schema\n') |
| for key, schema in self.property_nodes: |
| key += ',' |
| f.write(' { %-50s %6d },\n' % (key, schema)) |
| f.write('};\n\n') |
| |
| if self.properties_nodes: |
| f.write('const internal::PropertiesNode kProperties[] = {\n' |
| '// Begin End PatternEnd Additional Properties\n') |
| for node in self.properties_nodes: |
| f.write(' { %5d, %5d, %10d, %5d }, // %s\n' % node) |
| f.write('};\n\n') |
| |
| if self.restriction_nodes: |
| f.write('const internal::RestrictionNode kRestrictionNodes[] = {\n') |
| f.write('// FIRST, SECOND\n') |
| for first, second in self.restriction_nodes: |
| f.write(' {{ %-8s %4s}},\n' % (first + ',', second)) |
| f.write('};\n\n') |
| |
| if self.int_enums: |
| f.write('const int kIntegerEnumerations[] = {\n') |
| for possible_values in self.int_enums: |
| f.write(' %d,\n' % possible_values) |
| f.write('};\n\n') |
| |
| if self.string_enums: |
| f.write('const char* kStringEnumerations[] = {\n') |
| for possible_values in self.string_enums: |
| f.write(' %s,\n' % self.GetString(possible_values)) |
| f.write('};\n\n') |
| |
| f.write('const internal::SchemaData kChromeSchemaData = {\n' |
| ' kSchemas,\n') |
| f.write(' kPropertyNodes,\n' if self.property_nodes else ' NULL,\n') |
| f.write(' kProperties,\n' if self.properties_nodes else ' NULL,\n') |
| f.write(' kRestrictionNodes,\n' if self.restriction_nodes else ' NULL,\n') |
| f.write(' kIntegerEnumerations,\n' if self.int_enums else ' NULL,\n') |
| f.write(' kStringEnumerations,\n' if self.string_enums else ' NULL,\n') |
| f.write('};\n\n') |
| |
| def GetByID(self, id_str): |
| if not isinstance(id_str, types.StringTypes): |
| return id_str |
| if not self.id_map.has_key(id_str): |
| raise RuntimeError('Invalid $ref: ' + id_str) |
| return self.id_map[id_str] |
| |
| def ResolveID(self, index, params): |
| return params[:index] + (self.GetByID(params[index]),) + params[index + 1:] |
| |
| def ResolveReferences(self): |
| """Resolve reference mapping, required to be called after Generate() |
| |
| After calling Generate(), the type of indices used in schema structures |
| might be either int or string. An int type suggests that it's a resolved |
| index, but for string type it's unresolved. Resolving a reference is as |
| simple as looking up for corresponding ID in self.id_map, and replace the |
| old index with the mapped index. |
| """ |
| self.schema_nodes = map(partial(self.ResolveID, 1), self.schema_nodes) |
| self.property_nodes = map(partial(self.ResolveID, 1), self.property_nodes) |
| self.properties_nodes = map(partial(self.ResolveID, 3), |
| self.properties_nodes) |
| |
| def _WritePolicyConstantSource(policies, os, f, riskTags): |
| f.write('#include "policy/policy_constants.h"\n' |
| '\n' |
| '#include <algorithm>\n' |
| '#include <climits>\n' |
| '\n' |
| '#include "base/logging.h"\n' |
| '#include "base/memory/ptr_util.h"\n' |
| '#include "policy/risk_tag.h"\n' |
| '#include "components/policy/core/common/policy_types.h"\n' |
| '#include "components/policy/core/common/schema_internal.h"\n' |
| '\n' |
| 'namespace policy {\n' |
| '\n') |
| |
| # Generate the Chrome schema. |
| chrome_schema = { |
| 'type': 'object', |
| 'properties': {}, |
| } |
| shared_strings = {} |
| for policy in policies: |
| shared_strings[policy.name] = "key::k%s" % policy.name |
| if policy.is_supported: |
| chrome_schema['properties'][policy.name] = policy.schema |
| |
| # Note: this list must be kept in sync with the known property list of the |
| # Chrome schema, so that binary searching in the PropertyNode array gets the |
| # right index on this array as well. See the implementation of |
| # GetChromePolicyDetails() below. |
| f.write('const PolicyDetails kChromePolicyDetails[] = {\n' |
| '// is_deprecated is_device_policy id max_external_data_size\n') |
| for policy in policies: |
| if policy.is_supported: |
| f.write(' // %s\n' % policy.name) |
| f.write(' { %-14s %-16s %3s, %24s,\n' |
| ' %s },\n' % ( |
| 'true,' if policy.is_deprecated else 'false,', |
| 'true,' if policy.is_device_only else 'false,', |
| policy.id, |
| policy.max_size, |
| riskTags.ToInitString(policy.tags))) |
| f.write('};\n\n') |
| |
| schema_generator = SchemaNodesGenerator(shared_strings) |
| schema_generator.GenerateAndCollectID(chrome_schema, 'root node') |
| schema_generator.ResolveReferences() |
| schema_generator.Write(f) |
| |
| f.write('\n' |
| 'namespace {\n') |
| |
| f.write('bool CompareKeys(const internal::PropertyNode& node,\n' |
| ' const std::string& key) {\n' |
| ' return node.key < key;\n' |
| '}\n\n') |
| |
| f.write('} // namespace\n\n') |
| |
| if os == 'win': |
| f.write('#if defined(GOOGLE_CHROME_BUILD)\n' |
| 'const wchar_t kRegistryChromePolicyKey[] = ' |
| 'L"' + CHROME_POLICY_KEY + '";\n' |
| '#else\n' |
| 'const wchar_t kRegistryChromePolicyKey[] = ' |
| 'L"' + CHROMIUM_POLICY_KEY + '";\n' |
| '#endif\n\n') |
| |
| f.write('const internal::SchemaData* GetChromeSchemaData() {\n' |
| ' return &kChromeSchemaData;\n' |
| '}\n\n') |
| |
| f.write('#if defined (OS_CHROMEOS)\n' |
| 'void SetEnterpriseUsersDefaults(PolicyMap* policy_map) {\n') |
| |
| for policy in policies: |
| if policy.has_enterprise_default: |
| if policy.policy_type == 'TYPE_BOOLEAN': |
| creation_expression = 'new base::FundamentalValue(%s)' %\ |
| ('true' if policy.enterprise_default else 'false') |
| elif policy.policy_type == 'TYPE_INTEGER': |
| creation_expression = 'new base::FundamentalValue(%s)' %\ |
| policy.enterprise_default |
| elif policy.policy_type == 'TYPE_STRING': |
| creation_expression = 'new base::StringValue("%s")' %\ |
| policy.enterprise_default |
| else: |
| raise RuntimeError('Type %s of policy %s is not supported at ' |
| 'enterprise defaults' % (policy.policy_type, |
| policy.name)) |
| f.write(' if (!policy_map->Get(key::k%s)) {\n' |
| ' policy_map->Set(key::k%s,\n' |
| ' POLICY_LEVEL_MANDATORY,\n' |
| ' POLICY_SCOPE_USER,\n' |
| ' POLICY_SOURCE_ENTERPRISE_DEFAULT,\n' |
| ' base::WrapUnique(%s),\n' |
| ' NULL);\n' |
| ' }\n' % (policy.name, policy.name, creation_expression)) |
| |
| f.write('}\n' |
| '#endif\n\n') |
| |
| f.write('const PolicyDetails* GetChromePolicyDetails(' |
| 'const std::string& policy) {\n' |
| ' // First index in kPropertyNodes of the Chrome policies.\n' |
| ' static const int begin_index = %s;\n' |
| ' // One-past-the-end of the Chrome policies in kPropertyNodes.\n' |
| ' static const int end_index = %s;\n' % |
| (schema_generator.root_properties_begin, |
| schema_generator.root_properties_end)) |
| f.write(' const internal::PropertyNode* begin =\n' |
| ' kPropertyNodes + begin_index;\n' |
| ' const internal::PropertyNode* end = kPropertyNodes + end_index;\n' |
| ' const internal::PropertyNode* it =\n' |
| ' std::lower_bound(begin, end, policy, CompareKeys);\n' |
| ' if (it == end || it->key != policy)\n' |
| ' return NULL;\n' |
| ' // This relies on kPropertyNodes from begin_index to end_index\n' |
| ' // having exactly the same policies (and in the same order) as\n' |
| ' // kChromePolicyDetails, so that binary searching on the first\n' |
| ' // gets the same results as a binary search on the second would.\n' |
| ' // However, kPropertyNodes has the policy names and\n' |
| ' // kChromePolicyDetails doesn\'t, so we obtain the index into\n' |
| ' // the second array by searching the first to avoid duplicating\n' |
| ' // the policy name pointers.\n' |
| ' // Offsetting |it| from |begin| here obtains the index we\'re\n' |
| ' // looking for.\n' |
| ' size_t index = it - begin;\n' |
| ' CHECK_LT(index, arraysize(kChromePolicyDetails));\n' |
| ' return kChromePolicyDetails + index;\n' |
| '}\n\n') |
| |
| f.write('namespace key {\n\n') |
| for policy in policies: |
| # TODO(joaodasilva): Include only supported policies in |
| # configuration_policy_handler.cc and configuration_policy_handler_list.cc |
| # so that these names can be conditional on 'policy.is_supported'. |
| # http://crbug.com/223616 |
| f.write('const char k{name}[] = "{name}";\n'.format(name=policy.name)) |
| f.write('\n} // namespace key\n\n' |
| '} // namespace policy\n') |
| |
| |
| #------------------ policy risk tag header ------------------------# |
| |
| class RiskTags(object): |
| '''Generates files and strings to translate the parsed risk tags.''' |
| # TODO(fhorschig|tnagel): Add, Check & Generate translation descriptions. |
| |
| def __init__(self, template_file_contents): |
| self.max_tags = None |
| self.enum_for_tag = OrderedDict() # Ordered by severity as stated in JSON. |
| self._ReadRiskTagMetaData(template_file_contents) |
| |
| def GenerateEnum(self): |
| values = [' ' + self.enum_for_tag[tag] for tag in self.enum_for_tag] |
| values.append(' RISK_TAG_COUNT') |
| values.append(' RISK_TAG_NONE') |
| enum_text = 'enum RiskTag {\n' |
| enum_text +=',\n'.join(values) + '\n};\n' |
| return enum_text |
| |
| def GetMaxTags(self): |
| return str(self.max_tags) |
| |
| def GetValidTags(self): |
| return [tag for tag in self.enum_for_tag] |
| |
| def ToInitString(self, tags): |
| all_tags = [self._ToEnum(tag) for tag in tags] |
| all_tags += ["RISK_TAG_NONE" for missing in range(len(tags), self.max_tags)] |
| str_tags = "{ " + ", ".join(all_tags) + " }" |
| return "\n ".join(textwrap.wrap(str_tags, 69)) |
| |
| def ComputeMaxTags(self, policies): |
| self.max_tags = 0 |
| for policy in policies: |
| if not policy.is_supported or policy.tags == None: |
| continue; |
| self.max_tags = max(len(policy.tags), self.max_tags) |
| |
| def _ToEnum(self, tag): |
| if tag in self.enum_for_tag: |
| return self.enum_for_tag[tag] |
| raise RuntimeError('Invalid Tag:' + tag + '!\n' |
| 'Chose a valid tag from \'risk_tag_definitions\' (a ' |
| 'subproperty of root in policy_templates.json)!') |
| |
| def _ReadRiskTagMetaData(self, template_file_contents): |
| for tag in template_file_contents['risk_tag_definitions']: |
| if tag.get('name', None) == None: |
| raise RuntimeError('Tag in \'risk_tag_definitions\' without ' |
| 'description found!') |
| if tag.get('description', None) == None: |
| raise RuntimeError('Tag ' + tag['name'] + ' has no description!') |
| if tag.get('user-description', None) == None: |
| raise RuntimeError('Tag ' + tag['name'] + ' has no user-description!') |
| self.enum_for_tag[tag['name']] = "RISK_TAG_" + \ |
| tag['name'].replace("-","_").upper() |
| |
| def _WritePolicyRiskTagHeader(policies, os, f, riskTags): |
| f.write('#ifndef CHROME_COMMON_POLICY_RISK_TAG_H_\n' |
| '#define CHROME_COMMON_POLICY_RISK_TAG_H_\n' |
| '\n' |
| '#include <stddef.h>\n' |
| '\n' |
| 'namespace policy {\n' |
| '\n' + \ |
| '// The tag of a policy indicates which impact a policy can have on\n' |
| '// a user\'s privacy and/or security. Ordered descending by \n' |
| '// impact.\n' |
| '// The explanation of the single tags is stated in\n' |
| '// policy_templates.json within the \'risk_tag_definitions\' tag.' |
| '\n' + riskTags.GenerateEnum() + '\n' |
| '// This constant describes how many risk tags were used by the\n' |
| '// policy which uses the most risk tags. \n' |
| 'const size_t kMaxRiskTagCount = ' + \ |
| riskTags.GetMaxTags() + ';\n' |
| '\n' |
| '} // namespace policy\n' |
| '\n' |
| '#endif // CHROME_COMMON_POLICY_RISK_TAG_H_' |
| '\n') |
| |
| #------------------ policy protobufs --------------------------------# |
| |
| CHROME_SETTINGS_PROTO_HEAD = ''' |
| syntax = "proto2"; |
| |
| option optimize_for = LITE_RUNTIME; |
| |
| package enterprise_management; |
| |
| // For StringList and PolicyOptions. |
| import "cloud_policy.proto"; |
| |
| ''' |
| |
| |
| CLOUD_POLICY_PROTO_HEAD = ''' |
| syntax = "proto2"; |
| |
| option optimize_for = LITE_RUNTIME; |
| |
| package enterprise_management; |
| |
| message StringList { |
| repeated string entries = 1; |
| } |
| |
| message PolicyOptions { |
| enum PolicyMode { |
| // The given settings are applied regardless of user choice. |
| MANDATORY = 0; |
| // The user may choose to override the given settings. |
| RECOMMENDED = 1; |
| // No policy value is present and the policy should be ignored. |
| UNSET = 2; |
| } |
| optional PolicyMode mode = 1 [default = MANDATORY]; |
| } |
| |
| message BooleanPolicyProto { |
| optional PolicyOptions policy_options = 1; |
| optional bool value = 2; |
| } |
| |
| message IntegerPolicyProto { |
| optional PolicyOptions policy_options = 1; |
| optional int64 value = 2; |
| } |
| |
| message StringPolicyProto { |
| optional PolicyOptions policy_options = 1; |
| optional string value = 2; |
| } |
| |
| message StringListPolicyProto { |
| optional PolicyOptions policy_options = 1; |
| optional StringList value = 2; |
| } |
| |
| ''' |
| |
| |
| # Field IDs [1..RESERVED_IDS] will not be used in the wrapping protobuf. |
| RESERVED_IDS = 2 |
| |
| |
| def _WritePolicyProto(f, policy, fields): |
| _OutputComment(f, policy.caption + '\n\n' + policy.desc) |
| if policy.items is not None: |
| _OutputComment(f, '\nValid values:') |
| for item in policy.items: |
| _OutputComment(f, ' %s: %s' % (str(item.value), item.caption)) |
| if policy.policy_type == 'TYPE_DICTIONARY': |
| _OutputComment(f, '\nValue schema:\n%s' % |
| json.dumps(policy.schema, sort_keys=True, indent=4, |
| separators=(',', ': '))) |
| _OutputComment(f, '\nSupported on: %s' % ', '.join(policy.platforms)) |
| if policy.can_be_recommended and not policy.can_be_mandatory: |
| _OutputComment(f, '\nNote: this policy must have a RECOMMENDED ' +\ |
| 'PolicyMode set in PolicyOptions.') |
| f.write('message %sProto {\n' % policy.name) |
| f.write(' optional PolicyOptions policy_options = 1;\n') |
| f.write(' optional %s %s = 2;\n' % (policy.protobuf_type, policy.name)) |
| f.write('}\n\n') |
| fields += [ ' optional %sProto %s = %s;\n' % |
| (policy.name, policy.name, policy.id + RESERVED_IDS) ] |
| |
| |
| def _WriteChromeSettingsProtobuf(policies, os, f, riskTags): |
| f.write(CHROME_SETTINGS_PROTO_HEAD) |
| |
| fields = [] |
| f.write('// PBs for individual settings.\n\n') |
| for policy in policies: |
| # Note: This protobuf also gets the unsupported policies, since it's an |
| # exhaustive list of all the supported user policies on any platform. |
| if not policy.is_device_only: |
| _WritePolicyProto(f, policy, fields) |
| |
| f.write('// --------------------------------------------------\n' |
| '// Big wrapper PB containing the above groups.\n\n' |
| 'message ChromeSettingsProto {\n') |
| f.write(''.join(fields)) |
| f.write('}\n\n') |
| |
| |
| def _WriteCloudPolicyProtobuf(policies, os, f, riskTags): |
| f.write(CLOUD_POLICY_PROTO_HEAD) |
| f.write('message CloudPolicySettings {\n') |
| for policy in policies: |
| if policy.is_supported and not policy.is_device_only: |
| f.write(' optional %sPolicyProto %s = %s;\n' % |
| (policy.policy_protobuf_type, policy.name, |
| policy.id + RESERVED_IDS)) |
| f.write('}\n\n') |
| |
| |
| #------------------ protobuf decoder -------------------------------# |
| |
| CPP_HEAD = ''' |
| #include <limits> |
| #include <memory> |
| #include <utility> |
| #include <string> |
| |
| #include "base/callback.h" |
| #include "base/json/json_reader.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/values.h" |
| #include "components/policy/core/common/cloud/cloud_external_data_manager.h" |
| #include "components/policy/core/common/external_data_fetcher.h" |
| #include "components/policy/core/common/policy_map.h" |
| #include "components/policy/core/common/policy_types.h" |
| #include "policy/policy_constants.h" |
| #include "policy/proto/cloud_policy.pb.h" |
| |
| using google::protobuf::RepeatedPtrField; |
| |
| namespace policy { |
| |
| namespace em = enterprise_management; |
| |
| std::unique_ptr<base::Value> DecodeIntegerValue( |
| google::protobuf::int64 value) { |
| if (value < std::numeric_limits<int>::min() || |
| value > std::numeric_limits<int>::max()) { |
| LOG(WARNING) << "Integer value " << value |
| << " out of numeric limits, ignoring."; |
| return nullptr; |
| } |
| |
| return base::WrapUnique( |
| new base::FundamentalValue(static_cast<int>(value))); |
| } |
| |
| std::unique_ptr<base::ListValue> DecodeStringList( |
| const em::StringList& string_list) { |
| std::unique_ptr<base::ListValue> list_value(new base::ListValue); |
| for (const auto& entry : string_list.entries()) |
| list_value->AppendString(entry); |
| return list_value; |
| } |
| |
| std::unique_ptr<base::Value> DecodeJson(const std::string& json) { |
| std::unique_ptr<base::Value> root = |
| base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS); |
| |
| if (!root) |
| LOG(WARNING) << "Invalid JSON string, ignoring: " << json; |
| |
| // Accept any Value type that parsed as JSON, and leave it to the handler to |
| // convert and check the concrete type. |
| return root; |
| } |
| |
| void DecodePolicy(const em::CloudPolicySettings& policy, |
| base::WeakPtr<CloudExternalDataManager> external_data_manager, |
| PolicyMap* map) { |
| ''' |
| |
| |
| CPP_FOOT = '''} |
| |
| } // namespace policy |
| ''' |
| |
| |
| def _CreateValue(type, arg): |
| if type == 'TYPE_BOOLEAN': |
| return 'new base::FundamentalValue(%s)' % arg |
| elif type == 'TYPE_INTEGER': |
| return 'DecodeIntegerValue(%s)' % arg |
| elif type == 'TYPE_STRING': |
| return 'new base::StringValue(%s)' % arg |
| elif type == 'TYPE_LIST': |
| return 'DecodeStringList(%s)' % arg |
| elif type == 'TYPE_DICTIONARY' or type == 'TYPE_EXTERNAL': |
| return 'DecodeJson(%s)' % arg |
| else: |
| raise NotImplementedError('Unknown type %s' % type) |
| |
| |
| def _CreateExternalDataFetcher(type, name): |
| if type == 'TYPE_EXTERNAL': |
| return 'new ExternalDataFetcher(external_data_manager, key::k%s)' % name |
| return 'nullptr' |
| |
| |
| def _WritePolicyCode(f, policy): |
| membername = policy.name.lower() |
| proto_type = '%sPolicyProto' % policy.policy_protobuf_type |
| f.write(' if (policy.has_%s()) {\n' % membername) |
| f.write(' const em::%s& policy_proto = policy.%s();\n' % |
| (proto_type, membername)) |
| f.write(' if (policy_proto.has_value()) {\n') |
| f.write(' PolicyLevel level = POLICY_LEVEL_MANDATORY;\n' |
| ' bool do_set = true;\n' |
| ' if (policy_proto.has_policy_options()) {\n' |
| ' do_set = false;\n' |
| ' switch(policy_proto.policy_options().mode()) {\n' |
| ' case em::PolicyOptions::MANDATORY:\n' |
| ' do_set = true;\n' |
| ' level = POLICY_LEVEL_MANDATORY;\n' |
| ' break;\n' |
| ' case em::PolicyOptions::RECOMMENDED:\n' |
| ' do_set = true;\n' |
| ' level = POLICY_LEVEL_RECOMMENDED;\n' |
| ' break;\n' |
| ' case em::PolicyOptions::UNSET:\n' |
| ' break;\n' |
| ' }\n' |
| ' }\n' |
| ' if (do_set) {\n') |
| f.write(' std::unique_ptr<base::Value> value(%s);\n' % |
| (_CreateValue(policy.policy_type, 'policy_proto.value()'))) |
| # TODO(bartfab): |value| == NULL indicates that the policy value could not be |
| # parsed successfully. Surface such errors in the UI. |
| f.write(' if (value) {\n') |
| f.write(' std::unique_ptr<ExternalDataFetcher>\n') |
| f.write(' external_data_fetcher(%s);\n' % |
| _CreateExternalDataFetcher(policy.policy_type, policy.name)) |
| f.write(' map->Set(key::k%s, \n' % policy.name) |
| f.write(' level, \n' |
| ' POLICY_SCOPE_USER, \n' |
| ' POLICY_SOURCE_CLOUD, \n' |
| ' std::move(value), \n' |
| ' std::move(external_data_fetcher));\n' |
| ' }\n' |
| ' }\n' |
| ' }\n' |
| ' }\n') |
| |
| |
| def _WriteCloudPolicyDecoder(policies, os, f, riskTags): |
| f.write(CPP_HEAD) |
| for policy in policies: |
| if policy.is_supported and not policy.is_device_only: |
| _WritePolicyCode(f, policy) |
| f.write(CPP_FOOT) |
| |
| |
| def _WriteAppRestrictions(policies, os, f, riskTags): |
| |
| def WriteRestrictionCommon(key): |
| f.write(' <restriction\n' |
| ' android:key="%s"\n' % key) |
| f.write(' android:title="@string/%sTitle"\n' % key) |
| f.write(' android:description="@string/%sDesc"\n' % key) |
| |
| def WriteItemsDefinition(key): |
| f.write(' android:entries="@array/%sEntries"\n' % key) |
| f.write(' android:entryValues="@array/%sValues"\n' % key) |
| |
| def WriteAppRestriction(policy): |
| policy_name = policy.name |
| WriteRestrictionCommon(policy_name) |
| |
| if policy.items is not None: |
| WriteItemsDefinition(policy_name) |
| |
| f.write(' android:restrictionType="%s"/>' % policy.restriction_type) |
| f.write('\n\n') |
| |
| # _WriteAppRestrictions body |
| f.write('<restrictions xmlns:android="' |
| 'http://schemas.android.com/apk/res/android">\n\n') |
| for policy in policies: |
| if policy.is_supported and policy.restriction_type != 'invalid': |
| WriteAppRestriction(policy) |
| f.write('</restrictions>') |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |