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