#!/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 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', } 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('--ppb', '--policy-protobuf', dest='proto_path', help='generate cloud policy protobuf file', metavar='FILE') parser.add_option('--ppd', '--protobuf-decoder', dest='decoder_path', help='generate C++ code decoding the 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.proto_path is not None: _WriteProtobuf(template_file_contents, args, opts.proto_path) if opts.decoder_path is not None: _WriteProtobufParser(template_file_contents, args, opts.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 a tuple with details about the given policy: # (name, type, list_of_platforms, is_deprecated, is_device_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 (name, vtype, platforms, is_deprecated, is_device_policy) def _GetPolicyList(template_file_contents): policies = [] for policy in template_file_contents['policy_definitions']: if policy['type'] == 'group': for sub_policy in policy['policies']: policies.append(_GetPolicyDetails(sub_policy)) else: policies.append(_GetPolicyDetails(policy)) # Tuples are sorted in lexicographical order, which will sort by policy name # in this case. policies.sort() return policies def _GetPolicyNameList(template_file_contents): return [name for (name, _, _, _, _) in _GetPolicyList(template_file_contents)] def _GetChromePolicyList(template_file_contents): return [(name, platforms, vtype, is_device_policy) for (name, vtype, platforms, _, is_device_policy) in _GetPolicyList(template_file_contents)] def _GetDeprecatedPolicyList(template_file_contents): return [name for (name, _, _, is_deprecated, _) in _GetPolicyList(template_file_contents) if is_deprecated] 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' '#pragma once\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' ' };\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_name in _GetPolicyNameList(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') policy_list = _GetChromePolicyList(template_file_contents) for (name, platforms, vtype, device_policy) in policy_list: if (platform in platforms) or (platform_wildcard in platforms): f.write(' { key::k%s, Value::%s, %s },\n' % (name, vtype, 'true' if device_policy else 'false')) 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 name in _GetDeprecatedPolicyList(template_file_contents): f.write(' key::k%s,\n' % 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_name in _GetPolicyNameList(template_file_contents): f.write('const char k%s[] = "%s";\n' % (policy_name, policy_name)) f.write('\n} // namespace key\n\n' '} // namespace policy\n') #------------------ policy protobuf --------------------------------# 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]; } ''' # 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', } # 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 _WriteProtobuf(template_file_contents, args, outfilepath): with open(outfilepath, 'w') as f: _OutputGeneratedWarningForC(f, args[2]) f.write(PROTO_HEAD) fields = [] f.write('// PBs for individual settings.\n\n') for policy in template_file_contents['policy_definitions']: if policy['type'] == 'group': for sub_policy in policy['policies']: _WritePolicyProto(f, sub_policy, fields) else: _WritePolicyProto(f, policy, fields) f.write('// --------------------------------------------------\n' '// Big wrapper PB containing the above groups.\n\n' 'message CloudPolicySettings {\n') f.write(''.join(fields)) 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 = '%sProto' % policy['name'] proto_name = '%s_proto' % membername file.write(' if (policy.has_%s()) {\n' % membername) file.write(' const em::%s& %s = policy.%s();\n' % (proto_type, proto_name, membername)) file.write(' if (%s.has_%s()) {\n' % (proto_name, membername)) file.write(' PolicyLevel level = POLICY_LEVEL_MANDATORY;\n' ' bool do_set = true;\n' ' if (%s.has_policy_options()) {\n' ' do_set = false;\n' ' switch(%s.policy_options().mode()) {\n' % (proto_name, proto_name)) file.write(' 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'], '%s.%s()' % (proto_name, membername)))) file.write(' map->Set(key::k%s, level, POLICY_SCOPE_USER, value);\n' % policy['name']) file.write(' }\n' ' }\n' ' }\n') def _WriteProtobufParser(template_file_contents, args, outfilepath): with open(outfilepath, 'w') as f: _OutputGeneratedWarningForC(f, args[2]) f.write(CPP_HEAD) for policy in template_file_contents['policy_definitions']: if policy['type'] == 'group': for sub_policy in policy['policies']: _WritePolicyCode(f, sub_policy) else: _WritePolicyCode(f, policy) f.write(CPP_FOOT) if __name__ == '__main__': sys.exit(main())