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