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.
6 '''python %prog [options] platform chromium_os_flag template
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.'''
13 from __future__
import with_statement
14 from functools
import partial
16 from optparse
import OptionParser
21 from xml
.sax
.saxutils
import escape
as xml_escape
24 CHROME_POLICY_KEY
= 'SOFTWARE\\\\Policies\\\\Google\\\\Chrome'
25 CHROMIUM_POLICY_KEY
= 'SOFTWARE\\\\Policies\\\\Chromium'
29 """Parses a policy template and caches all its details."""
31 # Maps policy types to a tuple with 5 other types:
32 # - the equivalent base::Value::Type or 'TYPE_EXTERNAL' if the policy
33 # references external data
34 # - the equivalent Protobuf field type
35 # - the name of one of the protobufs for shared policy types
36 # - the equivalent type in Android's App Restriction Schema
37 # - whether the equivalent app restriction type needs supporting resources
38 # TODO(joaodasilva): refactor the 'dict' type into a more generic 'json' type
39 # that can also be used to represent lists of other JSON objects.
41 'dict': ('TYPE_DICTIONARY', 'string', 'String',
43 'external': ('TYPE_EXTERNAL', 'string', 'String',
45 'int': ('TYPE_INTEGER', 'int64', 'Integer',
47 'int-enum': ('TYPE_INTEGER', 'int64', 'Integer',
49 'list': ('TYPE_LIST', 'StringList', 'StringList',
51 'main': ('TYPE_BOOLEAN', 'bool', 'Boolean',
53 'string': ('TYPE_STRING', 'string', 'String',
55 'string-enum': ('TYPE_STRING', 'string', 'String',
57 'string-enum-list': ('TYPE_LIST', 'StringList', 'StringList',
58 'multi-select', True),
62 def __init__(self
, item
):
63 self
.caption
= PolicyDetails
._RemovePlaceholders
(item
['caption'])
64 self
.value
= item
['value']
66 def __init__(self
, policy
, os
, is_chromium_os
):
67 self
.id = policy
['id']
68 self
.name
= policy
['name']
69 features
= policy
.get('features', {})
70 self
.can_be_recommended
= features
.get('can_be_recommended', False)
71 self
.can_be_mandatory
= features
.get('can_be_mandatory', True)
72 self
.is_deprecated
= policy
.get('deprecated', False)
73 self
.is_device_only
= policy
.get('device_only', False)
74 self
.schema
= policy
.get('schema', {})
75 self
.has_enterprise_default
= 'default_for_enterprise_users' in policy
76 if self
.has_enterprise_default
:
77 self
.enterprise_default
= policy
['default_for_enterprise_users']
79 expected_platform
= 'chrome_os' if is_chromium_os
else os
.lower()
81 for platform
, version
in [ p
.split(':') for p
in policy
['supported_on'] ]:
82 if not version
.endswith('-'):
85 if platform
.startswith('chrome.'):
86 platform_sub
= platform
[7:]
87 if platform_sub
== '*':
88 self
.platforms
.extend(['win', 'mac', 'linux'])
90 self
.platforms
.append(platform_sub
)
92 self
.platforms
.append(platform
)
95 self
.is_supported
= expected_platform
in self
.platforms
97 if not PolicyDetails
.TYPE_MAP
.has_key(policy
['type']):
98 raise NotImplementedError('Unknown policy type for %s: %s' %
99 (policy
['name'], policy
['type']))
100 self
.policy_type
, self
.protobuf_type
, self
.policy_protobuf_type
, \
101 self
.restriction_type
, self
.has_restriction_resources
= \
102 PolicyDetails
.TYPE_MAP
[policy
['type']]
103 self
.schema
= policy
['schema']
105 self
.desc
= '\n'.join(
107 PolicyDetails
._RemovePlaceholders
(policy
['desc']).splitlines()))
108 self
.caption
= PolicyDetails
._RemovePlaceholders
(policy
['caption'])
109 self
.max_size
= policy
.get('max_size', 0)
111 items
= policy
.get('items')
115 self
.items
= [ PolicyDetails
.EnumItem(entry
) for entry
in items
]
117 PH_PATTERN
= re
.compile('<ph[^>]*>([^<]*|[^<]*<ex>([^<]*)</ex>[^<]*)</ph>')
119 # Simplistic grit placeholder stripper.
121 def _RemovePlaceholders(text
):
124 for m
in PolicyDetails
.PH_PATTERN
.finditer(text
):
125 result
+= text
[pos
:m
.start(0)]
126 result
+= m
.group(2) or m
.group(1)
133 parser
= OptionParser(usage
=__doc__
)
134 parser
.add_option('--pch', '--policy-constants-header', dest
='header_path',
135 help='generate header file of policy constants',
137 parser
.add_option('--pcc', '--policy-constants-source', dest
='source_path',
138 help='generate source file of policy constants',
140 parser
.add_option('--cpp', '--cloud-policy-protobuf',
141 dest
='cloud_policy_proto_path',
142 help='generate cloud policy protobuf file',
144 parser
.add_option('--csp', '--chrome-settings-protobuf',
145 dest
='chrome_settings_proto_path',
146 help='generate chrome settings protobuf file',
148 parser
.add_option('--cpd', '--cloud-policy-decoder',
149 dest
='cloud_policy_decoder_path',
150 help='generate C++ code decoding the cloud policy protobuf',
152 parser
.add_option('--ard', '--app-restrictions-definition',
153 dest
='app_restrictions_path',
154 help='generate an XML file as specified by '
155 'Android\'s App Restriction Schema',
157 parser
.add_option('--arr', '--app-restrictions-resources',
158 dest
='app_resources_path',
159 help='generate an XML file with resources supporting the '
160 'restrictions defined in --app-restrictions-definition '
164 (opts
, args
) = parser
.parse_args()
167 print 'exactly platform, chromium_os flag and input file must be specified.'
172 is_chromium_os
= args
[1] == '1'
173 template_file_name
= args
[2]
175 template_file_contents
= _LoadJSONFile(template_file_name
)
176 policy_details
= [ PolicyDetails(policy
, os
, is_chromium_os
)
177 for policy
in _Flatten(template_file_contents
) ]
178 sorted_policy_details
= sorted(policy_details
, key
=lambda policy
: policy
.name
)
180 def GenerateFile(path
, writer
, sorted=False, xml
=False):
182 with
open(path
, 'w') as f
:
183 _OutputGeneratedWarningHeader(f
, template_file_name
, xml
)
184 writer(sorted and sorted_policy_details
or policy_details
, os
, f
)
186 GenerateFile(opts
.header_path
, _WritePolicyConstantHeader
, sorted=True)
187 GenerateFile(opts
.source_path
, _WritePolicyConstantSource
, sorted=True)
188 GenerateFile(opts
.cloud_policy_proto_path
, _WriteCloudPolicyProtobuf
)
189 GenerateFile(opts
.chrome_settings_proto_path
, _WriteChromeSettingsProtobuf
)
190 GenerateFile(opts
.cloud_policy_decoder_path
, _WriteCloudPolicyDecoder
)
193 GenerateFile(opts
.app_restrictions_path
, _WriteAppRestrictions
, xml
=True)
194 GenerateFile(opts
.app_resources_path
, _WriteResourcesForPolicies
, xml
=True)
199 #------------------ shared helpers ---------------------------------#
201 def _OutputGeneratedWarningHeader(f
, template_file_path
, xml_style
):
205 f
.write('<?xml version="1.0" encoding="utf-8"?>\n'
210 f
.write(left_margin
+ ' DO NOT MODIFY THIS FILE DIRECTLY!\n')
211 f
.write(left_margin
+ ' IT IS GENERATED BY generate_policy_source.py\n')
212 f
.write(left_margin
+ ' FROM ' + template_file_path
+ '\n')
217 f
.write(left_margin
+ '\n\n')
220 COMMENT_WRAPPER
= textwrap
.TextWrapper()
221 COMMENT_WRAPPER
.width
= 80
222 COMMENT_WRAPPER
.initial_indent
= '// '
223 COMMENT_WRAPPER
.subsequent_indent
= '// '
224 COMMENT_WRAPPER
.replace_whitespace
= False
227 # Writes a comment, each line prefixed by // and wrapped to 80 spaces.
228 def _OutputComment(f
, comment
):
229 for line
in comment
.splitlines():
233 f
.write(COMMENT_WRAPPER
.fill(line
))
237 # Returns an iterator over all the policies in |template_file_contents|.
238 def _Flatten(template_file_contents
):
239 for policy
in template_file_contents
['policy_definitions']:
240 if policy
['type'] == 'group':
241 for sub_policy
in policy
['policies']:
247 def _LoadJSONFile(json_file
):
248 with
open(json_file
, 'r') as f
:
253 #------------------ policy constants header ------------------------#
255 def _WritePolicyConstantHeader(policies
, os
, f
):
256 f
.write('#ifndef CHROME_COMMON_POLICY_CONSTANTS_H_\n'
257 '#define CHROME_COMMON_POLICY_CONSTANTS_H_\n'
259 '#include <string>\n'
261 '#include "base/basictypes.h"\n'
262 '#include "base/values.h"\n'
263 '#include "components/policy/core/common/policy_details.h"\n'
264 '#include "components/policy/core/common/policy_map.h"\n'
266 'namespace policy {\n'
268 'namespace internal {\n'
269 'struct SchemaData;\n'
273 f
.write('// The windows registry path where Chrome policy '
274 'configuration resides.\n'
275 'extern const wchar_t kRegistryChromePolicyKey[];\n')
277 f
.write('#if defined (OS_CHROMEOS)\n'
278 '// Sets default values for enterprise users.\n'
279 'void SetEnterpriseUsersDefaults(PolicyMap* policy_map);\n'
282 '// Returns the PolicyDetails for |policy| if |policy| is a known\n'
283 '// Chrome policy, otherwise returns NULL.\n'
284 'const PolicyDetails* GetChromePolicyDetails('
285 'const std::string& policy);\n'
287 '// Returns the schema data of the Chrome policy schema.\n'
288 'const internal::SchemaData* GetChromeSchemaData();\n'
290 f
.write('// Key names for the policy settings.\n'
291 'namespace key {\n\n')
292 for policy
in policies
:
293 # TODO(joaodasilva): Include only supported policies in
294 # configuration_policy_handler.cc and configuration_policy_handler_list.cc
295 # so that these names can be conditional on 'policy.is_supported'.
296 # http://crbug.com/223616
297 f
.write('extern const char k' + policy
.name
+ '[];\n')
298 f
.write('\n} // namespace key\n\n'
299 '} // namespace policy\n\n'
300 '#endif // CHROME_COMMON_POLICY_CONSTANTS_H_\n')
303 #------------------ policy constants source ------------------------#
305 # A mapping of the simple schema types to base::Value::Types.
306 SIMPLE_SCHEMA_NAME_MAP
= {
307 'boolean': 'TYPE_BOOLEAN',
308 'integer': 'TYPE_INTEGER',
309 'null' : 'TYPE_NULL',
310 'number' : 'TYPE_DOUBLE',
311 'string' : 'TYPE_STRING',
314 class SchemaNodesGenerator
:
315 """Builds the internal structs to represent a JSON schema."""
317 def __init__(self
, shared_strings
):
318 """Creates a new generator.
320 |shared_strings| is a map of strings to a C expression that evaluates to
321 that string at runtime. This mapping can be used to reuse existing string
323 self
.shared_strings
= shared_strings
324 self
.schema_nodes
= []
325 self
.property_nodes
= []
326 self
.properties_nodes
= []
327 self
.restriction_nodes
= []
329 self
.string_enums
= []
330 self
.simple_types
= {
337 self
.stringlist_type
= None
341 def GetString(self
, s
):
342 if s
in self
.shared_strings
:
343 return self
.shared_strings
[s
]
344 # Generate JSON escaped string, which is slightly different from desired
345 # C/C++ escaped string. Known differences includes unicode escaping format.
348 def AppendSchema(self
, type, extra
, comment
=''):
349 index
= len(self
.schema_nodes
)
350 self
.schema_nodes
.append((type, extra
, comment
))
353 def AppendRestriction(self
, first
, second
):
354 r
= (str(first
), str(second
))
355 if not r
in self
.ranges
:
356 self
.ranges
[r
] = len(self
.restriction_nodes
)
357 self
.restriction_nodes
.append(r
)
358 return self
.ranges
[r
]
360 def GetSimpleType(self
, name
):
361 if self
.simple_types
[name
] == None:
362 self
.simple_types
[name
] = self
.AppendSchema(
363 SIMPLE_SCHEMA_NAME_MAP
[name
],
365 'simple type: ' + name
)
366 return self
.simple_types
[name
]
368 def GetStringList(self
):
369 if self
.stringlist_type
== None:
370 self
.stringlist_type
= self
.AppendSchema(
372 self
.GetSimpleType('string'),
373 'simple type: stringlist')
374 return self
.stringlist_type
376 def SchemaHaveRestriction(self
, schema
):
377 return any(keyword
in schema
for keyword
in
378 ['minimum', 'maximum', 'enum', 'pattern'])
380 def IsConsecutiveInterval(self
, seq
):
381 sortedSeq
= sorted(seq
)
382 return all(sortedSeq
[i
] + 1 == sortedSeq
[i
+ 1]
383 for i
in xrange(len(sortedSeq
) - 1))
385 def GetEnumIntegerType(self
, schema
, name
):
386 assert all(type(x
) == int for x
in schema
['enum'])
387 possible_values
= schema
['enum']
388 if self
.IsConsecutiveInterval(possible_values
):
389 index
= self
.AppendRestriction(max(possible_values
), min(possible_values
))
390 return self
.AppendSchema('TYPE_INTEGER', index
,
391 'integer with enumeration restriction (use range instead): %s' % name
)
392 offset_begin
= len(self
.int_enums
)
393 self
.int_enums
+= possible_values
394 offset_end
= len(self
.int_enums
)
395 return self
.AppendSchema('TYPE_INTEGER',
396 self
.AppendRestriction(offset_begin
, offset_end
),
397 'integer with enumeration restriction: %s' % name
)
399 def GetEnumStringType(self
, schema
, name
):
400 assert all(type(x
) == str for x
in schema
['enum'])
401 offset_begin
= len(self
.string_enums
)
402 self
.string_enums
+= schema
['enum']
403 offset_end
= len(self
.string_enums
)
404 return self
.AppendSchema('TYPE_STRING',
405 self
.AppendRestriction(offset_begin
, offset_end
),
406 'string with enumeration restriction: %s' % name
)
408 def GetEnumType(self
, schema
, name
):
409 if len(schema
['enum']) == 0:
410 raise RuntimeError('Empty enumeration in %s' % name
)
411 elif schema
['type'] == 'integer':
412 return self
.GetEnumIntegerType(schema
, name
)
413 elif schema
['type'] == 'string':
414 return self
.GetEnumStringType(schema
, name
)
416 raise RuntimeError('Unknown enumeration type in %s' % name
)
418 def GetPatternType(self
, schema
, name
):
419 if schema
['type'] != 'string':
420 raise RuntimeError('Unknown pattern type in %s' % name
)
421 pattern
= schema
['pattern']
422 # Try to compile the pattern to validate it, note that the syntax used
423 # here might be slightly different from re2.
424 # TODO(binjin): Add a python wrapper of re2 and use it here.
426 index
= len(self
.string_enums
);
427 self
.string_enums
.append(pattern
);
428 return self
.AppendSchema('TYPE_STRING',
429 self
.AppendRestriction(index
, index
),
430 'string with pattern restriction: %s' % name
);
432 def GetRangedType(self
, schema
, name
):
433 if schema
['type'] != 'integer':
434 raise RuntimeError('Unknown ranged type in %s' % name
)
435 min_value_set
, max_value_set
= False, False
436 if 'minimum' in schema
:
437 min_value
= int(schema
['minimum'])
439 if 'maximum' in schema
:
440 max_value
= int(schema
['minimum'])
442 if min_value_set
and max_value_set
and min_value
> max_value
:
443 raise RuntimeError('Invalid ranged type in %s' % name
)
444 index
= self
.AppendRestriction(
445 str(max_value
) if max_value_set
else 'INT_MAX',
446 str(min_value
) if min_value_set
else 'INT_MIN')
447 return self
.AppendSchema('TYPE_INTEGER',
449 'integer with ranged restriction: %s' % name
)
451 def Generate(self
, schema
, name
):
452 """Generates the structs for the given schema.
454 |schema|: a valid JSON schema in a dictionary.
455 |name|: the name of the current node, for the generated comments."""
456 if schema
.has_key('$ref'):
457 if schema
.has_key('id'):
458 raise RuntimeError("Schemas with a $ref can't have an id")
459 if not isinstance(schema
['$ref'], types
.StringTypes
):
460 raise RuntimeError("$ref attribute must be a string")
461 return schema
['$ref']
462 if schema
['type'] in self
.simple_types
:
463 if not self
.SchemaHaveRestriction(schema
):
464 # Simple types use shared nodes.
465 return self
.GetSimpleType(schema
['type'])
466 elif 'enum' in schema
:
467 return self
.GetEnumType(schema
, name
)
468 elif 'pattern' in schema
:
469 return self
.GetPatternType(schema
, name
)
471 return self
.GetRangedType(schema
, name
)
473 if schema
['type'] == 'array':
474 # Special case for lists of strings, which is a common policy type.
475 # The 'type' may be missing if the schema has a '$ref' attribute.
476 if schema
['items'].get('type', '') == 'string':
477 return self
.GetStringList()
478 return self
.AppendSchema('TYPE_LIST',
479 self
.GenerateAndCollectID(schema
['items'], 'items of ' + name
))
480 elif schema
['type'] == 'object':
481 # Reserve an index first, so that dictionaries come before their
482 # properties. This makes sure that the root node is the first in the
484 index
= self
.AppendSchema('TYPE_DICTIONARY', -1)
486 if 'additionalProperties' in schema
:
487 additionalProperties
= self
.GenerateAndCollectID(
488 schema
['additionalProperties'],
489 'additionalProperties of ' + name
)
491 additionalProperties
= -1
493 # Properties must be sorted by name, for the binary search lookup.
494 # Note that |properties| must be evaluated immediately, so that all the
495 # recursive calls to Generate() append the necessary child nodes; if
496 # |properties| were a generator then this wouldn't work.
497 sorted_properties
= sorted(schema
.get('properties', {}).items())
499 (self
.GetString(key
), self
.GenerateAndCollectID(subschema
, key
))
500 for key
, subschema
in sorted_properties
]
502 pattern_properties
= []
503 for pattern
, subschema
in schema
.get('patternProperties', {}).items():
504 pattern_properties
.append((self
.GetString(pattern
),
505 self
.GenerateAndCollectID(subschema
, pattern
)));
507 begin
= len(self
.property_nodes
)
508 self
.property_nodes
+= properties
509 end
= len(self
.property_nodes
)
510 self
.property_nodes
+= pattern_properties
511 pattern_end
= len(self
.property_nodes
)
514 self
.root_properties_begin
= begin
515 self
.root_properties_end
= end
517 extra
= len(self
.properties_nodes
)
518 self
.properties_nodes
.append((begin
, end
, pattern_end
,
519 additionalProperties
, name
))
521 # Set the right data at |index| now.
522 self
.schema_nodes
[index
] = ('TYPE_DICTIONARY', extra
, name
)
527 def GenerateAndCollectID(self
, schema
, name
):
528 """A wrapper of Generate(), will take the return value, check and add 'id'
529 attribute to self.id_map. The wrapper needs to be used for every call to
532 index
= self
.Generate(schema
, name
)
533 if not schema
.has_key('id'):
535 id_str
= schema
['id']
536 if self
.id_map
.has_key(id_str
):
537 raise RuntimeError('Duplicated id: ' + id_str
)
538 self
.id_map
[id_str
] = index
542 """Writes the generated structs to the given file.
544 |f| an open file to write to."""
545 f
.write('const internal::SchemaNode kSchemas[] = {\n'
547 for type, extra
, comment
in self
.schema_nodes
:
549 f
.write(' { base::Value::%-18s %3d }, // %s\n' % (type, extra
, comment
))
552 if self
.property_nodes
:
553 f
.write('const internal::PropertyNode kPropertyNodes[] = {\n'
554 '// Property #Schema\n')
555 for key
, schema
in self
.property_nodes
:
557 f
.write(' { %-50s %6d },\n' % (key
, schema
))
560 if self
.properties_nodes
:
561 f
.write('const internal::PropertiesNode kProperties[] = {\n'
562 '// Begin End PatternEnd Additional Properties\n')
563 for node
in self
.properties_nodes
:
564 f
.write(' { %5d, %5d, %10d, %5d }, // %s\n' % node
)
567 if self
.restriction_nodes
:
568 f
.write('const internal::RestrictionNode kRestrictionNodes[] = {\n')
569 f
.write('// FIRST, SECOND\n')
570 for first
, second
in self
.restriction_nodes
:
571 f
.write(' {{ %-8s %4s}},\n' % (first
+ ',', second
))
575 f
.write('const int kIntegerEnumerations[] = {\n')
576 for possible_values
in self
.int_enums
:
577 f
.write(' %d,\n' % possible_values
)
580 if self
.string_enums
:
581 f
.write('const char* kStringEnumerations[] = {\n')
582 for possible_values
in self
.string_enums
:
583 f
.write(' %s,\n' % self
.GetString(possible_values
))
586 f
.write('const internal::SchemaData kChromeSchemaData = {\n'
588 f
.write(' kPropertyNodes,\n' if self
.property_nodes
else ' NULL,\n')
589 f
.write(' kProperties,\n' if self
.properties_nodes
else ' NULL,\n')
590 f
.write(' kRestrictionNodes,\n' if self
.restriction_nodes
else ' NULL,\n')
591 f
.write(' kIntegerEnumerations,\n' if self
.int_enums
else ' NULL,\n')
592 f
.write(' kStringEnumerations,\n' if self
.string_enums
else ' NULL,\n')
595 def GetByID(self
, id_str
):
596 if not isinstance(id_str
, types
.StringTypes
):
598 if not self
.id_map
.has_key(id_str
):
599 raise RuntimeError('Invalid $ref: ' + id_str
)
600 return self
.id_map
[id_str
]
602 def ResolveID(self
, index
, params
):
603 return params
[:index
] + (self
.GetByID(params
[index
]),) + params
[index
+ 1:]
605 def ResolveReferences(self
):
606 """Resolve reference mapping, required to be called after Generate()
608 After calling Generate(), the type of indices used in schema structures
609 might be either int or string. An int type suggests that it's a resolved
610 index, but for string type it's unresolved. Resolving a reference is as
611 simple as looking up for corresponding ID in self.id_map, and replace the
612 old index with the mapped index.
614 self
.schema_nodes
= map(partial(self
.ResolveID
, 1), self
.schema_nodes
)
615 self
.property_nodes
= map(partial(self
.ResolveID
, 1), self
.property_nodes
)
616 self
.properties_nodes
= map(partial(self
.ResolveID
, 3),
617 self
.properties_nodes
)
619 def _WritePolicyConstantSource(policies
, os
, f
):
620 f
.write('#include "policy/policy_constants.h"\n'
622 '#include <algorithm>\n'
623 '#include <climits>\n'
625 '#include "base/logging.h"\n'
626 '#include "components/policy/core/common/schema_internal.h"\n'
628 'namespace policy {\n'
633 # Generate the Chrome schema.
639 for policy
in policies
:
640 shared_strings
[policy
.name
] = "key::k%s" % policy
.name
641 if policy
.is_supported
:
642 chrome_schema
['properties'][policy
.name
] = policy
.schema
644 # Note: this list must be kept in sync with the known property list of the
645 # Chrome schema, so that binary seaching in the PropertyNode array gets the
646 # right index on this array as well. See the implementation of
647 # GetChromePolicyDetails() below.
648 f
.write('const PolicyDetails kChromePolicyDetails[] = {\n'
649 '// is_deprecated is_device_policy id max_external_data_size\n')
650 for policy
in policies
:
651 if policy
.is_supported
:
652 f
.write(' { %-14s %-16s %3s, %24s },\n' % (
653 'true,' if policy
.is_deprecated
else 'false,',
654 'true,' if policy
.is_device_only
else 'false,',
659 schema_generator
= SchemaNodesGenerator(shared_strings
)
660 schema_generator
.GenerateAndCollectID(chrome_schema
, 'root node')
661 schema_generator
.ResolveReferences()
662 schema_generator
.Write(f
)
664 f
.write('bool CompareKeys(const internal::PropertyNode& node,\n'
665 ' const std::string& key) {\n'
666 ' return node.key < key;\n'
669 f
.write('} // namespace\n\n')
672 f
.write('#if defined(GOOGLE_CHROME_BUILD)\n'
673 'const wchar_t kRegistryChromePolicyKey[] = '
674 'L"' + CHROME_POLICY_KEY
+ '";\n'
676 'const wchar_t kRegistryChromePolicyKey[] = '
677 'L"' + CHROMIUM_POLICY_KEY
+ '";\n'
680 f
.write('const internal::SchemaData* GetChromeSchemaData() {\n'
681 ' return &kChromeSchemaData;\n'
684 f
.write('#if defined (OS_CHROMEOS)\n'
685 'void SetEnterpriseUsersDefaults(PolicyMap* policy_map) {\n')
687 for policy
in policies
:
688 if policy
.has_enterprise_default
:
689 if policy
.policy_type
== 'TYPE_BOOLEAN':
690 creation_expression
= 'new base::FundamentalValue(%s)' %\
691 ('true' if policy
.enterprise_default
else 'false')
692 elif policy
.policy_type
== 'TYPE_INTEGER':
693 creation_expression
= 'new base::FundamentalValue(%s)' %\
694 policy
.enterprise_default
695 elif policy
.policy_type
== 'TYPE_STRING':
696 creation_expression
= 'new base::StringValue("%s")' %\
697 policy
.enterprise_default
699 raise RuntimeError('Type %s of policy %s is not supported at '
700 'enterprise defaults' % (policy
.policy_type
,
702 f
.write(' if (!policy_map->Get(key::k%s)) {\n'
703 ' policy_map->Set(key::k%s,\n'
704 ' POLICY_LEVEL_MANDATORY,\n'
705 ' POLICY_SCOPE_USER,\n'
708 ' }\n' % (policy
.name
, policy
.name
, creation_expression
))
713 f
.write('const PolicyDetails* GetChromePolicyDetails('
714 'const std::string& policy) {\n'
715 ' // First index in kPropertyNodes of the Chrome policies.\n'
716 ' static const int begin_index = %s;\n'
717 ' // One-past-the-end of the Chrome policies in kPropertyNodes.\n'
718 ' static const int end_index = %s;\n' %
719 (schema_generator
.root_properties_begin
,
720 schema_generator
.root_properties_end
))
721 f
.write(' const internal::PropertyNode* begin =\n'
722 ' kPropertyNodes + begin_index;\n'
723 ' const internal::PropertyNode* end = kPropertyNodes + end_index;\n'
724 ' const internal::PropertyNode* it =\n'
725 ' std::lower_bound(begin, end, policy, CompareKeys);\n'
726 ' if (it == end || it->key != policy)\n'
728 ' // This relies on kPropertyNodes from begin_index to end_index\n'
729 ' // having exactly the same policies (and in the same order) as\n'
730 ' // kChromePolicyDetails, so that binary searching on the first\n'
731 ' // gets the same results as a binary search on the second would.\n'
732 ' // However, kPropertyNodes has the policy names and\n'
733 ' // kChromePolicyDetails doesn\'t, so we obtain the index into\n'
734 ' // the second array by searching the first to avoid duplicating\n'
735 ' // the policy name pointers.\n'
736 ' // Offsetting |it| from |begin| here obtains the index we\'re\n'
738 ' size_t index = it - begin;\n'
739 ' CHECK_LT(index, arraysize(kChromePolicyDetails));\n'
740 ' return kChromePolicyDetails + index;\n'
743 f
.write('namespace key {\n\n')
744 for policy
in policies
:
745 # TODO(joaodasilva): Include only supported policies in
746 # configuration_policy_handler.cc and configuration_policy_handler_list.cc
747 # so that these names can be conditional on 'policy.is_supported'.
748 # http://crbug.com/223616
749 f
.write('const char k{name}[] = "{name}";\n'.format(name
=policy
.name
))
750 f
.write('\n} // namespace key\n\n'
751 '} // namespace policy\n')
754 #------------------ policy protobufs --------------------------------#
756 CHROME_SETTINGS_PROTO_HEAD
= '''
759 option optimize_for = LITE_RUNTIME;
761 package enterprise_management;
763 // For StringList and PolicyOptions.
764 import "cloud_policy.proto";
769 CLOUD_POLICY_PROTO_HEAD
= '''
772 option optimize_for = LITE_RUNTIME;
774 package enterprise_management;
777 repeated string entries = 1;
780 message PolicyOptions {
782 // The given settings are applied regardless of user choice.
784 // The user may choose to override the given settings.
786 // No policy value is present and the policy should be ignored.
789 optional PolicyMode mode = 1 [default = MANDATORY];
792 message BooleanPolicyProto {
793 optional PolicyOptions policy_options = 1;
794 optional bool value = 2;
797 message IntegerPolicyProto {
798 optional PolicyOptions policy_options = 1;
799 optional int64 value = 2;
802 message StringPolicyProto {
803 optional PolicyOptions policy_options = 1;
804 optional string value = 2;
807 message StringListPolicyProto {
808 optional PolicyOptions policy_options = 1;
809 optional StringList value = 2;
815 # Field IDs [1..RESERVED_IDS] will not be used in the wrapping protobuf.
819 def _WritePolicyProto(f
, policy
, fields
):
820 _OutputComment(f
, policy
.caption
+ '\n\n' + policy
.desc
)
821 if policy
.items
is not None:
822 _OutputComment(f
, '\nValid values:')
823 for item
in policy
.items
:
824 _OutputComment(f
, ' %s: %s' % (str(item
.value
), item
.caption
))
825 if policy
.policy_type
== 'TYPE_DICTIONARY':
826 _OutputComment(f
, '\nValue schema:\n%s' %
827 json
.dumps(policy
.schema
, sort_keys
=True, indent
=4,
828 separators
=(',', ': ')))
829 _OutputComment(f
, '\nSupported on: %s' % ', '.join(policy
.platforms
))
830 if policy
.can_be_recommended
and not policy
.can_be_mandatory
:
831 _OutputComment(f
, '\nNote: this policy must have a RECOMMENDED ' +\
832 'PolicyMode set in PolicyOptions.')
833 f
.write('message %sProto {\n' % policy
.name
)
834 f
.write(' optional PolicyOptions policy_options = 1;\n')
835 f
.write(' optional %s %s = 2;\n' % (policy
.protobuf_type
, policy
.name
))
837 fields
+= [ ' optional %sProto %s = %s;\n' %
838 (policy
.name
, policy
.name
, policy
.id + RESERVED_IDS
) ]
841 def _WriteChromeSettingsProtobuf(policies
, os
, f
):
842 f
.write(CHROME_SETTINGS_PROTO_HEAD
)
845 f
.write('// PBs for individual settings.\n\n')
846 for policy
in policies
:
847 # Note: this protobuf also gets the unsupported policies, since it's an
848 # exaustive list of all the supported user policies on any platform.
849 if not policy
.is_device_only
:
850 _WritePolicyProto(f
, policy
, fields
)
852 f
.write('// --------------------------------------------------\n'
853 '// Big wrapper PB containing the above groups.\n\n'
854 'message ChromeSettingsProto {\n')
855 f
.write(''.join(fields
))
859 def _WriteCloudPolicyProtobuf(policies
, os
, f
):
860 f
.write(CLOUD_POLICY_PROTO_HEAD
)
861 f
.write('message CloudPolicySettings {\n')
862 for policy
in policies
:
863 if policy
.is_supported
and not policy
.is_device_only
:
864 f
.write(' optional %sPolicyProto %s = %s;\n' %
865 (policy
.policy_protobuf_type
, policy
.name
,
866 policy
.id + RESERVED_IDS
))
870 #------------------ protobuf decoder -------------------------------#
876 #include "base/basictypes.h"
877 #include "base/callback.h"
878 #include "base/json/json_reader.h"
879 #include "base/logging.h"
880 #include "base/memory/scoped_ptr.h"
881 #include "base/memory/weak_ptr.h"
882 #include "base/values.h"
883 #include "components/policy/core/common/cloud/cloud_external_data_manager.h"
884 #include "components/policy/core/common/external_data_fetcher.h"
885 #include "components/policy/core/common/policy_map.h"
886 #include "policy/policy_constants.h"
887 #include "policy/proto/cloud_policy.pb.h"
889 using google::protobuf::RepeatedPtrField;
893 namespace em = enterprise_management;
895 base::Value* DecodeIntegerValue(google::protobuf::int64 value) {
896 if (value < std::numeric_limits<int>::min() ||
897 value > std::numeric_limits<int>::max()) {
898 LOG(WARNING) << "Integer value " << value
899 << " out of numeric limits, ignoring.";
903 return new base::FundamentalValue(static_cast<int>(value));
906 base::ListValue* DecodeStringList(const em::StringList& string_list) {
907 base::ListValue* list_value = new base::ListValue;
908 RepeatedPtrField<std::string>::const_iterator entry;
909 for (entry = string_list.entries().begin();
910 entry != string_list.entries().end(); ++entry) {
911 list_value->AppendString(*entry);
916 base::Value* DecodeJson(const std::string& json) {
917 scoped_ptr<base::Value> root(
918 base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS));
921 LOG(WARNING) << "Invalid JSON string, ignoring: " << json;
923 // Accept any Value type that parsed as JSON, and leave it to the handler to
924 // convert and check the concrete type.
925 return root.release();
928 void DecodePolicy(const em::CloudPolicySettings& policy,
929 base::WeakPtr<CloudExternalDataManager> external_data_manager,
936 } // namespace policy
940 def _CreateValue(type, arg
):
941 if type == 'TYPE_BOOLEAN':
942 return 'new base::FundamentalValue(%s)' % arg
943 elif type == 'TYPE_INTEGER':
944 return 'DecodeIntegerValue(%s)' % arg
945 elif type == 'TYPE_STRING':
946 return 'new base::StringValue(%s)' % arg
947 elif type == 'TYPE_LIST':
948 return 'DecodeStringList(%s)' % arg
949 elif type == 'TYPE_DICTIONARY' or type == 'TYPE_EXTERNAL':
950 return 'DecodeJson(%s)' % arg
952 raise NotImplementedError('Unknown type %s' % type)
955 def _CreateExternalDataFetcher(type, name
):
956 if type == 'TYPE_EXTERNAL':
957 return 'new ExternalDataFetcher(external_data_manager, key::k%s)' % name
961 def _WritePolicyCode(f
, policy
):
962 membername
= policy
.name
.lower()
963 proto_type
= '%sPolicyProto' % policy
.policy_protobuf_type
964 f
.write(' if (policy.has_%s()) {\n' % membername
)
965 f
.write(' const em::%s& policy_proto = policy.%s();\n' %
966 (proto_type
, membername
))
967 f
.write(' if (policy_proto.has_value()) {\n')
968 f
.write(' PolicyLevel level = POLICY_LEVEL_MANDATORY;\n'
969 ' bool do_set = true;\n'
970 ' if (policy_proto.has_policy_options()) {\n'
972 ' switch(policy_proto.policy_options().mode()) {\n'
973 ' case em::PolicyOptions::MANDATORY:\n'
975 ' level = POLICY_LEVEL_MANDATORY;\n'
977 ' case em::PolicyOptions::RECOMMENDED:\n'
979 ' level = POLICY_LEVEL_RECOMMENDED;\n'
981 ' case em::PolicyOptions::UNSET:\n'
986 f
.write(' base::Value* value = %s;\n' %
987 (_CreateValue(policy
.policy_type
, 'policy_proto.value()')))
988 # TODO(bartfab): |value| == NULL indicates that the policy value could not be
989 # parsed successfully. Surface such errors in the UI.
990 f
.write(' if (value) {\n')
991 f
.write(' ExternalDataFetcher* external_data_fetcher = %s;\n' %
992 _CreateExternalDataFetcher(policy
.policy_type
, policy
.name
))
993 f
.write(' map->Set(key::k%s, level, POLICY_SCOPE_USER,\n' %
995 f
.write(' value, external_data_fetcher);\n'
1002 def _WriteCloudPolicyDecoder(policies
, os
, f
):
1004 for policy
in policies
:
1005 if policy
.is_supported
and not policy
.is_device_only
:
1006 _WritePolicyCode(f
, policy
)
1010 def _EscapeResourceString(raw_resource
):
1011 if type(raw_resource
) == int:
1013 return xml_escape(raw_resource
)\
1014 .replace('\\', '\\\\')\
1015 .replace('\"','\\\"')\
1016 .replace('\'','\\\'')
1018 def _WriteAppRestrictions(policies
, os
, f
):
1020 def WriteRestrictionCommon(key
):
1021 f
.write(' <restriction\n'
1022 ' android:key="%s"\n' % key
)
1023 f
.write(' android:title="@string/%sTitle"\n' % key
)
1024 f
.write(' android:description="@string/%sDesc"\n' % key
)
1026 def WriteItemsDefinition(key
):
1027 f
.write(' android:entries="@array/%sEntries"\n' % key
)
1028 f
.write(' android:entryValues="@array/%sValues"\n' % key
)
1030 def WriteAppRestriction(policy
):
1031 policy_name
= policy
.name
1032 WriteRestrictionCommon(policy_name
)
1034 if policy
.has_restriction_resources
:
1035 WriteItemsDefinition(policy_name
)
1037 f
.write(' android:restrictionType="%s"/>' % policy
.restriction_type
)
1040 # _WriteAppRestrictions body
1041 f
.write('<restrictions xmlns:android="'
1042 'http://schemas.android.com/apk/res/android">\n\n')
1043 for policy
in policies
:
1044 if policy
.is_supported
and policy
.restriction_type
!= 'invalid':
1045 WriteAppRestriction(policy
)
1046 f
.write('</restrictions>')
1049 def _WriteResourcesForPolicies(policies
, os
, f
):
1051 # TODO(knn): Update this to support i18n.
1052 def WriteString(key
, value
):
1053 f
.write(' <string name="%s">%s</string>\n'
1054 % (key
, _EscapeResourceString(value
)))
1056 def WriteItems(key
, items
):
1058 f
.write(' <string-array name="%sEntries">\n' % key
)
1060 f
.write(' <item>%s</item>\n' %
1061 _EscapeResourceString(item
.caption
))
1062 f
.write(' </string-array>\n')
1063 f
.write(' <string-array name="%sValues">\n' % key
)
1065 f
.write(' <item>%s</item>\n' % _EscapeResourceString(item
.value
))
1066 f
.write(' </string-array>\n')
1068 def WriteResourceForPolicy(policy
):
1069 policy_name
= policy
.name
1070 WriteString(policy_name
+ 'Title', policy
.caption
)
1072 # Get the first line of the policy description.
1073 description
= policy
.desc
.split('\n', 1)[0]
1074 WriteString(policy_name
+ 'Desc', description
)
1076 if policy
.has_restriction_resources
:
1077 WriteItems(policy_name
, policy
.items
)
1079 # _WriteResourcesForPolicies body
1080 f
.write('<resources>\n\n')
1081 for policy
in policies
:
1082 if policy
.is_supported
and policy
.restriction_type
!= 'invalid':
1083 WriteResourceForPolicy(policy
)
1084 f
.write('</resources>')
1087 if __name__
== '__main__':