| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 '''python %prog [options] platform chromium_os_flag template | 6 '''python %prog [options] platform chromium_os_flag template |
| 7 | 7 |
| 8 platform specifies which platform source is being generated for | 8 platform specifies which platform source is being generated for |
| 9 and can be one of (win, mac, linux) | 9 and can be one of (win, mac, linux) |
| 10 chromium_os_flag should be 1 if this is a Chromium OS build | 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.''' | 11 template is the path to a .json policy template file.''' |
| 12 | 12 |
| 13 from __future__ import with_statement | 13 from __future__ import with_statement |
| 14 from functools import partial | 14 from functools import partial |
| 15 import json | 15 import json |
| 16 from optparse import OptionParser | 16 from optparse import OptionParser |
| 17 import re | 17 import re |
| 18 import sys | 18 import sys |
| 19 import textwrap | 19 import textwrap |
| 20 import types | 20 import types |
| 21 from xml.sax.saxutils import escape as xml_escape |
| 21 | 22 |
| 22 | 23 |
| 23 CHROME_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Google\\\\Chrome' | 24 CHROME_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Google\\\\Chrome' |
| 24 CHROMIUM_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Chromium' | 25 CHROMIUM_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Chromium' |
| 25 | 26 |
| 26 | 27 |
| 27 class PolicyDetails: | 28 class PolicyDetails: |
| 28 """Parses a policy template and caches all its details.""" | 29 """Parses a policy template and caches all its details.""" |
| 29 | 30 |
| 30 # Maps policy types to a tuple with 3 other types: | 31 # Maps policy types to a tuple with 5 other types: |
| 31 # - the equivalent base::Value::Type or 'TYPE_EXTERNAL' if the policy | 32 # - the equivalent base::Value::Type or 'TYPE_EXTERNAL' if the policy |
| 32 # references external data | 33 # references external data |
| 33 # - the equivalent Protobuf field type | 34 # - the equivalent Protobuf field type |
| 34 # - the name of one of the protobufs for shared policy types | 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 |
| 35 # TODO(joaodasilva): refactor the 'dict' type into a more generic 'json' type | 38 # 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. | 39 # that can also be used to represent lists of other JSON objects. |
| 37 TYPE_MAP = { | 40 TYPE_MAP = { |
| 38 'dict': ('TYPE_DICTIONARY', 'string', 'String'), | 41 'dict': ('TYPE_DICTIONARY', 'string', 'String', |
| 39 'external': ('TYPE_EXTERNAL', 'string', 'String'), | 42 'string', False), |
| 40 'int': ('TYPE_INTEGER', 'int64', 'Integer'), | 43 'external': ('TYPE_EXTERNAL', 'string', 'String', |
| 41 'int-enum': ('TYPE_INTEGER', 'int64', 'Integer'), | 44 'invalid', False), |
| 42 'list': ('TYPE_LIST', 'StringList', 'StringList'), | 45 'int': ('TYPE_INTEGER', 'int64', 'Integer', |
| 43 'main': ('TYPE_BOOLEAN', 'bool', 'Boolean'), | 46 'integer', False), |
| 44 'string': ('TYPE_STRING', 'string', 'String'), | 47 'int-enum': ('TYPE_INTEGER', 'int64', 'Integer', |
| 45 'string-enum': ('TYPE_STRING', 'string', 'String'), | 48 'choice', True), |
| 46 'string-enum-list': ('TYPE_LIST', 'StringList', 'StringList'), | 49 'list': ('TYPE_LIST', 'StringList', 'StringList', |
| 50 'string', False), |
| 51 'main': ('TYPE_BOOLEAN', 'bool', 'Boolean', |
| 52 'bool', False), |
| 53 'string': ('TYPE_STRING', 'string', 'String', |
| 54 'string', False), |
| 55 'string-enum': ('TYPE_STRING', 'string', 'String', |
| 56 'choice', True), |
| 57 'string-enum-list': ('TYPE_LIST', 'StringList', 'StringList', |
| 58 'multi-select', True), |
| 47 } | 59 } |
| 48 | 60 |
| 49 class EnumItem: | 61 class EnumItem: |
| 50 def __init__(self, item): | 62 def __init__(self, item): |
| 51 self.caption = PolicyDetails._RemovePlaceholders(item['caption']) | 63 self.caption = PolicyDetails._RemovePlaceholders(item['caption']) |
| 52 self.value = item['value'] | 64 self.value = item['value'] |
| 53 | 65 |
| 54 def __init__(self, policy, os, is_chromium_os): | 66 def __init__(self, policy, os, is_chromium_os): |
| 55 self.id = policy['id'] | 67 self.id = policy['id'] |
| 56 self.name = policy['name'] | 68 self.name = policy['name'] |
| (...skipping 21 matching lines...) Expand all Loading... |
| 78 self.platforms.append(platform_sub) | 90 self.platforms.append(platform_sub) |
| 79 else: | 91 else: |
| 80 self.platforms.append(platform) | 92 self.platforms.append(platform) |
| 81 | 93 |
| 82 self.platforms.sort() | 94 self.platforms.sort() |
| 83 self.is_supported = expected_platform in self.platforms | 95 self.is_supported = expected_platform in self.platforms |
| 84 | 96 |
| 85 if not PolicyDetails.TYPE_MAP.has_key(policy['type']): | 97 if not PolicyDetails.TYPE_MAP.has_key(policy['type']): |
| 86 raise NotImplementedError('Unknown policy type for %s: %s' % | 98 raise NotImplementedError('Unknown policy type for %s: %s' % |
| 87 (policy['name'], policy['type'])) | 99 (policy['name'], policy['type'])) |
| 88 self.policy_type, self.protobuf_type, self.policy_protobuf_type = \ | 100 self.policy_type, self.protobuf_type, self.policy_protobuf_type, \ |
| 89 PolicyDetails.TYPE_MAP[policy['type']] | 101 self.restriction_type, self.has_restriction_resources = \ |
| 102 PolicyDetails.TYPE_MAP[policy['type']] |
| 90 self.schema = policy['schema'] | 103 self.schema = policy['schema'] |
| 91 | 104 |
| 92 self.desc = '\n'.join( | 105 self.desc = '\n'.join( |
| 93 map(str.strip, | 106 map(str.strip, |
| 94 PolicyDetails._RemovePlaceholders(policy['desc']).splitlines())) | 107 PolicyDetails._RemovePlaceholders(policy['desc']).splitlines())) |
| 95 self.caption = PolicyDetails._RemovePlaceholders(policy['caption']) | 108 self.caption = PolicyDetails._RemovePlaceholders(policy['caption']) |
| 96 self.max_size = policy.get('max_size', 0) | 109 self.max_size = policy.get('max_size', 0) |
| 97 | 110 |
| 98 items = policy.get('items') | 111 items = policy.get('items') |
| 99 if items is None: | 112 if items is None: |
| (...skipping 29 matching lines...) Expand all Loading... |
| 129 help='generate cloud policy protobuf file', | 142 help='generate cloud policy protobuf file', |
| 130 metavar='FILE') | 143 metavar='FILE') |
| 131 parser.add_option('--csp', '--chrome-settings-protobuf', | 144 parser.add_option('--csp', '--chrome-settings-protobuf', |
| 132 dest='chrome_settings_proto_path', | 145 dest='chrome_settings_proto_path', |
| 133 help='generate chrome settings protobuf file', | 146 help='generate chrome settings protobuf file', |
| 134 metavar='FILE') | 147 metavar='FILE') |
| 135 parser.add_option('--cpd', '--cloud-policy-decoder', | 148 parser.add_option('--cpd', '--cloud-policy-decoder', |
| 136 dest='cloud_policy_decoder_path', | 149 dest='cloud_policy_decoder_path', |
| 137 help='generate C++ code decoding the cloud policy protobuf', | 150 help='generate C++ code decoding the cloud policy protobuf', |
| 138 metavar='FILE') | 151 metavar='FILE') |
| 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', |
| 156 metavar='FILE') |
| 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 ' |
| 161 'parameter', |
| 162 metavar='FILE') |
| 139 | 163 |
| 140 (opts, args) = parser.parse_args() | 164 (opts, args) = parser.parse_args() |
| 141 | 165 |
| 142 if len(args) != 3: | 166 if len(args) != 3: |
| 143 print 'exactly platform, chromium_os flag and input file must be specified.' | 167 print 'exactly platform, chromium_os flag and input file must be specified.' |
| 144 parser.print_help() | 168 parser.print_help() |
| 145 return 2 | 169 return 2 |
| 146 | 170 |
| 147 os = args[0] | 171 os = args[0] |
| 148 is_chromium_os = args[1] == '1' | 172 is_chromium_os = args[1] == '1' |
| 149 template_file_name = args[2] | 173 template_file_name = args[2] |
| 150 | 174 |
| 151 template_file_contents = _LoadJSONFile(template_file_name) | 175 template_file_contents = _LoadJSONFile(template_file_name) |
| 152 policy_details = [ PolicyDetails(policy, os, is_chromium_os) | 176 policy_details = [ PolicyDetails(policy, os, is_chromium_os) |
| 153 for policy in _Flatten(template_file_contents) ] | 177 for policy in _Flatten(template_file_contents) ] |
| 154 sorted_policy_details = sorted(policy_details, key=lambda policy: policy.name) | 178 sorted_policy_details = sorted(policy_details, key=lambda policy: policy.name) |
| 155 | 179 |
| 156 def GenerateFile(path, writer, sorted=False): | 180 def GenerateFile(path, writer, sorted=False, xml=False): |
| 157 if path: | 181 if path: |
| 158 with open(path, 'w') as f: | 182 with open(path, 'w') as f: |
| 159 _OutputGeneratedWarningHeader(f, template_file_name) | 183 _OutputGeneratedWarningHeader(f, template_file_name, xml) |
| 160 writer(sorted and sorted_policy_details or policy_details, os, f) | 184 writer(sorted and sorted_policy_details or policy_details, os, f) |
| 161 | 185 |
| 162 GenerateFile(opts.header_path, _WritePolicyConstantHeader, sorted=True) | 186 GenerateFile(opts.header_path, _WritePolicyConstantHeader, sorted=True) |
| 163 GenerateFile(opts.source_path, _WritePolicyConstantSource, sorted=True) | 187 GenerateFile(opts.source_path, _WritePolicyConstantSource, sorted=True) |
| 164 GenerateFile(opts.cloud_policy_proto_path, _WriteCloudPolicyProtobuf) | 188 GenerateFile(opts.cloud_policy_proto_path, _WriteCloudPolicyProtobuf) |
| 165 GenerateFile(opts.chrome_settings_proto_path, _WriteChromeSettingsProtobuf) | 189 GenerateFile(opts.chrome_settings_proto_path, _WriteChromeSettingsProtobuf) |
| 166 GenerateFile(opts.cloud_policy_decoder_path, _WriteCloudPolicyDecoder) | 190 GenerateFile(opts.cloud_policy_decoder_path, _WriteCloudPolicyDecoder) |
| 167 | 191 |
| 192 if os == 'android': |
| 193 GenerateFile(opts.app_restrictions_path, _WriteAppRestrictions, xml=True) |
| 194 GenerateFile(opts.app_resources_path, _WriteResourcesForPolicies, xml=True) |
| 195 |
| 168 return 0 | 196 return 0 |
| 169 | 197 |
| 170 | 198 |
| 171 #------------------ shared helpers ---------------------------------# | 199 #------------------ shared helpers ---------------------------------# |
| 172 | 200 |
| 173 def _OutputGeneratedWarningHeader(f, template_file_path): | 201 def _OutputGeneratedWarningHeader(f, template_file_path, xml_style): |
| 174 f.write('//\n' | 202 left_margin = '//' |
| 175 '// DO NOT MODIFY THIS FILE DIRECTLY!\n' | 203 if xml_style: |
| 176 '// IT IS GENERATED BY generate_policy_source.py\n' | 204 left_margin = ' ' |
| 177 '// FROM ' + template_file_path + '\n' | 205 f.write('<?xml version="1.0" encoding="utf-8"?>\n' |
| 178 '//\n\n') | 206 '<!--\n') |
| 207 else: |
| 208 f.write('//\n') |
| 209 |
| 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') |
| 213 |
| 214 if xml_style: |
| 215 f.write('-->\n\n') |
| 216 else: |
| 217 f.write(left_margin + '\n\n') |
| 179 | 218 |
| 180 | 219 |
| 181 COMMENT_WRAPPER = textwrap.TextWrapper() | 220 COMMENT_WRAPPER = textwrap.TextWrapper() |
| 182 COMMENT_WRAPPER.width = 80 | 221 COMMENT_WRAPPER.width = 80 |
| 183 COMMENT_WRAPPER.initial_indent = '// ' | 222 COMMENT_WRAPPER.initial_indent = '// ' |
| 184 COMMENT_WRAPPER.subsequent_indent = '// ' | 223 COMMENT_WRAPPER.subsequent_indent = '// ' |
| 185 COMMENT_WRAPPER.replace_whitespace = False | 224 COMMENT_WRAPPER.replace_whitespace = False |
| 186 | 225 |
| 187 | 226 |
| 188 # Writes a comment, each line prefixed by // and wrapped to 80 spaces. | 227 # Writes a comment, each line prefixed by // and wrapped to 80 spaces. |
| (...skipping 365 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 554 f.write('};\n\n') | 593 f.write('};\n\n') |
| 555 | 594 |
| 556 def GetByID(self, id_str): | 595 def GetByID(self, id_str): |
| 557 if not isinstance(id_str, types.StringTypes): | 596 if not isinstance(id_str, types.StringTypes): |
| 558 return id_str | 597 return id_str |
| 559 if not self.id_map.has_key(id_str): | 598 if not self.id_map.has_key(id_str): |
| 560 raise RuntimeError('Invalid $ref: ' + id_str) | 599 raise RuntimeError('Invalid $ref: ' + id_str) |
| 561 return self.id_map[id_str] | 600 return self.id_map[id_str] |
| 562 | 601 |
| 563 def ResolveID(self, index, params): | 602 def ResolveID(self, index, params): |
| 564 return params[:index] + (self.GetByID(params[index]),) + params[index+1:] | 603 return params[:index] + (self.GetByID(params[index]),) + params[index + 1:] |
| 565 | 604 |
| 566 def ResolveReferences(self): | 605 def ResolveReferences(self): |
| 567 """Resolve reference mapping, required to be called after Generate() | 606 """Resolve reference mapping, required to be called after Generate() |
| 568 | 607 |
| 569 After calling Generate(), the type of indices used in schema structures | 608 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 | 609 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 | 610 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 | 611 simple as looking up for corresponding ID in self.id_map, and replace the |
| 573 old index with the mapped index. | 612 old index with the mapped index. |
| 574 """ | 613 """ |
| (...skipping 386 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 961 | 1000 |
| 962 | 1001 |
| 963 def _WriteCloudPolicyDecoder(policies, os, f): | 1002 def _WriteCloudPolicyDecoder(policies, os, f): |
| 964 f.write(CPP_HEAD) | 1003 f.write(CPP_HEAD) |
| 965 for policy in policies: | 1004 for policy in policies: |
| 966 if policy.is_supported and not policy.is_device_only: | 1005 if policy.is_supported and not policy.is_device_only: |
| 967 _WritePolicyCode(f, policy) | 1006 _WritePolicyCode(f, policy) |
| 968 f.write(CPP_FOOT) | 1007 f.write(CPP_FOOT) |
| 969 | 1008 |
| 970 | 1009 |
| 1010 def _EscapeResourceString(raw_resource): |
| 1011 if type(raw_resource) == int: |
| 1012 return raw_resource |
| 1013 return xml_escape(raw_resource)\ |
| 1014 .replace('\\', '\\\\')\ |
| 1015 .replace('\"','\\\"')\ |
| 1016 .replace('\'','\\\'') |
| 1017 |
| 1018 def _WriteAppRestrictions(policies, os, f): |
| 1019 |
| 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) |
| 1025 |
| 1026 def WriteItemsDefinition(key): |
| 1027 f.write(' android:entries="@array/%sEntries"\n' % key) |
| 1028 f.write(' android:entryValues="@array/%sValues"\n' % key) |
| 1029 |
| 1030 def WriteAppRestriction(policy): |
| 1031 policy_name = policy.name |
| 1032 WriteRestrictionCommon(policy_name) |
| 1033 |
| 1034 if policy.has_restriction_resources: |
| 1035 WriteItemsDefinition(policy_name) |
| 1036 |
| 1037 f.write(' android:restrictionType="%s"/>' % policy.restriction_type) |
| 1038 f.write('\n\n') |
| 1039 |
| 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>') |
| 1047 |
| 1048 |
| 1049 def _WriteResourcesForPolicies(policies, os, f): |
| 1050 |
| 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))) |
| 1055 |
| 1056 def WriteItems(key, items): |
| 1057 if items: |
| 1058 f.write(' <string-array name="%sEntries">\n' % key) |
| 1059 for item in items: |
| 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) |
| 1064 for item in items: |
| 1065 f.write(' <item>%s</item>\n' % _EscapeResourceString(item.value)) |
| 1066 f.write(' </string-array>\n') |
| 1067 |
| 1068 def WriteResourceForPolicy(policy): |
| 1069 policy_name = policy.name |
| 1070 WriteString(policy_name + 'Title', policy.caption) |
| 1071 |
| 1072 # Get the first line of the policy description. |
| 1073 description = policy.desc.split('\n', 1)[0] |
| 1074 WriteString(policy_name + 'Desc', description) |
| 1075 |
| 1076 if policy.has_restriction_resources: |
| 1077 WriteItems(policy_name, policy.items) |
| 1078 |
| 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>') |
| 1085 |
| 1086 |
| 971 if __name__ == '__main__': | 1087 if __name__ == '__main__': |
| 972 sys.exit(main()) | 1088 sys.exit(main()) |
| OLD | NEW |