#!/usr/bin/env python # Copyright (c) 2012 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. '''python %prog [options] platform chromium_os_flag template platform specifies which platform source is being generated for and can be one of (win, mac, linux) chromium_os_flag should be 1 if this is a Chromium OS build template is the path to a .json policy template file.''' from __future__ import with_statement from collections import namedtuple from optparse import OptionParser import re import sys import textwrap CHROME_MANDATORY_SUBKEY = 'SOFTWARE\\\\Policies\\\\Google\\\\Chrome' CHROME_RECOMMENDED_SUBKEY = CHROME_MANDATORY_SUBKEY + '\\\\Recommended' CHROMIUM_MANDATORY_SUBKEY = 'SOFTWARE\\\\Policies\\\\Chromium' CHROMIUM_RECOMMENDED_SUBKEY = CHROMIUM_MANDATORY_SUBKEY + '\\\\Recommended' TYPE_MAP = { 'dict': 'TYPE_DICTIONARY', 'int': 'TYPE_INTEGER', 'int-enum': 'TYPE_INTEGER', 'list': 'TYPE_LIST', 'main': 'TYPE_BOOLEAN', 'string': 'TYPE_STRING', 'string-enum': 'TYPE_STRING', } PolicyDetails = namedtuple( 'PolicyDetails', 'id name vtype platforms is_deprecated is_device_policy') def main(): parser = OptionParser(usage=__doc__) parser.add_option('--pch', '--policy-constants-header', dest='header_path', help='generate header file of policy constants', metavar='FILE') parser.add_option('--pcc', '--policy-constants-source', dest='source_path', help='generate source file of policy constants', metavar='FILE') parser.add_option('--cpp', '--cloud-policy-protobuf', dest='cloud_policy_proto_path', help='generate cloud policy protobuf file', metavar='FILE') parser.add_option('--csp', '--chrome-settings-protobuf', dest='chrome_settings_proto_path', help='generate chrome settings protobuf file', metavar='FILE') parser.add_option('--cpd', '--cloud-policy-decoder', dest='cloud_policy_decoder_path', help='generate C++ code decoding the cloud policy protobuf', metavar='FILE') (opts, args) = parser.parse_args() if len(args) != 3: print 'exactly platform, chromium_os flag and input file must be specified.' parser.print_help() return 2 template_file_contents = _LoadJSONFile(args[2]) if opts.header_path is not None: _WritePolicyConstantHeader(template_file_contents, args, opts) if opts.source_path is not None: _WritePolicyConstantSource(template_file_contents, args, opts) if opts.cloud_policy_proto_path is not None: _WriteCloudPolicyProtobuf( template_file_contents, args, opts.cloud_policy_proto_path) if opts.chrome_settings_proto_path is not None: _WriteChromeSettingsProtobuf( template_file_contents, args, opts.chrome_settings_proto_path) if opts.cloud_policy_decoder_path is not None: _WriteCloudPolicyDecoder( template_file_contents, args, opts.cloud_policy_decoder_path) return 0 #------------------ shared helpers ---------------------------------# def _OutputGeneratedWarningForC(f, template_file_path): f.write('//\n' '// DO NOT MODIFY THIS FILE DIRECTLY!\n' '// IT IS GENERATED BY generate_policy_source.py\n' '// FROM ' + template_file_path + '\n' '//\n\n') COMMENT_WRAPPER = textwrap.TextWrapper() COMMENT_WRAPPER.width = 80 COMMENT_WRAPPER.initial_indent = '// ' COMMENT_WRAPPER.subsequent_indent = '// ' COMMENT_WRAPPER.replace_whitespace = False # Writes a comment, each line prefixed by // and wrapped to 80 spaces. def _OutputComment(f, comment): for line in comment.splitlines(): if len(line) == 0: f.write('//') else: f.write(COMMENT_WRAPPER.fill(line)) f.write('\n') PH_PATTERN = re.compile(']*>([^<]*|[^<]*([^<]*)[^<]*)') # Simplistic grit placeholder stripper. def _RemovePlaceholders(text): result = '' pos = 0 for m in PH_PATTERN.finditer(text): result += text[pos:m.start(0)] result += m.group(2) or m.group(1) pos = m.end(0) result += text[pos:] return result; # Returns an iterator over all the policies in |template_file_contents|. def _Flatten(template_file_contents): for policy in template_file_contents['policy_definitions']: if policy['type'] == 'group': for sub_policy in policy['policies']: yield sub_policy else: yield policy # Returns a PolicyDetails named tuple with details about the given policy. def _GetPolicyDetails(policy): name = policy['name'] if not TYPE_MAP.has_key(policy['type']): print 'Unknown policy type for %s: %s' % (name, policy['type']) sys.exit(3) vtype = TYPE_MAP[policy['type']] # platforms is a list of 'chrome', 'chrome_os' and/or 'chrome_frame'. platforms = [ x.split(':')[0] for x in policy['supported_on'] ] is_deprecated = policy.get('deprecated', False) is_device_policy = policy.get('device_only', False) return PolicyDetails(name=name, vtype=vtype, platforms=platforms, is_deprecated=is_deprecated, id=policy['id'], is_device_policy=is_device_policy) def _GetPolicyList(template_file_contents): policies = [_GetPolicyDetails(p) for p in _Flatten(template_file_contents)] policies.sort(key=lambda policy: policy.name) return policies def _LoadJSONFile(json_file): with open(json_file, 'r') as f: text = f.read() return eval(text) #------------------ policy constants header ------------------------# def _WritePolicyConstantHeader(template_file_contents, args, opts): os = args[0] with open(opts.header_path, 'w') as f: _OutputGeneratedWarningForC(f, args[2]) f.write('#ifndef CHROME_COMMON_POLICY_CONSTANTS_H_\n' '#define CHROME_COMMON_POLICY_CONSTANTS_H_\n' '\n' '#include \n' '\n' '#include "base/values.h"\n' '\n' 'namespace policy {\n\n') if os == 'win': f.write('// The windows registry path where mandatory policy ' 'configuration resides.\n' 'extern const wchar_t kRegistryMandatorySubKey[];\n' '// The windows registry path where recommended policy ' 'configuration resides.\n' 'extern const wchar_t kRegistryRecommendedSubKey[];\n\n') f.write('// Lists policy types mapped to their names and expected types.\n' '// Used to initialize ConfigurationPolicyProviders.\n' 'struct PolicyDefinitionList {\n' ' struct Entry {\n' ' const char* name;\n' ' base::Value::Type value_type;\n' ' bool device_policy;\n' ' int id;\n' ' };\n' '\n' ' const Entry* begin;\n' ' const Entry* end;\n' '};\n' '\n' '// Returns true if the given policy is deprecated.\n' 'bool IsDeprecatedPolicy(const std::string& policy);\n' '\n' '// Returns the default policy definition list for Chrome.\n' 'const PolicyDefinitionList* GetChromePolicyDefinitionList();\n\n') f.write('// Key names for the policy settings.\n' 'namespace key {\n\n') for policy in _GetPolicyList(template_file_contents): f.write('extern const char k' + policy.name + '[];\n') f.write('\n} // namespace key\n\n' '} // namespace policy\n\n' '#endif // CHROME_COMMON_POLICY_CONSTANTS_H_\n') #------------------ policy constants source ------------------------# def _WritePolicyConstantSource(template_file_contents, args, opts): os = args[0] is_chromium_os = args[1] == '1' platform = None platform_wildcard = None if is_chromium_os: platform = 'chrome_os' else: platform = 'chrome.' + os.lower() platform_wildcard = 'chrome.*' with open(opts.source_path, 'w') as f: _OutputGeneratedWarningForC(f, args[2]) f.write('#include "base/basictypes.h"\n' '#include "base/logging.h"\n' '#include "policy/policy_constants.h"\n' '\n' 'namespace policy {\n\n') f.write('namespace {\n\n') f.write('const PolicyDefinitionList::Entry kEntries[] = {\n') for policy in _GetPolicyList(template_file_contents): if (platform in policy.platforms) or \ (platform_wildcard in policy.platforms): f.write(' { key::k%s, Value::%s, %s, %s },\n' % (policy.name, policy.vtype, 'true' if policy.is_device_policy else 'false', policy.id)) f.write('};\n\n') f.write('const PolicyDefinitionList kChromePolicyList = {\n' ' kEntries,\n' ' kEntries + arraysize(kEntries),\n' '};\n\n') f.write('// List of deprecated policies.\n' 'const char* kDeprecatedPolicyList[] = {\n') for policy in _GetPolicyList(template_file_contents): if policy.is_deprecated: f.write(' key::k%s,\n' % policy.name) f.write('};\n\n') f.write('} // namespace\n\n') if os == 'win': f.write('#if defined(GOOGLE_CHROME_BUILD)\n' 'const wchar_t kRegistryMandatorySubKey[] = ' 'L"' + CHROME_MANDATORY_SUBKEY + '";\n' 'const wchar_t kRegistryRecommendedSubKey[] = ' 'L"' + CHROME_RECOMMENDED_SUBKEY + '";\n' '#else\n' 'const wchar_t kRegistryMandatorySubKey[] = ' 'L"' + CHROMIUM_MANDATORY_SUBKEY + '";\n' 'const wchar_t kRegistryRecommendedSubKey[] = ' 'L"' + CHROMIUM_RECOMMENDED_SUBKEY + '";\n' '#endif\n\n') f.write('bool IsDeprecatedPolicy(const std::string& policy) {\n' ' for (size_t i = 0; i < arraysize(kDeprecatedPolicyList);' ' ++i) {\n' ' if (policy == kDeprecatedPolicyList[i])\n' ' return true;\n' ' }\n' ' return false;\n' '}\n' '\n' 'const PolicyDefinitionList* GetChromePolicyDefinitionList() {\n' ' return &kChromePolicyList;\n' '}\n\n') f.write('namespace key {\n\n') for policy in _GetPolicyList(template_file_contents): f.write('const char k{name}[] = "{name}";\n'.format(name=policy.name)) f.write('\n} // namespace key\n\n' '} // namespace policy\n') #------------------ policy protobufs --------------------------------# CHROME_SETTINGS_PROTO_HEAD = ''' syntax = "proto2"; option optimize_for = LITE_RUNTIME; package enterprise_management; // For StringList and PolicyOptions. import "cloud_policy.proto"; ''' CLOUD_POLICY_PROTO_HEAD = ''' syntax = "proto2"; option optimize_for = LITE_RUNTIME; package enterprise_management; message StringList { repeated string entries = 1; } message PolicyOptions { enum PolicyMode { // The given settings are applied regardless of user choice. MANDATORY = 0; // The user may choose to override the given settings. RECOMMENDED = 1; // No policy value is present and the policy should be ignored. UNSET = 2; } optional PolicyMode mode = 1 [default = MANDATORY]; } message BooleanPolicyProto { optional PolicyOptions policy_options = 1; optional bool value = 2; } message IntegerPolicyProto { optional PolicyOptions policy_options = 1; optional int64 value = 2; } message StringPolicyProto { optional PolicyOptions policy_options = 1; optional string value = 2; } message StringListPolicyProto { optional PolicyOptions policy_options = 1; optional StringList value = 2; } ''' # TODO(joaodasilva): introduce a message to represent dictionary values. # Mapping 'dict' to 'string' for now. http://crbug.com/108997 PROTOBUF_TYPE = { 'dict': 'string', 'int': 'int64', 'int-enum': 'int64', 'list': 'StringList', 'main': 'bool', 'string': 'string', 'string-enum': 'string', } POLICY_PROTOBUF_TYPE = { 'dict': 'String', 'int': 'Integer', 'int-enum': 'Integer', 'list': 'StringList', 'main': 'Boolean', 'string': 'String', 'string-enum': 'String', } # Field IDs [1..RESERVED_IDS] will not be used in the wrapping protobuf. RESERVED_IDS = 2 def _WritePolicyProto(file, policy, fields): if policy.get('device_only', False): return desc = '\n'.join(map(str.strip, _RemovePlaceholders(policy['desc']).splitlines())) _OutputComment(file, _RemovePlaceholders(policy['caption']) + '\n\n' + desc) file.write('message %sProto {\n' % policy['name']) file.write(' optional PolicyOptions policy_options = 1;\n') file.write(' optional %s %s = 2;\n' % (PROTOBUF_TYPE[policy['type']], policy['name'])) file.write('}\n\n') fields += [' optional %sProto %s = %s;\n' % (policy['name'], policy['name'], policy['id'] + RESERVED_IDS)] def _WriteChromeSettingsProtobuf(template_file_contents, args, outfilepath): with open(outfilepath, 'w') as f: _OutputGeneratedWarningForC(f, args[2]) f.write(CHROME_SETTINGS_PROTO_HEAD) fields = [] f.write('// PBs for individual settings.\n\n') for policy in _Flatten(template_file_contents): _WritePolicyProto(f, policy, fields) f.write('// --------------------------------------------------\n' '// Big wrapper PB containing the above groups.\n\n' 'message ChromeSettingsProto {\n') f.write(''.join(fields)) f.write('}\n\n') def _WriteCloudPolicyProtobuf(template_file_contents, args, outfilepath): with open(outfilepath, 'w') as f: _OutputGeneratedWarningForC(f, args[2]) f.write(CLOUD_POLICY_PROTO_HEAD) f.write('message CloudPolicySettings {\n') for policy in _Flatten(template_file_contents): if policy.get('device_only', False): continue proto_type = POLICY_PROTOBUF_TYPE[policy['type']] f.write(' optional %sPolicyProto %s = %s;\n' % (proto_type, policy['name'], policy['id'] + RESERVED_IDS)) f.write('}\n\n') #------------------ protobuf decoder -------------------------------# CPP_HEAD = ''' #include #include #include "base/logging.h" #include "base/values.h" #include "chrome/browser/policy/policy_map.h" #include "chrome/browser/policy/proto/cloud_policy.pb.h" #include "policy/policy_constants.h" using google::protobuf::RepeatedPtrField; namespace policy { namespace em = enterprise_management; Value* DecodeIntegerValue(google::protobuf::int64 value) { if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { LOG(WARNING) << "Integer value " << value << " out of numeric limits, ignoring."; return NULL; } return Value::CreateIntegerValue(static_cast(value)); } ListValue* DecodeStringList(const em::StringList& string_list) { ListValue* list_value = new ListValue; RepeatedPtrField::const_iterator entry; for (entry = string_list.entries().begin(); entry != string_list.entries().end(); ++entry) { list_value->Append(Value::CreateStringValue(*entry)); } return list_value; } void DecodePolicy(const em::CloudPolicySettings& policy, PolicyMap* map) { ''' CPP_FOOT = '''} } // namespace policy ''' def _CreateValue(type, arg): if type == 'main': return "Value::CreateBooleanValue(%s)" % arg elif type in ('int', 'int-enum'): return "DecodeIntegerValue(%s)" % arg elif type in ('string', 'string-enum'): return "Value::CreateStringValue(%s)" % arg elif type == 'list': return "DecodeStringList(%s)" % arg elif type == 'dict': # TODO(joaodasilva): decode 'dict' types. http://crbug.com/108997 return "new DictionaryValue()" else: raise NotImplementedError() def _WritePolicyCode(file, policy): if policy.get('device_only', False): return membername = policy['name'].lower() proto_type = '%sPolicyProto' % POLICY_PROTOBUF_TYPE[policy['type']] file.write(' if (policy.has_%s()) {\n' % membername) file.write(' const em::%s& policy_proto = policy.%s();\n' % (proto_type, membername)) file.write(' if (policy_proto.has_value()) {\n') file.write(' PolicyLevel level = POLICY_LEVEL_MANDATORY;\n' ' bool do_set = true;\n' ' if (policy_proto.has_policy_options()) {\n' ' do_set = false;\n' ' switch(policy_proto.policy_options().mode()) {\n' ' case em::PolicyOptions::MANDATORY:\n' ' do_set = true;\n' ' level = POLICY_LEVEL_MANDATORY;\n' ' break;\n' ' case em::PolicyOptions::RECOMMENDED:\n' ' do_set = true;\n' ' level = POLICY_LEVEL_RECOMMENDED;\n' ' break;\n' ' case em::PolicyOptions::UNSET:\n' ' break;\n' ' }\n' ' }\n' ' if (do_set) {\n') file.write(' Value* value = %s;\n' % (_CreateValue(policy['type'], 'policy_proto.value()'))) file.write(' map->Set(key::k%s, level, POLICY_SCOPE_USER, value);\n' % policy['name']) file.write(' }\n' ' }\n' ' }\n') def _WriteCloudPolicyDecoder(template_file_contents, args, outfilepath): with open(outfilepath, 'w') as f: _OutputGeneratedWarningForC(f, args[2]) f.write(CPP_HEAD) for policy in _Flatten(template_file_contents): _WritePolicyCode(f, policy) f.write(CPP_FOOT) if __name__ == '__main__': sys.exit(main())