Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 '''python %prog [options] platform chromium_os_flag template
      7 
      8 platform specifies which platform source is being generated for
      9   and can be one of (win, mac, linux)
     10 chromium_os_flag should be 1 if this is a Chromium OS build
     11 template is the path to a .json policy template file.'''
     12 
     13 from __future__ import with_statement
     14 from functools import partial
     15 import json
     16 from optparse import OptionParser
     17 import re
     18 import sys
     19 import textwrap
     20 import types
     21 
     22 
     23 CHROME_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Google\\\\Chrome'
     24 CHROMIUM_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Chromium'
     25 
     26 
     27 class PolicyDetails:
     28   """Parses a policy template and caches all its details."""
     29 
     30   # Maps policy types to a tuple with 3 other types:
     31   # - the equivalent base::Value::Type or 'TYPE_EXTERNAL' if the policy
     32   #   references external data
     33   # - the equivalent Protobuf field type
     34   # - the name of one of the protobufs for shared policy types
     35   # TODO(joaodasilva): refactor the 'dict' type into a more generic 'json' type
     36   # that can also be used to represent lists of other JSON objects.
     37   TYPE_MAP = {
     38     'dict':             ('TYPE_DICTIONARY',   'string',       'String'),
     39     'external':         ('TYPE_EXTERNAL',     'string',       'String'),
     40     'int':              ('TYPE_INTEGER',      'int64',        'Integer'),
     41     'int-enum':         ('TYPE_INTEGER',      'int64',        'Integer'),
     42     'list':             ('TYPE_LIST',         'StringList',   'StringList'),
     43     'main':             ('TYPE_BOOLEAN',      'bool',         'Boolean'),
     44     'string':           ('TYPE_STRING',       'string',       'String'),
     45     'string-enum':      ('TYPE_STRING',       'string',       'String'),
     46     'string-enum-list': ('TYPE_LIST',         'StringList',   'StringList'),
     47   }
     48 
     49   class EnumItem:
     50     def __init__(self, item):
     51       self.caption = PolicyDetails._RemovePlaceholders(item['caption'])
     52       self.value = item['value']
     53 
     54   def __init__(self, policy, os, is_chromium_os):
     55     self.id = policy['id']
     56     self.name = policy['name']
     57     features = policy.get('features', {})
     58     self.can_be_recommended = features.get('can_be_recommended', False)
     59     self.can_be_mandatory = features.get('can_be_mandatory', True)
     60     self.is_deprecated = policy.get('deprecated', False)
     61     self.is_device_only = policy.get('device_only', False)
     62     self.schema = policy.get('schema', {})
     63     self.has_enterprise_default = 'default_for_enterprise_users' in policy
     64     if self.has_enterprise_default:
     65       self.enterprise_default = policy['default_for_enterprise_users']
     66 
     67     expected_platform = 'chrome_os' if is_chromium_os else os.lower()
     68     self.platforms = []
     69     for platform, version in [ p.split(':') for p in policy['supported_on'] ]:
     70       if not version.endswith('-'):
     71         continue
     72 
     73       if platform.startswith('chrome.'):
     74         platform_sub = platform[7:]
     75         if platform_sub == '*':
     76           self.platforms.extend(['win', 'mac', 'linux'])
     77         else:
     78           self.platforms.append(platform_sub)
     79       else:
     80         self.platforms.append(platform)
     81 
     82     self.platforms.sort()
     83     self.is_supported = expected_platform in self.platforms
     84 
     85     if not PolicyDetails.TYPE_MAP.has_key(policy['type']):
     86       raise NotImplementedError('Unknown policy type for %s: %s' %
     87                                 (policy['name'], policy['type']))
     88     self.policy_type, self.protobuf_type, self.policy_protobuf_type = \
     89         PolicyDetails.TYPE_MAP[policy['type']]
     90     self.schema = policy['schema']
     91 
     92     self.desc = '\n'.join(
     93         map(str.strip,
     94             PolicyDetails._RemovePlaceholders(policy['desc']).splitlines()))
     95     self.caption = PolicyDetails._RemovePlaceholders(policy['caption'])
     96     self.max_size = policy.get('max_size', 0)
     97 
     98     items = policy.get('items')
     99     if items is None:
    100       self.items = None
    101     else:
    102       self.items = [ PolicyDetails.EnumItem(entry) for entry in items ]
    103 
    104   PH_PATTERN = re.compile('<ph[^>]*>([^<]*|[^<]*<ex>([^<]*)</ex>[^<]*)</ph>')
    105 
    106   # Simplistic grit placeholder stripper.
    107   @staticmethod
    108   def _RemovePlaceholders(text):
    109     result = ''
    110     pos = 0
    111     for m in PolicyDetails.PH_PATTERN.finditer(text):
    112       result += text[pos:m.start(0)]
    113       result += m.group(2) or m.group(1)
    114       pos = m.end(0)
    115     result += text[pos:]
    116     return result
    117 
    118 
    119 def main():
    120   parser = OptionParser(usage=__doc__)
    121   parser.add_option('--pch', '--policy-constants-header', dest='header_path',
    122                     help='generate header file of policy constants',
    123                     metavar='FILE')
    124   parser.add_option('--pcc', '--policy-constants-source', dest='source_path',
    125                     help='generate source file of policy constants',
    126                     metavar='FILE')
    127   parser.add_option('--cpp', '--cloud-policy-protobuf',
    128                     dest='cloud_policy_proto_path',
    129                     help='generate cloud policy protobuf file',
    130                     metavar='FILE')
    131   parser.add_option('--csp', '--chrome-settings-protobuf',
    132                     dest='chrome_settings_proto_path',
    133                     help='generate chrome settings protobuf file',
    134                     metavar='FILE')
    135   parser.add_option('--cpd', '--cloud-policy-decoder',
    136                     dest='cloud_policy_decoder_path',
    137                     help='generate C++ code decoding the cloud policy protobuf',
    138                     metavar='FILE')
    139 
    140   (opts, args) = parser.parse_args()
    141 
    142   if len(args) != 3:
    143     print 'exactly platform, chromium_os flag and input file must be specified.'
    144     parser.print_help()
    145     return 2
    146 
    147   os = args[0]
    148   is_chromium_os = args[1] == '1'
    149   template_file_name = args[2]
    150 
    151   template_file_contents = _LoadJSONFile(template_file_name)
    152   policy_details = [ PolicyDetails(policy, os, is_chromium_os)
    153                      for policy in _Flatten(template_file_contents) ]
    154   sorted_policy_details = sorted(policy_details, key=lambda policy: policy.name)
    155 
    156   def GenerateFile(path, writer, sorted=False):
    157     if path:
    158       with open(path, 'w') as f:
    159         _OutputGeneratedWarningHeader(f, template_file_name)
    160         writer(sorted and sorted_policy_details or policy_details, os, f)
    161 
    162   GenerateFile(opts.header_path, _WritePolicyConstantHeader, sorted=True)
    163   GenerateFile(opts.source_path, _WritePolicyConstantSource, sorted=True)
    164   GenerateFile(opts.cloud_policy_proto_path, _WriteCloudPolicyProtobuf)
    165   GenerateFile(opts.chrome_settings_proto_path, _WriteChromeSettingsProtobuf)
    166   GenerateFile(opts.cloud_policy_decoder_path, _WriteCloudPolicyDecoder)
    167 
    168   return 0
    169 
    170 
    171 #------------------ shared helpers ---------------------------------#
    172 
    173 def _OutputGeneratedWarningHeader(f, template_file_path):
    174   f.write('//\n'
    175           '// DO NOT MODIFY THIS FILE DIRECTLY!\n'
    176           '// IT IS GENERATED BY generate_policy_source.py\n'
    177           '// FROM ' + template_file_path + '\n'
    178           '//\n\n')
    179 
    180 
    181 COMMENT_WRAPPER = textwrap.TextWrapper()
    182 COMMENT_WRAPPER.width = 80
    183 COMMENT_WRAPPER.initial_indent = '// '
    184 COMMENT_WRAPPER.subsequent_indent = '// '
    185 COMMENT_WRAPPER.replace_whitespace = False
    186 
    187 
    188 # Writes a comment, each line prefixed by // and wrapped to 80 spaces.
    189 def _OutputComment(f, comment):
    190   for line in comment.splitlines():
    191     if len(line) == 0:
    192       f.write('//')
    193     else:
    194       f.write(COMMENT_WRAPPER.fill(line))
    195     f.write('\n')
    196 
    197 
    198 # Returns an iterator over all the policies in |template_file_contents|.
    199 def _Flatten(template_file_contents):
    200   for policy in template_file_contents['policy_definitions']:
    201     if policy['type'] == 'group':
    202       for sub_policy in policy['policies']:
    203         yield sub_policy
    204     else:
    205       yield policy
    206 
    207 
    208 def _LoadJSONFile(json_file):
    209   with open(json_file, 'r') as f:
    210     text = f.read()
    211   return eval(text)
    212 
    213 
    214 #------------------ policy constants header ------------------------#
    215 
    216 def _WritePolicyConstantHeader(policies, os, f):
    217   f.write('#ifndef CHROME_COMMON_POLICY_CONSTANTS_H_\n'
    218           '#define CHROME_COMMON_POLICY_CONSTANTS_H_\n'
    219           '\n'
    220           '#include <string>\n'
    221           '\n'
    222           '#include "base/basictypes.h"\n'
    223           '#include "base/values.h"\n'
    224           '#include "components/policy/core/common/policy_details.h"\n'
    225           '#include "components/policy/core/common/policy_map.h"\n'
    226           '\n'
    227           'namespace policy {\n'
    228           '\n'
    229           'namespace internal {\n'
    230           'struct SchemaData;\n'
    231           '}\n\n')
    232 
    233   if os == 'win':
    234     f.write('// The windows registry path where Chrome policy '
    235             'configuration resides.\n'
    236             'extern const wchar_t kRegistryChromePolicyKey[];\n')
    237 
    238   f.write('#if defined (OS_CHROMEOS)\n'
    239           '// Sets default values for enterprise users.\n'
    240           'void SetEnterpriseUsersDefaults(PolicyMap* policy_map);\n'
    241           '#endif\n'
    242           '\n'
    243           '// Returns the PolicyDetails for |policy| if |policy| is a known\n'
    244           '// Chrome policy, otherwise returns NULL.\n'
    245           'const PolicyDetails* GetChromePolicyDetails('
    246               'const std::string& policy);\n'
    247           '\n'
    248           '// Returns the schema data of the Chrome policy schema.\n'
    249           'const internal::SchemaData* GetChromeSchemaData();\n'
    250           '\n')
    251   f.write('// Key names for the policy settings.\n'
    252           'namespace key {\n\n')
    253   for policy in policies:
    254     # TODO(joaodasilva): Include only supported policies in
    255     # configuration_policy_handler.cc and configuration_policy_handler_list.cc
    256     # so that these names can be conditional on 'policy.is_supported'.
    257     # http://crbug.com/223616
    258     f.write('extern const char k' + policy.name + '[];\n')
    259   f.write('\n}  // namespace key\n\n'
    260           '}  // namespace policy\n\n'
    261           '#endif  // CHROME_COMMON_POLICY_CONSTANTS_H_\n')
    262 
    263 
    264 #------------------ policy constants source ------------------------#
    265 
    266 # A mapping of the simple schema types to base::Value::Types.
    267 SIMPLE_SCHEMA_NAME_MAP = {
    268   'boolean': 'TYPE_BOOLEAN',
    269   'integer': 'TYPE_INTEGER',
    270   'null'   : 'TYPE_NULL',
    271   'number' : 'TYPE_DOUBLE',
    272   'string' : 'TYPE_STRING',
    273 }
    274 
    275 class SchemaNodesGenerator:
    276   """Builds the internal structs to represent a JSON schema."""
    277 
    278   def __init__(self, shared_strings):
    279     """Creates a new generator.
    280 
    281     |shared_strings| is a map of strings to a C expression that evaluates to
    282     that string at runtime. This mapping can be used to reuse existing string
    283     constants."""
    284     self.shared_strings = shared_strings
    285     self.schema_nodes = []
    286     self.property_nodes = []
    287     self.properties_nodes = []
    288     self.restriction_nodes = []
    289     self.int_enums = []
    290     self.string_enums = []
    291     self.simple_types = {
    292       'boolean': None,
    293       'integer': None,
    294       'null': None,
    295       'number': None,
    296       'string': None,
    297     }
    298     self.stringlist_type = None
    299     self.ranges = {}
    300     self.id_map = {}
    301 
    302   def GetString(self, s):
    303     if s in self.shared_strings:
    304       return self.shared_strings[s]
    305     # Generate JSON escaped string, which is slightly different from desired
    306     # C/C++ escaped string. Known differences includes unicode escaping format.
    307     return json.dumps(s)
    308 
    309   def AppendSchema(self, type, extra, comment=''):
    310     index = len(self.schema_nodes)
    311     self.schema_nodes.append((type, extra, comment))
    312     return index
    313 
    314   def AppendRestriction(self, first, second):
    315     r = (str(first), str(second))
    316     if not r in self.ranges:
    317       self.ranges[r] = len(self.restriction_nodes)
    318       self.restriction_nodes.append(r)
    319     return self.ranges[r]
    320 
    321   def GetSimpleType(self, name):
    322     if self.simple_types[name] == None:
    323       self.simple_types[name] = self.AppendSchema(
    324           SIMPLE_SCHEMA_NAME_MAP[name],
    325           -1,
    326           'simple type: ' + name)
    327     return self.simple_types[name]
    328 
    329   def GetStringList(self):
    330     if self.stringlist_type == None:
    331       self.stringlist_type = self.AppendSchema(
    332           'TYPE_LIST',
    333           self.GetSimpleType('string'),
    334           'simple type: stringlist')
    335     return self.stringlist_type
    336 
    337   def SchemaHaveRestriction(self, schema):
    338     return any(keyword in schema for keyword in
    339         ['minimum', 'maximum', 'enum', 'pattern'])
    340 
    341   def IsConsecutiveInterval(self, seq):
    342     sortedSeq = sorted(seq)
    343     return all(sortedSeq[i] + 1 == sortedSeq[i + 1]
    344                for i in xrange(len(sortedSeq) - 1))
    345 
    346   def GetEnumIntegerType(self, schema, name):
    347     assert all(type(x) == int for x in schema['enum'])
    348     possible_values = schema['enum']
    349     if self.IsConsecutiveInterval(possible_values):
    350       index = self.AppendRestriction(max(possible_values), min(possible_values))
    351       return self.AppendSchema('TYPE_INTEGER', index,
    352           'integer with enumeration restriction (use range instead): %s' % name)
    353     offset_begin = len(self.int_enums)
    354     self.int_enums += possible_values
    355     offset_end = len(self.int_enums)
    356     return self.AppendSchema('TYPE_INTEGER',
    357         self.AppendRestriction(offset_begin, offset_end),
    358         'integer with enumeration restriction: %s' % name)
    359 
    360   def GetEnumStringType(self, schema, name):
    361     assert all(type(x) == str for x in schema['enum'])
    362     offset_begin = len(self.string_enums)
    363     self.string_enums += schema['enum']
    364     offset_end = len(self.string_enums)
    365     return self.AppendSchema('TYPE_STRING',
    366         self.AppendRestriction(offset_begin, offset_end),
    367         'string with enumeration restriction: %s' % name)
    368 
    369   def GetEnumType(self, schema, name):
    370     if len(schema['enum']) == 0:
    371       raise RuntimeError('Empty enumeration in %s' % name)
    372     elif schema['type'] == 'integer':
    373       return self.GetEnumIntegerType(schema, name)
    374     elif schema['type'] == 'string':
    375       return self.GetEnumStringType(schema, name)
    376     else:
    377       raise RuntimeError('Unknown enumeration type in %s' % name)
    378 
    379   def GetPatternType(self, schema, name):
    380     if schema['type'] != 'string':
    381       raise RuntimeError('Unknown pattern type in %s' % name)
    382     pattern = schema['pattern']
    383     # Try to compile the pattern to validate it, note that the syntax used
    384     # here might be slightly different from re2.
    385     # TODO(binjin): Add a python wrapper of re2 and use it here.
    386     re.compile(pattern)
    387     index = len(self.string_enums);
    388     self.string_enums.append(pattern);
    389     return self.AppendSchema('TYPE_STRING',
    390         self.AppendRestriction(index, index),
    391         'string with pattern restriction: %s' % name);
    392 
    393   def GetRangedType(self, schema, name):
    394     if schema['type'] != 'integer':
    395       raise RuntimeError('Unknown ranged type in %s' % name)
    396     min_value_set, max_value_set = False, False
    397     if 'minimum' in schema:
    398       min_value = int(schema['minimum'])
    399       min_value_set = True
    400     if 'maximum' in schema:
    401       max_value = int(schema['minimum'])
    402       max_value_set = True
    403     if min_value_set and max_value_set and min_value > max_value:
    404       raise RuntimeError('Invalid ranged type in %s' % name)
    405     index = self.AppendRestriction(
    406         str(max_value) if max_value_set else 'INT_MAX',
    407         str(min_value) if min_value_set else 'INT_MIN')
    408     return self.AppendSchema('TYPE_INTEGER',
    409         index,
    410         'integer with ranged restriction: %s' % name)
    411 
    412   def Generate(self, schema, name):
    413     """Generates the structs for the given schema.
    414 
    415     |schema|: a valid JSON schema in a dictionary.
    416     |name|: the name of the current node, for the generated comments."""
    417     if schema.has_key('$ref'):
    418       if schema.has_key('id'):
    419         raise RuntimeError("Schemas with a $ref can't have an id")
    420       if not isinstance(schema['$ref'], types.StringTypes):
    421         raise RuntimeError("$ref attribute must be a string")
    422       return schema['$ref']
    423     if schema['type'] in self.simple_types:
    424       if not self.SchemaHaveRestriction(schema):
    425         # Simple types use shared nodes.
    426         return self.GetSimpleType(schema['type'])
    427       elif 'enum' in schema:
    428         return self.GetEnumType(schema, name)
    429       elif 'pattern' in schema:
    430         return self.GetPatternType(schema, name)
    431       else:
    432         return self.GetRangedType(schema, name)
    433 
    434     if schema['type'] == 'array':
    435       # Special case for lists of strings, which is a common policy type.
    436       # The 'type' may be missing if the schema has a '$ref' attribute.
    437       if schema['items'].get('type', '') == 'string':
    438         return self.GetStringList()
    439       return self.AppendSchema('TYPE_LIST',
    440           self.GenerateAndCollectID(schema['items'], 'items of ' + name))
    441     elif schema['type'] == 'object':
    442       # Reserve an index first, so that dictionaries come before their
    443       # properties. This makes sure that the root node is the first in the
    444       # SchemaNodes array.
    445       index = self.AppendSchema('TYPE_DICTIONARY', -1)
    446 
    447       if 'additionalProperties' in schema:
    448         additionalProperties = self.GenerateAndCollectID(
    449             schema['additionalProperties'],
    450             'additionalProperties of ' + name)
    451       else:
    452         additionalProperties = -1
    453 
    454       # Properties must be sorted by name, for the binary search lookup.
    455       # Note that |properties| must be evaluated immediately, so that all the
    456       # recursive calls to Generate() append the necessary child nodes; if
    457       # |properties| were a generator then this wouldn't work.
    458       sorted_properties = sorted(schema.get('properties', {}).items())
    459       properties = [
    460           (self.GetString(key), self.GenerateAndCollectID(subschema, key))
    461           for key, subschema in sorted_properties ]
    462 
    463       pattern_properties = []
    464       for pattern, subschema in schema.get('patternProperties', {}).items():
    465         pattern_properties.append((self.GetString(pattern),
    466             self.GenerateAndCollectID(subschema, pattern)));
    467 
    468       begin = len(self.property_nodes)
    469       self.property_nodes += properties
    470       end = len(self.property_nodes)
    471       self.property_nodes += pattern_properties
    472       pattern_end = len(self.property_nodes)
    473 
    474       if index == 0:
    475         self.root_properties_begin = begin
    476         self.root_properties_end = end
    477 
    478       extra = len(self.properties_nodes)
    479       self.properties_nodes.append((begin, end, pattern_end,
    480           additionalProperties, name))
    481 
    482       # Set the right data at |index| now.
    483       self.schema_nodes[index] = ('TYPE_DICTIONARY', extra, name)
    484       return index
    485     else:
    486       assert False
    487 
    488   def GenerateAndCollectID(self, schema, name):
    489     """A wrapper of Generate(), will take the return value, check and add 'id'
    490     attribute to self.id_map. The wrapper needs to be used for every call to
    491     Generate().
    492     """
    493     index = self.Generate(schema, name)
    494     if not schema.has_key('id'):
    495       return index
    496     id_str = schema['id']
    497     if self.id_map.has_key(id_str):
    498       raise RuntimeError('Duplicated id: ' + id_str)
    499     self.id_map[id_str] = index
    500     return index
    501 
    502   def Write(self, f):
    503     """Writes the generated structs to the given file.
    504 
    505     |f| an open file to write to."""
    506     f.write('const internal::SchemaNode kSchemas[] = {\n'
    507             '//  Type                          Extra\n')
    508     for type, extra, comment in self.schema_nodes:
    509       type += ','
    510       f.write('  { base::Value::%-18s %3d },  // %s\n' % (type, extra, comment))
    511     f.write('};\n\n')
    512 
    513     if self.property_nodes:
    514       f.write('const internal::PropertyNode kPropertyNodes[] = {\n'
    515               '//  Property                                          #Schema\n')
    516       for key, schema in self.property_nodes:
    517         key += ','
    518         f.write('  { %-50s %6d },\n' % (key, schema))
    519       f.write('};\n\n')
    520 
    521     if self.properties_nodes:
    522       f.write('const internal::PropertiesNode kProperties[] = {\n'
    523               '//  Begin    End  PatternEnd Additional Properties\n')
    524       for node in self.properties_nodes:
    525         f.write('  { %5d, %5d, %10d, %5d },  // %s\n' % node)
    526       f.write('};\n\n')
    527 
    528     if self.restriction_nodes:
    529       f.write('const internal::RestrictionNode kRestrictionNodes[] = {\n')
    530       f.write('//   FIRST, SECOND\n')
    531       for first, second in self.restriction_nodes:
    532         f.write('  {{ %-8s %4s}},\n' % (first + ',', second))
    533       f.write('};\n\n')
    534 
    535     if self.int_enums:
    536       f.write('const int kIntegerEnumerations[] = {\n')
    537       for possible_values in self.int_enums:
    538         f.write('  %d,\n' % possible_values)
    539       f.write('};\n\n')
    540 
    541     if self.string_enums:
    542       f.write('const char* kStringEnumerations[] = {\n')
    543       for possible_values in self.string_enums:
    544         f.write('  %s,\n' % self.GetString(possible_values))
    545       f.write('};\n\n')
    546 
    547     f.write('const internal::SchemaData kChromeSchemaData = {\n'
    548             '  kSchemas,\n')
    549     f.write('  kPropertyNodes,\n' if self.property_nodes else '  NULL,\n')
    550     f.write('  kProperties,\n' if self.properties_nodes else '  NULL,\n')
    551     f.write('  kRestrictionNodes,\n' if self.restriction_nodes else '  NULL,\n')
    552     f.write('  kIntegerEnumerations,\n' if self.int_enums else '  NULL,\n')
    553     f.write('  kStringEnumerations,\n' if self.string_enums else '  NULL,\n')
    554     f.write('};\n\n')
    555 
    556   def GetByID(self, id_str):
    557     if not isinstance(id_str, types.StringTypes):
    558       return id_str
    559     if not self.id_map.has_key(id_str):
    560       raise RuntimeError('Invalid $ref: ' + id_str)
    561     return self.id_map[id_str]
    562 
    563   def ResolveID(self, index, params):
    564     return params[:index] + (self.GetByID(params[index]),) + params[index+1:]
    565 
    566   def ResolveReferences(self):
    567     """Resolve reference mapping, required to be called after Generate()
    568 
    569     After calling Generate(), the type of indices used in schema structures
    570     might be either int or string. An int type suggests that it's a resolved
    571     index, but for string type it's unresolved. Resolving a reference is as
    572     simple as looking up for corresponding ID in self.id_map, and replace the
    573     old index with the mapped index.
    574     """
    575     self.schema_nodes = map(partial(self.ResolveID, 1), self.schema_nodes)
    576     self.property_nodes = map(partial(self.ResolveID, 1), self.property_nodes)
    577     self.properties_nodes = map(partial(self.ResolveID, 3),
    578         self.properties_nodes)
    579 
    580 def _WritePolicyConstantSource(policies, os, f):
    581   f.write('#include "policy/policy_constants.h"\n'
    582           '\n'
    583           '#include <algorithm>\n'
    584           '#include <climits>\n'
    585           '\n'
    586           '#include "base/logging.h"\n'
    587           '#include "components/policy/core/common/schema_internal.h"\n'
    588           '\n'
    589           'namespace policy {\n'
    590           '\n'
    591           'namespace {\n'
    592           '\n')
    593 
    594   # Generate the Chrome schema.
    595   chrome_schema = {
    596     'type': 'object',
    597     'properties': {},
    598   }
    599   shared_strings = {}
    600   for policy in policies:
    601     shared_strings[policy.name] = "key::k%s" % policy.name
    602     if policy.is_supported:
    603       chrome_schema['properties'][policy.name] = policy.schema
    604 
    605   # Note: this list must be kept in sync with the known property list of the
    606   # Chrome schema, so that binary seaching in the PropertyNode array gets the
    607   # right index on this array as well. See the implementation of
    608   # GetChromePolicyDetails() below.
    609   f.write('const PolicyDetails kChromePolicyDetails[] = {\n'
    610           '//  is_deprecated  is_device_policy  id    max_external_data_size\n')
    611   for policy in policies:
    612     if policy.is_supported:
    613       f.write('  { %-14s %-16s %3s, %24s },\n' % (
    614                   'true,' if policy.is_deprecated else 'false,',
    615                   'true,' if policy.is_device_only else 'false,',
    616                   policy.id,
    617                   policy.max_size))
    618   f.write('};\n\n')
    619 
    620   schema_generator = SchemaNodesGenerator(shared_strings)
    621   schema_generator.GenerateAndCollectID(chrome_schema, 'root node')
    622   schema_generator.ResolveReferences()
    623   schema_generator.Write(f)
    624 
    625   f.write('bool CompareKeys(const internal::PropertyNode& node,\n'
    626           '                 const std::string& key) {\n'
    627           '  return node.key < key;\n'
    628           '}\n\n')
    629 
    630   f.write('}  // namespace\n\n')
    631 
    632   if os == 'win':
    633     f.write('#if defined(GOOGLE_CHROME_BUILD)\n'
    634             'const wchar_t kRegistryChromePolicyKey[] = '
    635             'L"' + CHROME_POLICY_KEY + '";\n'
    636             '#else\n'
    637             'const wchar_t kRegistryChromePolicyKey[] = '
    638             'L"' + CHROMIUM_POLICY_KEY + '";\n'
    639             '#endif\n\n')
    640 
    641   f.write('const internal::SchemaData* GetChromeSchemaData() {\n'
    642           '  return &kChromeSchemaData;\n'
    643           '}\n\n')
    644 
    645   f.write('#if defined (OS_CHROMEOS)\n'
    646           'void SetEnterpriseUsersDefaults(PolicyMap* policy_map) {\n')
    647 
    648   for policy in policies:
    649     if policy.has_enterprise_default:
    650       if policy.policy_type == 'TYPE_BOOLEAN':
    651         creation_expression = 'new base::FundamentalValue(%s)' %\
    652                               ('true' if policy.enterprise_default else 'false')
    653       elif policy.policy_type == 'TYPE_INTEGER':
    654         creation_expression = 'new base::FundamentalValue(%s)' %\
    655                               policy.enterprise_default
    656       elif policy.policy_type == 'TYPE_STRING':
    657         creation_expression = 'new base::StringValue("%s")' %\
    658                               policy.enterprise_default
    659       else:
    660         raise RuntimeError('Type %s of policy %s is not supported at '
    661                            'enterprise defaults' % (policy.policy_type,
    662                                                     policy.name))
    663       f.write('  if (!policy_map->Get(key::k%s)) {\n'
    664               '    policy_map->Set(key::k%s,\n'
    665               '                    POLICY_LEVEL_MANDATORY,\n'
    666               '                    POLICY_SCOPE_USER,\n'
    667               '                    %s,\n'
    668               '                    NULL);\n'
    669               '  }\n' % (policy.name, policy.name, creation_expression))
    670 
    671   f.write('}\n'
    672           '#endif\n\n')
    673 
    674   f.write('const PolicyDetails* GetChromePolicyDetails('
    675               'const std::string& policy) {\n'
    676           '  // First index in kPropertyNodes of the Chrome policies.\n'
    677           '  static const int begin_index = %s;\n'
    678           '  // One-past-the-end of the Chrome policies in kPropertyNodes.\n'
    679           '  static const int end_index = %s;\n' %
    680           (schema_generator.root_properties_begin,
    681            schema_generator.root_properties_end))
    682   f.write('  const internal::PropertyNode* begin =\n'
    683           '      kPropertyNodes + begin_index;\n'
    684           '  const internal::PropertyNode* end = kPropertyNodes + end_index;\n'
    685           '  const internal::PropertyNode* it =\n'
    686           '      std::lower_bound(begin, end, policy, CompareKeys);\n'
    687           '  if (it == end || it->key != policy)\n'
    688           '    return NULL;\n'
    689           '  // This relies on kPropertyNodes from begin_index to end_index\n'
    690           '  // having exactly the same policies (and in the same order) as\n'
    691           '  // kChromePolicyDetails, so that binary searching on the first\n'
    692           '  // gets the same results as a binary search on the second would.\n'
    693           '  // However, kPropertyNodes has the policy names and\n'
    694           '  // kChromePolicyDetails doesn\'t, so we obtain the index into\n'
    695           '  // the second array by searching the first to avoid duplicating\n'
    696           '  // the policy name pointers.\n'
    697           '  // Offsetting |it| from |begin| here obtains the index we\'re\n'
    698           '  // looking for.\n'
    699           '  size_t index = it - begin;\n'
    700           '  CHECK_LT(index, arraysize(kChromePolicyDetails));\n'
    701           '  return kChromePolicyDetails + index;\n'
    702           '}\n\n')
    703 
    704   f.write('namespace key {\n\n')
    705   for policy in policies:
    706     # TODO(joaodasilva): Include only supported policies in
    707     # configuration_policy_handler.cc and configuration_policy_handler_list.cc
    708     # so that these names can be conditional on 'policy.is_supported'.
    709     # http://crbug.com/223616
    710     f.write('const char k{name}[] = "{name}";\n'.format(name=policy.name))
    711   f.write('\n}  // namespace key\n\n'
    712           '}  // namespace policy\n')
    713 
    714 
    715 #------------------ policy protobufs --------------------------------#
    716 
    717 CHROME_SETTINGS_PROTO_HEAD = '''
    718 syntax = "proto2";
    719 
    720 option optimize_for = LITE_RUNTIME;
    721 
    722 package enterprise_management;
    723 
    724 // For StringList and PolicyOptions.
    725 import "cloud_policy.proto";
    726 
    727 '''
    728 
    729 
    730 CLOUD_POLICY_PROTO_HEAD = '''
    731 syntax = "proto2";
    732 
    733 option optimize_for = LITE_RUNTIME;
    734 
    735 package enterprise_management;
    736 
    737 message StringList {
    738   repeated string entries = 1;
    739 }
    740 
    741 message PolicyOptions {
    742   enum PolicyMode {
    743     // The given settings are applied regardless of user choice.
    744     MANDATORY = 0;
    745     // The user may choose to override the given settings.
    746     RECOMMENDED = 1;
    747     // No policy value is present and the policy should be ignored.
    748     UNSET = 2;
    749   }
    750   optional PolicyMode mode = 1 [default = MANDATORY];
    751 }
    752 
    753 message BooleanPolicyProto {
    754   optional PolicyOptions policy_options = 1;
    755   optional bool value = 2;
    756 }
    757 
    758 message IntegerPolicyProto {
    759   optional PolicyOptions policy_options = 1;
    760   optional int64 value = 2;
    761 }
    762 
    763 message StringPolicyProto {
    764   optional PolicyOptions policy_options = 1;
    765   optional string value = 2;
    766 }
    767 
    768 message StringListPolicyProto {
    769   optional PolicyOptions policy_options = 1;
    770   optional StringList value = 2;
    771 }
    772 
    773 '''
    774 
    775 
    776 # Field IDs [1..RESERVED_IDS] will not be used in the wrapping protobuf.
    777 RESERVED_IDS = 2
    778 
    779 
    780 def _WritePolicyProto(f, policy, fields):
    781   _OutputComment(f, policy.caption + '\n\n' + policy.desc)
    782   if policy.items is not None:
    783     _OutputComment(f, '\nValid values:')
    784     for item in policy.items:
    785       _OutputComment(f, '  %s: %s' % (str(item.value), item.caption))
    786   if policy.policy_type == 'TYPE_DICTIONARY':
    787     _OutputComment(f, '\nValue schema:\n%s' %
    788                    json.dumps(policy.schema, sort_keys=True, indent=4,
    789                               separators=(',', ': ')))
    790   _OutputComment(f, '\nSupported on: %s' % ', '.join(policy.platforms))
    791   if policy.can_be_recommended and not policy.can_be_mandatory:
    792     _OutputComment(f, '\nNote: this policy must have a RECOMMENDED ' +\
    793                       'PolicyMode set in PolicyOptions.')
    794   f.write('message %sProto {\n' % policy.name)
    795   f.write('  optional PolicyOptions policy_options = 1;\n')
    796   f.write('  optional %s %s = 2;\n' % (policy.protobuf_type, policy.name))
    797   f.write('}\n\n')
    798   fields += [ '  optional %sProto %s = %s;\n' %
    799               (policy.name, policy.name, policy.id + RESERVED_IDS) ]
    800 
    801 
    802 def _WriteChromeSettingsProtobuf(policies, os, f):
    803   f.write(CHROME_SETTINGS_PROTO_HEAD)
    804 
    805   fields = []
    806   f.write('// PBs for individual settings.\n\n')
    807   for policy in policies:
    808     # Note: this protobuf also gets the unsupported policies, since it's an
    809     # exaustive list of all the supported user policies on any platform.
    810     if not policy.is_device_only:
    811       _WritePolicyProto(f, policy, fields)
    812 
    813   f.write('// --------------------------------------------------\n'
    814           '// Big wrapper PB containing the above groups.\n\n'
    815           'message ChromeSettingsProto {\n')
    816   f.write(''.join(fields))
    817   f.write('}\n\n')
    818 
    819 
    820 def _WriteCloudPolicyProtobuf(policies, os, f):
    821   f.write(CLOUD_POLICY_PROTO_HEAD)
    822   f.write('message CloudPolicySettings {\n')
    823   for policy in policies:
    824     if policy.is_supported and not policy.is_device_only:
    825       f.write('  optional %sPolicyProto %s = %s;\n' %
    826               (policy.policy_protobuf_type, policy.name,
    827                policy.id + RESERVED_IDS))
    828   f.write('}\n\n')
    829 
    830 
    831 #------------------ protobuf decoder -------------------------------#
    832 
    833 CPP_HEAD = '''
    834 #include <limits>
    835 #include <string>
    836 
    837 #include "base/basictypes.h"
    838 #include "base/callback.h"
    839 #include "base/json/json_reader.h"
    840 #include "base/logging.h"
    841 #include "base/memory/scoped_ptr.h"
    842 #include "base/memory/weak_ptr.h"
    843 #include "base/values.h"
    844 #include "components/policy/core/common/cloud/cloud_external_data_manager.h"
    845 #include "components/policy/core/common/external_data_fetcher.h"
    846 #include "components/policy/core/common/policy_map.h"
    847 #include "policy/policy_constants.h"
    848 #include "policy/proto/cloud_policy.pb.h"
    849 
    850 using google::protobuf::RepeatedPtrField;
    851 
    852 namespace policy {
    853 
    854 namespace em = enterprise_management;
    855 
    856 base::Value* DecodeIntegerValue(google::protobuf::int64 value) {
    857   if (value < std::numeric_limits<int>::min() ||
    858       value > std::numeric_limits<int>::max()) {
    859     LOG(WARNING) << "Integer value " << value
    860                  << " out of numeric limits, ignoring.";
    861     return NULL;
    862   }
    863 
    864   return new base::FundamentalValue(static_cast<int>(value));
    865 }
    866 
    867 base::ListValue* DecodeStringList(const em::StringList& string_list) {
    868   base::ListValue* list_value = new base::ListValue;
    869   RepeatedPtrField<std::string>::const_iterator entry;
    870   for (entry = string_list.entries().begin();
    871        entry != string_list.entries().end(); ++entry) {
    872     list_value->AppendString(*entry);
    873   }
    874   return list_value;
    875 }
    876 
    877 base::Value* DecodeJson(const std::string& json) {
    878   scoped_ptr<base::Value> root(
    879       base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS));
    880 
    881   if (!root)
    882     LOG(WARNING) << "Invalid JSON string, ignoring: " << json;
    883 
    884   // Accept any Value type that parsed as JSON, and leave it to the handler to
    885   // convert and check the concrete type.
    886   return root.release();
    887 }
    888 
    889 void DecodePolicy(const em::CloudPolicySettings& policy,
    890                   base::WeakPtr<CloudExternalDataManager> external_data_manager,
    891                   PolicyMap* map) {
    892 '''
    893 
    894 
    895 CPP_FOOT = '''}
    896 
    897 }  // namespace policy
    898 '''
    899 
    900 
    901 def _CreateValue(type, arg):
    902   if type == 'TYPE_BOOLEAN':
    903     return 'new base::FundamentalValue(%s)' % arg
    904   elif type == 'TYPE_INTEGER':
    905     return 'DecodeIntegerValue(%s)' % arg
    906   elif type == 'TYPE_STRING':
    907     return 'new base::StringValue(%s)' % arg
    908   elif type == 'TYPE_LIST':
    909     return 'DecodeStringList(%s)' % arg
    910   elif type == 'TYPE_DICTIONARY' or type == 'TYPE_EXTERNAL':
    911     return 'DecodeJson(%s)' % arg
    912   else:
    913     raise NotImplementedError('Unknown type %s' % type)
    914 
    915 
    916 def _CreateExternalDataFetcher(type, name):
    917   if type == 'TYPE_EXTERNAL':
    918     return 'new ExternalDataFetcher(external_data_manager, key::k%s)' % name
    919   return 'NULL'
    920 
    921 
    922 def _WritePolicyCode(f, policy):
    923   membername = policy.name.lower()
    924   proto_type = '%sPolicyProto' % policy.policy_protobuf_type
    925   f.write('  if (policy.has_%s()) {\n' % membername)
    926   f.write('    const em::%s& policy_proto = policy.%s();\n' %
    927           (proto_type, membername))
    928   f.write('    if (policy_proto.has_value()) {\n')
    929   f.write('      PolicyLevel level = POLICY_LEVEL_MANDATORY;\n'
    930           '      bool do_set = true;\n'
    931           '      if (policy_proto.has_policy_options()) {\n'
    932           '        do_set = false;\n'
    933           '        switch(policy_proto.policy_options().mode()) {\n'
    934           '          case em::PolicyOptions::MANDATORY:\n'
    935           '            do_set = true;\n'
    936           '            level = POLICY_LEVEL_MANDATORY;\n'
    937           '            break;\n'
    938           '          case em::PolicyOptions::RECOMMENDED:\n'
    939           '            do_set = true;\n'
    940           '            level = POLICY_LEVEL_RECOMMENDED;\n'
    941           '            break;\n'
    942           '          case em::PolicyOptions::UNSET:\n'
    943           '            break;\n'
    944           '        }\n'
    945           '      }\n'
    946           '      if (do_set) {\n')
    947   f.write('        base::Value* value = %s;\n' %
    948           (_CreateValue(policy.policy_type, 'policy_proto.value()')))
    949   # TODO(bartfab): |value| == NULL indicates that the policy value could not be
    950   # parsed successfully. Surface such errors in the UI.
    951   f.write('        if (value) {\n')
    952   f.write('          ExternalDataFetcher* external_data_fetcher = %s;\n' %
    953           _CreateExternalDataFetcher(policy.policy_type, policy.name))
    954   f.write('          map->Set(key::k%s, level, POLICY_SCOPE_USER,\n' %
    955           policy.name)
    956   f.write('                   value, external_data_fetcher);\n'
    957           '        }\n'
    958           '      }\n'
    959           '    }\n'
    960           '  }\n')
    961 
    962 
    963 def _WriteCloudPolicyDecoder(policies, os, f):
    964   f.write(CPP_HEAD)
    965   for policy in policies:
    966     if policy.is_supported and not policy.is_device_only:
    967       _WritePolicyCode(f, policy)
    968   f.write(CPP_FOOT)
    969 
    970 
    971 if __name__ == '__main__':
    972   sys.exit(main())
    973