diff options
Diffstat (limited to 'chromium/components/policy/tools/template_writers')
47 files changed, 13545 insertions, 0 deletions
diff --git a/chromium/components/policy/tools/template_writers/PRESUBMIT.py b/chromium/components/policy/tools/template_writers/PRESUBMIT.py new file mode 100755 index 00000000000..e159b0d3b7a --- /dev/null +++ b/chromium/components/policy/tools/template_writers/PRESUBMIT.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# Copyright 2017 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. +"""Template writers unittests presubmit script. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for +details on the presubmit API built into gcl. +""" + + +USE_PYTHON3 = True + + +def RunUnittests(input_api, output_api): + return input_api.canned_checks.RunPythonUnitTests(input_api, output_api, + ['test_suite_all']) + + +def CheckChangeOnUpload(input_api, output_api): + return RunUnittests(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return RunUnittests(input_api, output_api) diff --git a/chromium/components/policy/tools/template_writers/__init__.py b/chromium/components/policy/tools/template_writers/__init__.py new file mode 100755 index 00000000000..abc93655ae4 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +# 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. +'''Module template_writers +''' + +pass diff --git a/chromium/components/policy/tools/template_writers/policy_template_generator.py b/chromium/components/policy/tools/template_writers/policy_template_generator.py new file mode 100755 index 00000000000..99a9e03c89f --- /dev/null +++ b/chromium/components/policy/tools/template_writers/policy_template_generator.py @@ -0,0 +1,321 @@ +#!/usr/bin/env python3 +# 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. + +import copy +import os +import re +import sys + +sys.path.insert( + 0, + os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, + os.pardir, 'third_party', 'six', 'src')) + +import six + + +def IsGroupOrAtomicGroup(policy): + return policy['type'] == 'group' or policy['type'] == 'atomic_group' + + +class PolicyTemplateGenerator: + '''Generates template text for a particular platform. + + This class is used to traverse a JSON structure from a .json template + definition metafile and merge GUI message string definitions that come + from a .grd resource tree onto it. After this, it can be used to output + this data to policy template files using TemplateWriter objects. + ''' + + def _ImportMessage(self, msg_txt): + msg_txt = six.ensure_text(msg_txt) + lines = msg_txt.split('\n') + + # Strip any extra leading spaces, but keep useful indentation: + min_leading_spaces = min(list(self._IterateLeadingSpaces(lines)) or [0]) + if min_leading_spaces > 0: + lstrip_pattern = re.compile('^[ ]{1,%s}' % min_leading_spaces) + lines = [lstrip_pattern.sub('', line) for line in lines] + # Strip all trailing spaces: + lines = [line.rstrip() for line in lines] + return "\n".join(lines) + + def _IterateLeadingSpaces(self, lines): + '''Yields the number of leading spaces on each line, skipping lines which + have no leading spaces.''' + for line in lines: + match = re.search('^[ ]+', line) + if match: + yield len(match.group(0)) + + def __init__(self, config, policy_data): + '''Initializes this object with all the data necessary to output a + policy template. + + Args: + config: Writer configuration. + policy_data: The list of defined policies and groups, as parsed from the + policy metafile. See + components/policy/resources/policy_templates.json + for description and content. + ''' + # List of all the policies. Create a copy since the data is modified. + self._policy_data = copy.deepcopy(policy_data) + # Localized messages to be inserted to the policy_definitions structure: + self._messages = self._policy_data['messages'] + self._config = config + for key in self._messages.keys(): + self._messages[key]['text'] = self._ImportMessage( + self._messages[key]['text']) + self._AddGroups(self._policy_data['policy_definitions']) + self._AddAtomicGroups(self._policy_data['policy_definitions'], + self._policy_data['policy_atomic_group_definitions']) + self._policy_data[ + 'policy_atomic_group_definitions'] = self._ExpandAtomicGroups( + self._policy_data['policy_definitions'], + self._policy_data['policy_atomic_group_definitions']) + self._ProcessPolicyList( + self._policy_data['policy_atomic_group_definitions']) + self._policy_data['policy_definitions'] = self._ExpandGroups( + self._policy_data['policy_definitions']) + self._policy_definitions = self._policy_data['policy_definitions'] + self._ProcessPolicyList(self._policy_definitions) + + def _ProcessProductPlatformString(self, product_platform_string): + '''Splits the |product_platform_string| string to product and a list of + platforms.''' + if '.' in product_platform_string: + product, platform = product_platform_string.split('.') + if platform == '*': + # e.g.: 'chrome.*:8-10' + platforms = ['linux', 'mac', 'win'] + else: + # e.g.: 'chrome.win:-10' + platforms = [platform] + else: + # e.g.: 'chrome_frame:7-' + product, platform = { + 'android': ('chrome', 'android'), + 'webview_android': ('webview', 'android'), + 'ios': ('chrome', 'ios'), + 'chrome_os': ('chrome_os', 'chrome_os'), + 'chrome_frame': ('chrome_frame', 'win'), + }[product_platform_string] + platforms = [platform] + return product, platforms + + def _ProcessSupportedOn(self, supported_on): + '''Parses and converts the string items of the list of supported platforms + into dictionaries. + + Args: + supported_on: The list of supported platforms. E.g.: + ['chrome.win:8-10', 'chrome_frame:10-'] + + Returns: + supported_on: The list with its items converted to dictionaries. E.g.: + [{ + 'product': 'chrome', + 'platform': 'win', + 'since_version': '8', + 'until_version': '10' + }, { + 'product': 'chrome_frame', + 'platform': 'win', + 'since_version': '10', + 'until_version': '' + }] + ''' + result = [] + for supported_on_item in supported_on: + product_platform_part, version_part = supported_on_item.split(':') + product, platforms = self._ProcessProductPlatformString( + product_platform_part) + + since_version, until_version = version_part.split('-') + for platform in platforms: + result.append({ + 'product': product, + 'platform': platform, + 'since_version': since_version, + 'until_version': until_version + }) + return result + + def _ProcessFutureOn(self, future_on): + '''Parses and converts the |future_on| strings into a list of dictionaries + contain product and platform string pair. + + Args: + future_on: A list of platform strings. E.g.: + ['chrome.win', 'chromeos'] + Returns: + future_on: A list of dictionaries. E.g.: + [{ + 'product': 'chrome', + 'platform': 'win', + },{ + 'product': 'chrome_os', + 'platform': 'chrome_os', + }] + ''' + result = [] + for future in future_on: + product, platforms = self._ProcessProductPlatformString(future) + for platform in platforms: + result.append({ + 'product': product, + 'platform': platform, + }) + return result + + def _ProcessPolicy(self, policy): + '''Processes localized message strings in a policy or a group. + Also breaks up the content of 'supported_on' attribute into a list. + + Args: + policy: The data structure of the policy or group, that will get message + strings here. + ''' + if policy['type'] != 'atomic_group': + policy['desc'] = self._ImportMessage(policy['desc']) + policy['caption'] = self._ImportMessage(policy['caption']) + if 'label' in policy: + policy['label'] = self._ImportMessage(policy['label']) + if 'arc_support' in policy: + policy['arc_support'] = self._ImportMessage(policy['arc_support']) + + if IsGroupOrAtomicGroup(policy): + self._ProcessPolicyList(policy['policies']) + elif policy['type'] in ('string-enum', 'int-enum', 'string-enum-list'): + # Iterate through all the items of an enum-type policy, and add captions. + for item in policy['items']: + item['caption'] = self._ImportMessage(item['caption']) + if 'supported_on' in item: + item['supported_on'] = self._ProcessSupportedOn(item['supported_on']) + if not IsGroupOrAtomicGroup(policy): + if not 'label' in policy: + # If 'label' is not specified, then it defaults to 'caption': + policy['label'] = policy['caption'] + policy['supported_on'] = self._ProcessSupportedOn( + policy.get('supported_on', [])) + policy['future_on'] = self._ProcessFutureOn(policy.get('future_on', [])) + + def _ProcessPolicyList(self, policy_list): + '''Adds localized message strings to each item in a list of policies and + groups. Also breaks up the content of 'supported_on' attributes into lists + of dictionaries. + + Args: + policy_list: A list of policies and groups. Message strings will be added + for each item and to their child items, recursively. + ''' + for policy in policy_list: + self._ProcessPolicy(policy) + + def GetTemplateText(self, template_writer): + '''Generates the text of the template from the arguments given + to the constructor, using a given TemplateWriter. + + Args: + template_writer: An object implementing TemplateWriter. Its methods + are called here for each item of self._policy_data. + + Returns: + The text of the generated template. + ''' + # Create a copy, so that writers can't screw up subsequent writers. + policy_data_copy = copy.deepcopy(self._policy_data) + return template_writer.WriteTemplate(policy_data_copy) + + + def _AddGroups(self, policy_list): + '''Adds a 'group' field, which is set to be the group's name, to the + policies that are part of a group. + + Args: + policy_list: A list of policies and groups whose policies will have a + 'group' field added. + ''' + groups = [policy for policy in policy_list if policy['type'] == 'group'] + policy_lookup = { + policy['name']: policy + for policy in policy_list + if not IsGroupOrAtomicGroup(policy) + } + for group in groups: + for policy_name in group['policies']: + policy_lookup[policy_name]['group'] = group['name'] + + def _AddAtomicGroups(self, policy_list, policy_atomic_groups): + '''Adds an 'atomic_group' field to the policies that are part of an atomic + group. + + Args: + policy_list: A list of policies and groups. + policy_atomic_groups: A list of policy atomic groups + ''' + policy_lookup = { + policy['name']: policy + for policy in policy_list + if not IsGroupOrAtomicGroup(policy) + } + for group in policy_atomic_groups: + for policy_name in group['policies']: + policy_lookup[policy_name]['atomic_group'] = group['name'] + break + + def _ExpandAtomicGroups(self, policy_list, policy_atomic_groups): + '''Replaces policies names inside atomic group definitions for actual + policies definitions. + + Args: + policy_list: A list of policies and groups. + + Returns: + Modified policy_list + ''' + policies = [ + policy for policy in policy_list if not IsGroupOrAtomicGroup(policy) + ] + for group in policy_atomic_groups: + group['type'] = 'atomic_group' + expanded = self._ExpandGroups(policies + policy_atomic_groups) + expanded = [policy for policy in expanded if IsGroupOrAtomicGroup(policy)] + return copy.deepcopy(expanded) + + def _ExpandGroups(self, policy_list): + '''Replaces policies names inside group definitions for actual policies + definitions. If policy does not belong to any group, leave it as is. + + Args: + policy_list: A list of policies and groups. + + Returns: + Modified policy_list + ''' + groups = [policy for policy in policy_list if IsGroupOrAtomicGroup(policy)] + policies = { + policy['name']: policy + for policy in policy_list + if not IsGroupOrAtomicGroup(policy) + } + policies_in_groups = set() + result_policies = [] + for group in groups: + group_policies = group['policies'] + expanded_policies = [ + policies[policy_name] for policy_name in group_policies + ] + assert policies_in_groups.isdisjoint(group_policies) + policies_in_groups.update(group_policies) + group['policies'] = expanded_policies + result_policies.append(group) + + result_policies.extend([ + policy for policy in policy_list if not IsGroupOrAtomicGroup(policy) and + policy['name'] not in policies_in_groups + ]) + return result_policies diff --git a/chromium/components/policy/tools/template_writers/policy_template_generator_unittest.py b/chromium/components/policy/tools/template_writers/policy_template_generator_unittest.py new file mode 100755 index 00000000000..5beb6082a4d --- /dev/null +++ b/chromium/components/policy/tools/template_writers/policy_template_generator_unittest.py @@ -0,0 +1,654 @@ +#!/usr/bin/env python3 +# 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. + +import os +import sys +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../..')) + +import unittest + +import policy_template_generator +from writers import mock_writer +from writers import template_writer + + +class PolicyTemplateGeneratorUnittest(unittest.TestCase): + '''Unit tests for policy_template_generator.py.''' + + TEST_CONFIG = { + 'app_name': '_app_name', + 'frame_name': '_frame_name', + 'os_name': '_os_name', + } + + TEST_POLICY_DATA = { + 'messages': {}, + 'placeholders': [], + 'policy_definitions': [], + 'policy_atomic_group_definitions': [], + } + + def do_test(self, policy_data, writer): + '''Executes a test case. + + Creates and invokes an instance of PolicyTemplateGenerator with + the given arguments. + + Notice: Plain comments are used in test methods instead of docstrings, + so that method names do not get overridden by the docstrings in the + test output. + + Args: + policy_data: The list of policies and groups as it would be + loaded from policy_templates.json. + writer: A writer used for this test. It is usually derived from + mock_writer.MockWriter. + ''' + writer.tester = self + + policy_data = dict(self.TEST_POLICY_DATA, **policy_data) + policy_generator = policy_template_generator.PolicyTemplateGenerator( + self.TEST_CONFIG, policy_data) + res = policy_generator.GetTemplateText(writer) + writer.Test() + return res + + def testSequence(self): + # Test the sequence of invoking the basic PolicyWriter operations, + # in case of empty input data structures. + class LocalMockWriter(mock_writer.MockWriter): + + def __init__(self): + self.log = 'init;' + + def Init(self): + self.log += 'prepare;' + + def BeginTemplate(self): + self.log += 'begin;' + + def EndTemplate(self): + self.log += 'end;' + + def GetTemplateText(self): + self.log += 'get_text;' + return 'writer_result_string' + + def Test(self): + self.tester.assertEquals(self.log, 'init;prepare;begin;end;get_text;') + + result = self.do_test({}, LocalMockWriter()) + self.assertEquals(result, 'writer_result_string') + + def testEmptyGroups(self): + # Test that empty policy groups are not passed to the writer. + policies_mock = { + 'policy_definitions': [ + { + 'name': 'Group1', + 'type': 'group', + 'policies': [], + 'desc': '', + 'caption': '' + }, + { + 'name': 'Group2', + 'type': 'group', + 'policies': [], + 'desc': '', + 'caption': '' + }, + { + 'name': 'Group3', + 'type': 'group', + 'policies': [], + 'desc': '', + 'caption': '' + }, + ] + } + + class LocalMockWriter(mock_writer.MockWriter): + + def __init__(self): + self.log = '' + + def BeginPolicyGroup(self, group): + self.log += '[' + + def EndPolicyGroup(self): + self.log += ']' + + def Test(self): + self.tester.assertEquals(self.log, '') + + self.do_test(policies_mock, LocalMockWriter()) + + def testGroups(self): + # Test that policy groups are passed to the writer in the correct order. + policies_mock = { + 'policy_definitions': [ + { + 'name': 'Group1', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['TAG1'], + }, + { + 'name': 'Group2', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['TAG2',], + }, + { + 'name': 'Group3', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['TAG3'], + }, + { + 'name': 'TAG1', + 'type': 'mock', + 'supported_on': [], + 'caption': '', + 'desc': '' + }, + { + 'name': 'TAG2', + 'type': 'mock', + 'supported_on': [], + 'caption': '', + 'desc': '' + }, + { + 'name': 'TAG3', + 'type': 'mock', + 'supported_on': [], + 'caption': '', + 'desc': '' + }, + ] + } + + class LocalMockWriter(mock_writer.MockWriter): + + def __init__(self): + self.log = '' + + def BeginPolicyGroup(self, group): + self.log += '[' + group['policies'][0]['name'] + + def EndPolicyGroup(self): + self.log += ']' + + def Test(self): + self.tester.assertEquals(self.log, '[TAG1][TAG2][TAG3]') + + self.do_test(policies_mock, LocalMockWriter()) + + def testPolicies(self): + # Test that policies are passed to the writer in the correct order. + policy_defs_mock = { + 'policy_definitions': [ + { + 'name': 'Group1', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['Group1Policy1', 'Group1Policy2'], + }, + { + 'name': 'Group2', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['Group2Policy3'], + }, + { + 'name': 'Group1Policy1', + 'type': 'string', + 'supported_on': [], + 'caption': '', + 'desc': '' + }, + { + 'name': 'Group1Policy2', + 'type': 'string', + 'supported_on': [], + 'caption': '', + 'desc': '' + }, + { + 'name': 'Group2Policy3', + 'type': 'string', + 'supported_on': [], + 'caption': '', + 'desc': '' + }, + ] + } + + class LocalMockWriter(mock_writer.MockWriter): + + def __init__(self): + self.policy_name = None + self.policy_list = [] + + def BeginPolicyGroup(self, group): + self.group = group + + def EndPolicyGroup(self): + self.group = None + + def WritePolicy(self, policy): + self.tester.assertEquals(policy['name'][0:6], self.group['name']) + self.policy_list.append(policy['name']) + + def Test(self): + self.tester.assertEquals( + self.policy_list, + ['Group1Policy1', 'Group1Policy2', 'Group2Policy3']) + + self.do_test(policy_defs_mock, LocalMockWriter()) + + def testIntEnumTexts(self): + # Test that GUI messages are assigned correctly to int-enums + # (aka dropdown menus). + policy_defs_mock = { + 'policy_definitions': [{ + 'name': + 'Policy1', + 'type': + 'int-enum', + 'caption': + '', + 'desc': + '', + 'supported_on': [], + 'items': [ + { + 'name': 'item1', + 'value': 0, + 'caption': 'string1', + 'desc': '' + }, + { + 'name': 'item2', + 'value': 1, + 'caption': 'string2', + 'desc': '' + }, + { + 'name': 'item3', + 'value': 3, + 'caption': 'string3', + 'desc': '' + }, + ] + }] + } + + class LocalMockWriter(mock_writer.MockWriter): + + def WritePolicy(self, policy): + self.tester.assertEquals(policy['items'][0]['caption'], 'string1') + self.tester.assertEquals(policy['items'][1]['caption'], 'string2') + self.tester.assertEquals(policy['items'][2]['caption'], 'string3') + + self.do_test(policy_defs_mock, LocalMockWriter()) + + def testStringEnumTexts(self): + # Test that GUI messages are assigned correctly to string-enums + # (aka dropdown menus). + policy_data_mock = { + 'policy_definitions': [{ + 'name': + 'Policy1', + 'type': + 'string-enum', + 'caption': + '', + 'desc': + '', + 'supported_on': [], + 'items': [ + { + 'name': 'item1', + 'value': 'one', + 'caption': 'string1', + 'desc': '' + }, + { + 'name': 'item2', + 'value': 'two', + 'caption': 'string2', + 'desc': '' + }, + { + 'name': 'item3', + 'value': 'three', + 'caption': 'string3', + 'desc': '' + }, + ] + }] + } + + class LocalMockWriter(mock_writer.MockWriter): + + def WritePolicy(self, policy): + self.tester.assertEquals(policy['items'][0]['caption'], 'string1') + self.tester.assertEquals(policy['items'][1]['caption'], 'string2') + self.tester.assertEquals(policy['items'][2]['caption'], 'string3') + + self.do_test(policy_data_mock, LocalMockWriter()) + + def testStringEnumTexts(self): + # Test that GUI messages are assigned correctly to string-enums + # (aka dropdown menus). + policy_data_mock = { + 'policy_definitions': [{ + 'name': + 'Policy1', + 'type': + 'string-enum-list', + 'caption': + '', + 'desc': + '', + 'supported_on': [], + 'items': [ + { + 'name': 'item1', + 'value': 'one', + 'caption': 'string1', + 'desc': '' + }, + { + 'name': 'item2', + 'value': 'two', + 'caption': 'string2', + 'desc': '' + }, + { + 'name': 'item3', + 'value': 'three', + 'caption': 'string3', + 'desc': '' + }, + ] + }] + } + + class LocalMockWriter(mock_writer.MockWriter): + + def WritePolicy(self, policy): + self.tester.assertEquals(policy['items'][0]['caption'], 'string1') + self.tester.assertEquals(policy['items'][1]['caption'], 'string2') + self.tester.assertEquals(policy['items'][2]['caption'], 'string3') + + self.do_test(policy_data_mock, LocalMockWriter()) + + def testWin7OnlyPolicy(self): + # Test that Win7 only policy is marked as windows policy with speicial flag. + policy_data_mock = { + 'policy_definitions': [{ + 'name': + 'Policy1', + 'type': + 'string-enum-list', + 'caption': + '', + 'desc': + '', + 'supported_on': ['chrome.win7:2-'], + 'items': [{ + 'name': 'item1', + 'value': 'one', + 'caption': 'string1', + 'desc': '', + 'supported_on': ['chrome.win7:2-'], + },] + }] + } + + class LocalMockWriter(mock_writer.MockWriter): + + def WritePolicy(self, policy): + self.tester.assertEquals(policy['supported_on'][0]['platform'], 'win7') + self.tester.assertEquals( + policy['items'][0]['supported_on'][0]['platform'], 'win7') + + self.do_test(policy_data_mock, LocalMockWriter()) + + def testFutures(self): + # Test that 'future_on' tag has been processed successfully. + policy_data_mock = { + 'policy_definitions': [{ + 'name': 'UnrelasedPolicy', + 'type': 'string', + 'caption': '', + 'desc': '', + 'future_on': ['chrome.*', 'chrome_os'] + }, { + 'name': + 'PartiallyReleasedPolicy', + 'type': + 'string', + 'caption': + '', + 'desc': + '', + 'supported_on': ['chrome.win:2-', 'chrome.mac:2-', 'chrome_os:4-'], + 'future_on': ['chrome.linux', 'chrome_os'], + }, { + 'name': 'ReleasedPolicy', + 'type': 'string', + 'caption': '', + 'desc': '', + 'supported_on': ['chrome.*:2-', 'chrome_os:4-'], + }] + } + + expected_future_on = { + 'UnrelasedPolicy': [{ + 'product': 'chrome', + 'platform': 'linux' + }, { + 'product': 'chrome', + 'platform': 'mac' + }, { + 'product': 'chrome', + 'platform': 'win' + }, { + 'product': 'chrome_os', + 'platform': 'chrome_os' + }], + 'PartiallyReleasedPolicy': [{ + 'product': 'chrome', + 'platform': 'linux' + }, { + 'product': 'chrome_os', + 'platform': 'chrome_os' + }], + 'ReleasedPolicy': [], + } + + class LocalMockWriter(mock_writer.MockWriter): + def WritePolicy(self, policy): + self.tester.assertTrue(isinstance(policy['supported_on'], list)) + self.tester.assertEquals(policy['future_on'], + expected_future_on[policy['name']]) + + self.do_test(policy_data_mock, LocalMockWriter()) + + def testPolicyFiltering(self): + # Test that policies are filtered correctly based on their annotations. + policy_data_mock = { + 'policy_definitions': [ + { + 'name': 'Group1', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['Group1Policy1', 'Group1Policy2'], + }, + { + 'name': 'Group2', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['Group2Policy3'], + }, + { + 'name': 'SinglePolicy', + 'type': 'int', + 'caption': '', + 'desc': '', + 'supported_on': ['chrome.eee:8-'] + }, + { + 'name': + 'Group1Policy1', + 'type': + 'string', + 'caption': + '', + 'desc': + '', + 'supported_on': [ + 'chrome.aaa:8-', 'chrome.bbb:8-', 'chrome.ccc:8-' + ] + }, + { + 'name': 'Group1Policy2', + 'type': 'string', + 'caption': '', + 'desc': '', + 'supported_on': ['chrome.ddd:8-'] + }, + { + 'name': 'Group2Policy3', + 'type': 'string', + 'caption': '', + 'desc': '', + 'supported_on': ['chrome.eee:8-'] + }, + ] + } + + # This writer accumulates the list of policies it is asked to write. + # This list is stored in the result_list member variable and can + # be used later for assertions. + class LocalMockWriter(mock_writer.MockWriter): + + def __init__(self, platforms): + super(LocalMockWriter, self).__init__(platforms) + self.policy_name = None + self.result_list = [] + + def BeginPolicyGroup(self, group): + self.group = group + self.result_list.append('begin_' + group['name']) + + def EndPolicyGroup(self): + self.result_list.append('end_group') + self.group = None + + def WritePolicy(self, policy): + self.result_list.append(policy['name']) + + def IsPolicySupported(self, policy): + # Call the original (non-mock) implementation of this method. + return template_writer.TemplateWriter.IsPolicySupported(self, policy) + + local_mock_writer = LocalMockWriter(['eee']) + self.do_test(policy_data_mock, local_mock_writer) + # Test that only policies of platform 'eee' were written: + self.assertEquals( + local_mock_writer.result_list, + ['begin_Group2', 'Group2Policy3', 'end_group', 'SinglePolicy']) + + local_mock_writer = LocalMockWriter(['ddd', 'bbb']) + self.do_test(policy_data_mock, local_mock_writer) + # Test that only policies of platforms 'ddd' and 'bbb' were written: + self.assertEquals( + local_mock_writer.result_list, + ['begin_Group1', 'Group1Policy1', 'Group1Policy2', 'end_group']) + + def testSortingInvoked(self): + # Tests that policy-sorting happens before passing policies to the writer. + policy_data = { + 'policy_definitions': [ + { + 'name': 'zp', + 'type': 'string', + 'supported_on': [], + 'caption': '', + 'desc': '' + }, + { + 'name': 'ap', + 'type': 'string', + 'supported_on': [], + 'caption': '', + 'desc': '' + }, + ] + } + + class LocalMockWriter(mock_writer.MockWriter): + + def __init__(self): + self.result_list = [] + + def WritePolicy(self, policy): + self.result_list.append(policy['name']) + + def Test(self): + self.tester.assertEquals(self.result_list, ['ap', 'zp']) + + self.do_test(policy_data, LocalMockWriter()) + + def testImportMessage_noIndentation(self): + message = ''' +Simple policy: + +Description of simple policy''' + + policy_generator = policy_template_generator.PolicyTemplateGenerator( + self.TEST_CONFIG, self.TEST_POLICY_DATA) + self.assertEquals(message, policy_generator._ImportMessage(message)) + + def testImportMessage_withIndentation(self): + message = '''JSON policy: + + JSON spec: + { + "key": { + "key2": "value" + } + }''' + imported_message = '''JSON policy: + +JSON spec: +{ + "key": { + "key2": "value" + } +}''' + + policy_generator = policy_template_generator.PolicyTemplateGenerator( + self.TEST_CONFIG, self.TEST_POLICY_DATA) + self.assertEquals(imported_message, + policy_generator._ImportMessage(message)) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/template_formatter.py b/chromium/components/policy/tools/template_writers/template_formatter.py new file mode 100755 index 00000000000..9af88164b66 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/template_formatter.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python3 +# Copyright 2017 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. +'''Takes translated policy_template.json files as input, applies template +writers and emits various template and doc files (admx, html, json etc.). +''' + +import argparse +import codecs +import collections +import json +import os +import re +import sys + +sys.path.insert( + 0, + os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, + os.pardir, 'third_party', 'six', 'src')) + +import six + +import writer_configuration +import policy_template_generator + +from writers import adm_writer, adml_writer, admx_writer, \ + chromeos_admx_writer, chromeos_adml_writer, \ + google_admx_writer, google_adml_writer, \ + android_policy_writer, reg_writer, doc_writer, \ + doc_atomic_groups_writer , json_writer, plist_writer, \ + plist_strings_writer, ios_app_config_writer, jamf_writer + + +def MacLanguageMap(lang): + '''Handles slightly different path naming convention for Macs: + - 'en-US' -> 'en' + - '-' -> '_' + + Args: + lang: Language, e.g. 'en-US'. + ''' + return 'en' if lang == 'en-US' else lang.replace('-', '_') + + +'''Template writer descriptors. + +Members: + type: Writer type, e.g. 'admx' + is_per_language: Whether one file per language should be emitted. + encoding: Encoding of the output file. + language_map: Optional language mapping for file paths. + force_windows_line_ending: Forces output file to use Windows line ending. +''' +WriterDesc = collections.namedtuple('WriterDesc', [ + 'type', 'is_per_language', 'encoding', 'language_map', + 'force_windows_line_ending' +]) + +_WRITER_DESCS = [ + WriterDesc('adm', True, 'utf-16', None, True), + WriterDesc('adml', True, 'utf-16', None, True), + WriterDesc('admx', False, 'utf-16', None, True), + WriterDesc('google_adml', True, 'utf-8', None, True), + WriterDesc('google_admx', False, 'utf-8', None, True), + WriterDesc('chromeos_adml', True, 'utf-8', None, True), + WriterDesc('chromeos_admx', False, 'utf-8', None, True), + WriterDesc('android_policy', False, 'utf-8', None, False), + WriterDesc('reg', False, 'utf-16', None, False), + WriterDesc('doc', True, 'utf-8', None, False), + WriterDesc('doc_atomic_groups', True, 'utf-8', None, False), + WriterDesc('json', False, 'utf-8', None, False), + WriterDesc('plist', False, 'utf-8', None, False), + WriterDesc('plist_strings', True, 'utf-8', MacLanguageMap, False), + WriterDesc('jamf', False, 'utf-8', None, False), + WriterDesc('ios_app_config', False, 'utf-8', None, False), +] + +# Template writers that are not per-language use policy_templates.json from +# this language. +_DEFAULT_LANGUAGE = 'en-US' + + +def GetWriter(writer_type, config): + '''Returns the template writer for the given writer type. + + Args: + writer_type: Writer type, e.g. 'admx'. + config: Writer configuration, see writer_configuration.py. + ''' + return eval(writer_type + '_writer.GetWriter(config)') + + +def _GetWriterConfiguration(grit_defines): + '''Returns the writer configuration based on grit defines. + + Args: + grit_defines: Array of grit defines, see grit_rule.gni. + ''' + # Build a dictionary from grit defines, which can be plain DEFs or KEY=VALUEs. + grit_defines_dict = {} + for define in grit_defines: + parts = define.split('=', 1) + grit_defines_dict[parts[0]] = parts[1] if len(parts) > 1 else 1 + return writer_configuration.GetConfigurationForBuild(grit_defines_dict) + + +def _ParseVersionFile(version_path): + '''Parse version file, return the version if it exists. + + Args: + version_path: The path of Chrome VERSION file containing the major version + number. + ''' + version = {} + with open(version_path) as fp: + for line in fp: + key, _, value = line.partition('=') + if key.strip() == 'MAJOR': + version['major'] = value.strip() + elif key.strip() == 'MINOR': + version['minor'] = value.strip() + elif key.strip() == 'BUILD': + version['build'] = value.strip() + elif key.strip() == 'PATCH': + version['patch'] = value.strip() + + version_found = len(version) == 4 + return version if version_found else None + + +def _JsonToUtf8Encoding(data, ignore_dicts=False): + if six.PY2 and isinstance(data, unicode): + return data.encode('utf-8') + elif isinstance(data, list): + return [_JsonToUtf8Encoding(item, False) for item in data] + elif isinstance(data, dict): + return { + _JsonToUtf8Encoding(key): _JsonToUtf8Encoding(value) + for key, value in data.items() + } + return data + + +def main(): + '''Main policy template conversion script. + Usage: template_formatter + --translations <translations_path> + --languages <language_list> + [--adm <adm_path>] + ... + [--android_policy <android_policy_path>] + -D <grit_define> + -E <grit_env_variable> + -t <grit_target_platform> + + Args: + translations: Absolute path of the translated policy_template.json + files. Must contain a ${lang} placeholder for the language. + languages: Comma-separated list of languages. Trailing commas are fine, e.g. + 'en,de,' + adm, adml, google_adml, doc, plist_string: Absolute path of the + corresponding file types. Must contain a ${lang} placeholder. + admx, google_admx, android_policy, reg, json, plist: Absolute path of the + corresponding file types. Must NOT contain a ${lang} placeholder. There + is only one output file, not one per language. + D: List of grit defines, used to assemble writer configuration. + E, t: Grit environment variables and target platform. Unused, but + grit_rule.gni adds them, so ArgumentParser must handle them. + ''' + parser = argparse.ArgumentParser() + parser.add_argument('--translations', dest='translations') + parser.add_argument('--languages', dest='languages') + parser.add_argument('--version_path', dest='version_path') + parser.add_argument('--adm', action='append', dest='adm') + parser.add_argument('--adml', action='append', dest='adml') + parser.add_argument('--admx', action='append', dest='admx') + parser.add_argument('--chromeos_adml', action='append', dest='chromeos_adml') + parser.add_argument('--chromeos_admx', action='append', dest='chromeos_admx') + parser.add_argument('--google_adml', action='append', dest='google_adml') + parser.add_argument('--google_admx', action='append', dest='google_admx') + parser.add_argument('--reg', action='append', dest='reg') + parser.add_argument('--doc', action='append', dest='doc') + parser.add_argument( + '--doc_atomic_groups', action='append', dest='doc_atomic_groups') + parser.add_argument('--local', + action='store_true', + help='If set, the documentation will be built so ' + 'that links work locally in the generated path.') + parser.add_argument('--json', action='append', dest='json') + parser.add_argument('--plist', action='append', dest='plist') + parser.add_argument('--plist_strings', action='append', dest='plist_strings') + parser.add_argument('--jamf', action='append', dest='jamf') + parser.add_argument( + '--android_policy', action='append', dest='android_policy') + parser.add_argument( + '--ios_app_config', action='append', dest='ios_app_config') + parser.add_argument('-D', action='append', dest='grit_defines') + parser.add_argument('-E', action='append', dest='grit_build_env') + parser.add_argument('-t', action='append', dest='grit_target') + args = parser.parse_args() + + _LANG_PLACEHOLDER = "${lang}" + assert _LANG_PLACEHOLDER in args.translations + + languages = list(filter(bool, args.languages.split(','))) + assert _DEFAULT_LANGUAGE in languages + + config = _GetWriterConfiguration(args.grit_defines) + + version = _ParseVersionFile(args.version_path) + if version != None: + config['major_version'] = int(version['major']) + config['version'] = '.'.join([ + version['major'], version['minor'], version['build'], version['patch'] + ]) + config['local'] = args.local + + # For each language, load policy data once and run all writers on it. + for lang in languages: + # Load the policy data. + policy_templates_json_path = args.translations.replace( + _LANG_PLACEHOLDER, lang) + # Loads the localized policy json file which must be a valid json file + # encoded in utf-8. + with codecs.open(policy_templates_json_path, 'r', 'utf-8') as policy_file: + policy_data = json.loads( + policy_file.read(), object_hook=_JsonToUtf8Encoding) + + # Preprocess the policy data. + policy_generator = policy_template_generator.PolicyTemplateGenerator( + config, policy_data) + + for writer_desc in _WRITER_DESCS: + # For writer types that are not per language (e.g. admx), only do it once. + if (not writer_desc.is_per_language and lang != _DEFAULT_LANGUAGE): + continue + + # Was the current writer type passed as argument, e.g. --admx <path>? + # Note that all paths are arrays and we loop over all of them. + output_paths = getattr(args, writer_desc.type, '') + if (not output_paths): + continue + for output_path in output_paths: + # Substitute language placeholder in output file. + if (writer_desc.is_per_language): + assert _LANG_PLACEHOLDER in output_path + mapped_lang = writer_desc.language_map( + lang) if writer_desc.language_map else lang + output_path = output_path.replace(_LANG_PLACEHOLDER, mapped_lang) + else: + assert _LANG_PLACEHOLDER not in output_path + + # Run the template writer on th policy data. + writer = GetWriter(writer_desc.type, config) + output_data = policy_generator.GetTemplateText(writer) + # Make sure the file uses Windows line endings if needed. This is + # important here because codecs.open() opens files in binary more and + # will not do line ending conversion. + if writer_desc.force_windows_line_ending: + output_data = re.sub(r'([^\r])\n', r'\1\r\n', output_data) + + # Make output directory if it doesn't exist yet. + output_dir = os.path.split(output_path)[0] + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Write output file. + with codecs.open(output_path, 'w', writer_desc.encoding) as output_file: + output_file.write(output_data) + + +if '__main__' == __name__: + sys.exit(main()) diff --git a/chromium/components/policy/tools/template_writers/test_suite_all.py b/chromium/components/policy/tools/template_writers/test_suite_all.py new file mode 100755 index 00000000000..8ca43de4571 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/test_suite_all.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# Copyright 2017 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. +'''Unit test suite that collects template_writer tests.''' + +import os +import sys +import unittest + + +class TestSuiteAll(unittest.TestSuite): + + def __init__(self): + super(TestSuiteAll, self).__init__() + # Imports placed here to prevent circular imports. + # pylint: disable-msg=C6204 + import policy_template_generator_unittest + import writers.adm_writer_unittest + import writers.adml_writer_unittest + import writers.admx_writer_unittest + import writers.android_policy_writer_unittest + import writers.chromeos_adml_writer_unittest + import writers.chromeos_admx_writer_unittest + import writers.doc_writer_unittest + import writers.google_adml_writer_unittest + import writers.google_admx_writer_unittest + import writers.ios_app_config_writer_unittest + import writers.jamf_writer_unittest + import writers.json_writer_unittest + import writers.plist_strings_writer_unittest + import writers.plist_writer_unittest + import writers.reg_writer_unittest + import writers.template_writer_unittest + import writers.xml_writer_base_unittest + + test_classes = [ + policy_template_generator_unittest.PolicyTemplateGeneratorUnittest, + writers.adm_writer_unittest.AdmWriterUnittest, + writers.adml_writer_unittest.AdmlWriterUnittest, + writers.admx_writer_unittest.AdmxWriterUnittest, + writers.android_policy_writer_unittest.AndroidPolicyWriterUnittest, + writers.chromeos_adml_writer_unittest.ChromeOsAdmlWriterUnittest, + writers.chromeos_admx_writer_unittest.ChromeOsAdmxWriterUnittest, + writers.doc_writer_unittest.DocWriterUnittest, + writers.google_adml_writer_unittest.GoogleAdmlWriterUnittest, + writers.google_admx_writer_unittest.GoogleAdmxWriterUnittest, + writers.ios_app_config_writer_unittest.IOSAppConfigWriterUnitTests, + writers.jamf_writer_unittest.JamfWriterUnitTests, + writers.json_writer_unittest.JsonWriterUnittest, + writers.plist_strings_writer_unittest.PListStringsWriterUnittest, + writers.plist_writer_unittest.PListWriterUnittest, + writers.reg_writer_unittest.RegWriterUnittest, + writers.template_writer_unittest.TemplateWriterUnittests, + writers.xml_writer_base_unittest.XmlWriterBaseTest, + # add test classes here, in alphabetical order... + ] + + for test_class in test_classes: + self.addTest(unittest.makeSuite(test_class)) + + +if __name__ == '__main__': + test_result = unittest.TextTestRunner(verbosity=2).run(TestSuiteAll()) + sys.exit(len(test_result.errors) + len(test_result.failures)) diff --git a/chromium/components/policy/tools/template_writers/writer_configuration.py b/chromium/components/policy/tools/template_writers/writer_configuration.py new file mode 100755 index 00000000000..43cb02dde07 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writer_configuration.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# 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. + + +def GetConfigurationForBuild(defines): + '''Returns a configuration dictionary for the given build that contains + build-specific settings and information. + + Args: + defines: Definitions coming from the build system. + + Raises: + Exception: If 'defines' contains an unknown build-type. + ''' + # The prefix of key names in config determines which writer will use their + # corresponding values: + # win: Both ADM and ADMX. + # mac: Only plist. + # admx: Only ADMX. + # adm: Only ADM. + # none/other: Used by all the writers. + # Google:Cat_Google references the external google.admx file. + # category_path_strings strings in curly braces are looked up from localized + # 'messages' in policy_templates.json. + if '_chromium' in defines: + config = { + 'build': 'chromium', + 'app_name': 'Chromium', + 'doc_url': 'https://chromeenterprise.google/policies/', + 'frame_name': 'Chromium Frame', + 'os_name': 'ChromiumOS', + 'webview_name': 'Chromium WebView', + 'win_config': { + 'win': { + 'reg_mandatory_key_name': 'Software\\Policies\\Chromium', + 'reg_recommended_key_name': + 'Software\\Policies\\Chromium\\Recommended', + 'mandatory_category_path': ['chromium'], + 'recommended_category_path': ['chromium_recommended'], + 'category_path_strings': { + 'chromium': 'Chromium', + 'chromium_recommended': 'Chromium - {doc_recommended}', + }, + 'namespace': 'Chromium.Policies.Chromium', + }, + 'chrome_os': { + 'reg_mandatory_key_name': 'Software\\Policies\\ChromiumOS', + 'reg_recommended_key_name': + 'Software\\Policies\\ChromiumOS\\Recommended', + 'mandatory_category_path': ['chromium_os'], + 'recommended_category_path': ['chromium_os_recommended'], + 'category_path_strings': { + 'chromium_os': 'ChromiumOS', + 'chromium_os_recommended': 'ChromiumOS - {doc_recommended}', + }, + 'namespace': 'Chromium.Policies.ChromiumOS' + }, + }, + 'admx_prefix': 'chromium', + 'linux_policy_path': '/etc/chromium/policies/', + 'bundle_id': 'org.chromium', + } + elif '_google_chrome' in defines: + config = { + 'build': 'chrome', + 'app_name': 'Google Chrome', + 'doc_url': 'https://chromeenterprise.google/policies/', + 'frame_name': 'Google Chrome Frame', + 'os_name': 'Google ChromeOS', + 'webview_name': 'Android System WebView', + 'win_config': { + 'win': { + 'reg_mandatory_key_name': + 'Software\\Policies\\Google\\Chrome', + 'reg_recommended_key_name': + 'Software\\Policies\\Google\\Chrome\\Recommended', + 'mandatory_category_path': + ['Google:Cat_Google', 'googlechrome'], + 'recommended_category_path': + ['Google:Cat_Google', 'googlechrome_recommended'], + 'category_path_strings': { + 'googlechrome': 'Google Chrome', + 'googlechrome_recommended': + 'Google Chrome - {doc_recommended}' + }, + 'namespace': + 'Google.Policies.Chrome', + }, + 'chrome_os': { + 'reg_mandatory_key_name': + 'Software\\Policies\\Google\\ChromeOS', + 'reg_recommended_key_name': + 'Software\\Policies\\Google\\ChromeOS\\Recommended', + 'mandatory_category_path': + ['Google:Cat_Google', 'googlechromeos'], + 'recommended_category_path': + ['Google:Cat_Google', 'googlechromeos_recommended'], + 'category_path_strings': { + 'googlechromeos': + 'Google ChromeOS', + 'googlechromeos_recommended': + 'Google ChromeOS - {doc_recommended}' + }, + 'namespace': + 'Google.Policies.ChromeOS', + }, + }, + # The string 'Google' is defined in google.adml for ADMX, but ADM + # doesn't support external references, so we define this map here. + 'adm_category_path_strings': { + 'Google:Cat_Google': 'Google' + }, + 'admx_prefix': 'chrome', + 'admx_using_namespaces': { + 'Google': 'Google.Policies' # prefix: namespace + }, + 'linux_policy_path': '/etc/opt/chrome/policies/', + 'bundle_id': 'com.google.chrome.ios', + } + else: + raise Exception('Unknown build') + if 'version' in defines: + config['version'] = defines['version'] + if 'major_version' in defines: + config['major_version'] = defines['major_version'] + config['win_supported_os'] = 'SUPPORTED_WIN7' + config['win_supported_os_win7'] = 'SUPPORTED_WIN7_ONLY' + if 'mac_bundle_id' in defines: + config['mac_bundle_id'] = defines['mac_bundle_id'] + config['android_webview_restriction_prefix'] = 'com.android.browser:' + return config diff --git a/chromium/components/policy/tools/template_writers/writers/__init__.py b/chromium/components/policy/tools/template_writers/writers/__init__.py new file mode 100755 index 00000000000..fb050d1f24f --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +# 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. +'''Module template_writers.writers +''' + +pass diff --git a/chromium/components/policy/tools/template_writers/writers/adm_writer.py b/chromium/components/policy/tools/template_writers/writers/adm_writer.py new file mode 100755 index 00000000000..7c20a5a8e78 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/adm_writer.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +# 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. + +from writers import gpo_editor_writer +import re + +NEWLINE = '\r\n' +POLICY_LIST_URL = '''https://cloud.google.com/docs/chrome-enterprise/policies/?policy=''' + + +def GetWriter(config): + '''Factory method for creating AdmWriter objects. + See the constructor of TemplateWriter for description of + arguments. + ''' + return AdmWriter(['win', 'win7'], config) + + +class IndentedStringBuilder: + '''Utility class for building text with indented lines.''' + + def __init__(self): + self.lines = [] + self.indent = '' + + def AddLine(self, string='', indent_diff=0): + '''Appends a string with indentation and a linebreak to |self.lines|. + + Args: + string: The string to print. + indent_diff: the difference of indentation of the printed line, + compared to the next/previous printed line. Increment occurs + after printing the line, while decrement occurs before that. + ''' + indent_diff *= 2 + if indent_diff < 0: + self.indent = self.indent[(-indent_diff):] + if string != '': + self.lines.append(self.indent + string) + else: + self.lines.append('') + if indent_diff > 0: + self.indent += ''.ljust(indent_diff) + + def AddLines(self, other): + '''Appends the content of another |IndentedStringBuilder| to |self.lines|. + Indentation of the added lines will be the sum of |self.indent| and + their original indentation. + + Args: + other: The buffer from which lines are copied. + ''' + for line in other.lines: + self.AddLine(line) + + def ToString(self): + '''Returns |self.lines| as text string.''' + return NEWLINE.join(self.lines) + + +class AdmWriter(gpo_editor_writer.GpoEditorWriter): + '''Class for generating policy templates in Windows ADM format. + It is used by PolicyTemplateGenerator to write ADM files. + ''' + + TYPE_TO_INPUT = { + 'string': 'EDITTEXT', + 'int': 'NUMERIC', + 'string-enum': 'DROPDOWNLIST', + 'int-enum': 'DROPDOWNLIST', + 'list': 'LISTBOX', + 'string-enum-list': 'LISTBOX', + 'dict': 'EDITTEXT', + 'external': 'EDITTEXT' + } + + def _Escape(self, string): + return string.replace('.', '_') + + def _AddGuiString(self, name, value): + # The |name| must be escaped. + assert name == self._Escape(name) + # Escape newlines in the value. + value = value.replace('\n', '\\n') + if name in self.strings_seen: + err = ('%s was added as "%s" and now added again as "%s"' % + (name, self.strings_seen[name], value)) + assert value == self.strings_seen[name], err + else: + self.strings_seen[name] = value + line = '%s="%s"' % (name, value) + self.strings.AddLine(line) + + def _WriteSupported(self, builder, is_win7_only): + builder.AddLine('#if version >= 4', 1) + key = 'win_supported_os_win7' if is_win7_only else 'win_supported_os' + supported_on_text = self.config[key] + builder.AddLine('SUPPORTED !!' + supported_on_text) + builder.AddLine('#endif', -1) + + def _WritePart(self, policy, key_name, builder): + '''Writes the PART ... END PART section of a policy. + + Args: + policy: The policy to write to the output. + key_name: The registry key backing the policy. + builder: Builder to append lines to. + ''' + policy_part_name = self._Escape(policy['name'] + '_Part') + self._AddGuiString(policy_part_name, policy['label']) + + # Print the PART ... END PART section: + builder.AddLine() + adm_type = self.TYPE_TO_INPUT[policy['type']] + builder.AddLine('PART !!%s %s' % (policy_part_name, adm_type), 1) + if policy['type'] in ('list', 'string-enum-list'): + # Note that the following line causes FullArmor ADMX Migrator to create + # corrupt ADMX files. Please use admx_writer to get ADMX files. + builder.AddLine('KEYNAME "%s\\%s"' % (key_name, policy['name'])) + builder.AddLine('VALUEPREFIX ""') + else: + builder.AddLine('VALUENAME "%s"' % policy['name']) + if policy['type'] == 'int': + # The default max for NUMERIC values is 9999 which is too small for us. + max = 2000000000 + min = 0 + if self.PolicyHasRestrictions(policy): + schema = policy['schema'] + if 'minimum' in schema: + min = schema['minimum'] + if 'maximum' in schema: + max = schema['maximum'] + builder.AddLine('MIN %d MAX %d' % (min, max)) + if policy['type'] in ('string', 'dict', 'external'): + # The default max for EDITTEXT values is 1023, which is too small for + # big JSON blobs and other string policies. + builder.AddLine('MAXLEN 1000000') + if policy['type'] in ('int-enum', 'string-enum'): + builder.AddLine('ITEMLIST', 1) + for item in policy['items']: + if policy['type'] == 'int-enum': + value_text = 'NUMERIC ' + str(item['value']) + else: + value_text = '"' + item['value'] + '"' + string_id = self._Escape(policy['name'] + '_' + item['name'] + + '_DropDown') + builder.AddLine('NAME !!%s VALUE %s' % (string_id, value_text)) + self._AddGuiString(string_id, item['caption']) + builder.AddLine('END ITEMLIST', -1) + builder.AddLine('END PART', -1) + + def PolicyHasRestrictions(self, policy): + if 'schema' in policy: + return any(keyword in policy['schema'] \ + for keyword in ['minimum', 'maximum']) + return False + + def _WritePolicy(self, policy, key_name, builder): + policy_name = self._Escape(policy['name'] + '_Policy') + self._AddGuiString(policy_name, policy['caption']) + builder.AddLine('POLICY !!%s' % policy_name, 1) + self._WriteSupported(builder, self.IsPolicyOnWin7Only(policy)) + policy_explain_name = self._Escape(policy['name'] + '_Explain') + policy_explain = self._GetPolicyExplanation(policy) + self._AddGuiString(policy_explain_name, policy_explain) + builder.AddLine('EXPLAIN !!' + policy_explain_name) + + if policy['type'] == 'main': + builder.AddLine('VALUENAME "%s"' % policy['name']) + builder.AddLine('VALUEON NUMERIC 1') + builder.AddLine('VALUEOFF NUMERIC 0') + else: + self._WritePart(policy, key_name, builder) + + builder.AddLine('END POLICY', -1) + builder.AddLine() + + def _GetPolicyExplanation(self, policy): + '''Returns the explanation for a given policy. + Includes a link to the relevant documentation on chromium.org. + ''' + policy_desc = policy.get('desc') + reference_url = POLICY_LIST_URL + policy['name'] + reference_link_text = self.GetLocalizedMessage('reference_link') + reference_link_text = reference_link_text.replace('$6', reference_url) + + if policy_desc is not None: + policy_desc += '\n\n' + if (not policy.get('deprecated', False) and + not self._IsRemovedPolicy(policy)): + policy_desc += reference_link_text + return policy_desc + else: + return reference_link_text + + def WriteComment(self, comment): + self.lines.AddLine('; ' + comment) + + def WritePolicy(self, policy): + if self.CanBeMandatory(policy): + self._WritePolicy(policy, self.winconfig['reg_mandatory_key_name'], + self.policies) + + def WriteRecommendedPolicy(self, policy): + self._WritePolicy(policy, self.winconfig['reg_recommended_key_name'], + self.recommended_policies) + + def BeginPolicyGroup(self, group): + category_name = self._Escape(group['name'] + '_Category') + self._AddGuiString(category_name, group['caption']) + self.policies.AddLine('CATEGORY !!' + category_name, 1) + + def EndPolicyGroup(self): + self.policies.AddLine('END CATEGORY', -1) + self.policies.AddLine('') + + def BeginRecommendedPolicyGroup(self, group): + category_name = self._Escape(group['name'] + '_Category') + self._AddGuiString(category_name, group['caption']) + self.recommended_policies.AddLine('CATEGORY !!' + category_name, 1) + + def EndRecommendedPolicyGroup(self): + self.recommended_policies.AddLine('END CATEGORY', -1) + self.recommended_policies.AddLine('') + + def _CreateTemplate(self, category_path, key_name, policies): + '''Creates the whole ADM template except for the [Strings] section, and + returns it as an |IndentedStringBuilder|. + + Args: + category_path: List of strings representing the category path. + key_name: Main registry key backing the policies. + policies: ADM code for all the policies in an |IndentedStringBuilder|. + ''' + lines = IndentedStringBuilder() + for part in category_path: + lines.AddLine('CATEGORY !!' + part, 1) + lines.AddLine('KEYNAME "%s"' % key_name) + lines.AddLine() + + lines.AddLines(policies) + + for part in category_path: + lines.AddLine('END CATEGORY', -1) + lines.AddLine() + + return lines + + def BeginTemplate(self): + if self._GetChromiumVersionString() is not None: + self.WriteComment(self.config['build'] + ' version: ' + \ + self._GetChromiumVersionString()) + self._AddGuiString(self.config['win_supported_os'], + self.messages['win_supported_all']['text']) + self._AddGuiString(self.config['win_supported_os_win7'], + self.messages['win_supported_win7']['text']) + categories = self.winconfig['mandatory_category_path'] + \ + self.winconfig['recommended_category_path'] + strings = self.winconfig['category_path_strings'].copy() + if 'adm_category_path_strings' in self.config: + strings.update(self.config['adm_category_path_strings']) + for category in categories: + if (category in strings): + # Replace {...} by localized messages. + string = re.sub(r"\{(\w+)\}", \ + lambda m: self.messages[m.group(1)]['text'], \ + strings[category]) + self._AddGuiString(category, string) + # All the policies will be written into self.policies. + # The final template text will be assembled into self.lines by + # self.EndTemplate(). + + def EndTemplate(self): + # Copy policies into self.lines. + policy_class = self.GetClass().upper() + for class_name in ['MACHINE', 'USER']: + if policy_class != 'BOTH' and policy_class != class_name: + continue + self.lines.AddLine('CLASS ' + class_name, 1) + self.lines.AddLines( + self._CreateTemplate(self.winconfig['mandatory_category_path'], + self.winconfig['reg_mandatory_key_name'], + self.policies)) + self.lines.AddLines( + self._CreateTemplate(self.winconfig['recommended_category_path'], + self.winconfig['reg_recommended_key_name'], + self.recommended_policies)) + self.lines.AddLine('', -1) + # Copy user strings into self.lines. + self.lines.AddLine('[Strings]') + self.lines.AddLines(self.strings) + + def Init(self): + # String buffer for building the whole ADM file. + self.lines = IndentedStringBuilder() + # String buffer for building the strings section of the ADM file. + self.strings = IndentedStringBuilder() + # Map of strings seen, to avoid duplicates. + self.strings_seen = {} + # String buffer for building the policies of the ADM file. + self.policies = IndentedStringBuilder() + # String buffer for building the recommended policies of the ADM file. + self.recommended_policies = IndentedStringBuilder() + # Shortcut to platform-specific ADMX/ADM specific configuration. + assert len(self.platforms) == 2 + self.winconfig = self.config['win_config'][self.platforms[0]] + + def GetTemplateText(self): + return self.lines.ToString() + + def GetClass(self): + return 'Both' diff --git a/chromium/components/policy/tools/template_writers/writers/adm_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/adm_writer_unittest.py new file mode 100755 index 00000000000..9d2b86d9e3a --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/adm_writer_unittest.py @@ -0,0 +1,1470 @@ +#!/usr/bin/env python3 +# 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. +'''Unit tests for writers.adm_writer''' + +import os +import sys +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +import unittest + +from writers import writer_unittest_common + +MESSAGES = ''' + { + 'win_supported_all': { + 'text': 'Microsoft Windows 7 or later', 'desc': 'blah' + }, + 'win_supported_win7': { + 'text': 'Microsoft Windows 7', 'desc': 'blah' + }, + + 'doc_recommended': { + 'text': 'Recommended', 'desc': 'bleh' + }, + 'doc_reference_link': { + 'text': 'Reference: $6', 'desc': 'bleh' + }, + 'deprecated_policy_group_caption': { + 'text': 'Deprecated policies', 'desc': 'bleh' + }, + 'deprecated_policy_group_desc': { + 'desc': 'bleh', + 'text': 'These policies are included here to make them easy to remove.' + }, + 'deprecated_policy_desc': { + 'desc': 'bleh', + 'text': 'This policy is deprecated. blah blah blah' + }, + 'removed_policy_group_caption': { + 'text': 'Removed policies', 'desc': 'bleh' + }, + 'removed_policy_group_desc': { + 'desc': 'bleh', + 'text': 'These policies are included here to make them easy to remove.' + }, + 'removed_policy_desc': { + 'desc': 'bleh', + 'text': 'This policy is removed. blah blah blah' + }, + }''' + + +class AdmWriterUnittest(writer_unittest_common.WriterUnittestCommon): + '''Unit tests for AdmWriter.''' + + def ConstructOutput(self, classes, body, strings): + result = [] + for clazz in classes: + result.append('CLASS ' + clazz) + result.append(body) + result.append(strings) + return ''.join(result) + + def CompareOutputs(self, output, expected_output): + '''Compares the output of the adm_writer with its expected output. + + Args: + output: The output of the adm writer. + expected_output: The expected output. + + Raises: + AssertionError: if the two strings are not equivalent. + ''' + self.assertEquals(output.strip(), + expected_output.strip().replace('\n', '\r\n')) + + def testEmpty(self): + # Test PListWriter in case of empty polices. + policy_json = ''' + { + 'policy_definitions': [], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, { + '_chromium': '1', + }, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended"''') + self.CompareOutputs(output, expected_output) + + def testVersionAnnotation(self): + # Test PListWriter in case of empty polices. + policy_json = ''' + { + 'policy_definitions': [], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'version': '39.0.0.0' + }, 'adm') + expected_output = '; chromium version: 39.0.0.0\n' + \ + self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended"''') + self.CompareOutputs(output, expected_output) + + def testMainPolicy(self): + # Tests a policy group with a single policy of type 'main'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'MainPolicy', + 'type': 'main', + 'supported_on': ['chrome.win:8-'], + 'features': { 'can_be_recommended': True }, + 'caption': 'Caption of main.', + 'desc': 'Description of main.', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!Google:Cat_Google + CATEGORY !!googlechrome + KEYNAME "Software\\Policies\\Google\\Chrome" + + POLICY !!MainPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!MainPolicy_Explain + VALUENAME "MainPolicy" + VALUEON NUMERIC 1 + VALUEOFF NUMERIC 0 + END POLICY + + END CATEGORY + END CATEGORY + + CATEGORY !!Google:Cat_Google + CATEGORY !!googlechrome_recommended + KEYNAME "Software\\Policies\\Google\\Chrome\\Recommended" + + POLICY !!MainPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!MainPolicy_Explain + VALUENAME "MainPolicy" + VALUEON NUMERIC 1 + VALUEOFF NUMERIC 0 + END POLICY + + END CATEGORY + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +Google:Cat_Google="Google" +googlechrome="Google Chrome" +googlechrome_recommended="Google Chrome - Recommended" +MainPolicy_Policy="Caption of main." +MainPolicy_Explain="Description of main.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=MainPolicy"''') + self.CompareOutputs(output, expected_output) + + def testMainPolicyRecommendedOnly(self): + # Tests a policy group with a single policy of type 'main'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'MainPolicy', + 'type': 'main', + 'supported_on': ['chrome.win:8-'], + 'features': { + 'can_be_recommended': True, + 'can_be_mandatory': False + }, + 'caption': 'Caption of main.', + 'desc': 'Description of main.', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!Google:Cat_Google + CATEGORY !!googlechrome + KEYNAME "Software\\Policies\\Google\\Chrome" + + END CATEGORY + END CATEGORY + + CATEGORY !!Google:Cat_Google + CATEGORY !!googlechrome_recommended + KEYNAME "Software\\Policies\\Google\\Chrome\\Recommended" + + POLICY !!MainPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!MainPolicy_Explain + VALUENAME "MainPolicy" + VALUEON NUMERIC 1 + VALUEOFF NUMERIC 0 + END POLICY + + END CATEGORY + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +Google:Cat_Google="Google" +googlechrome="Google Chrome" +googlechrome_recommended="Google Chrome - Recommended" +MainPolicy_Policy="Caption of main." +MainPolicy_Explain="Description of main.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=MainPolicy"''') + self.CompareOutputs(output, expected_output) + + def testStringPolicy(self): + # Tests a policy group with a single policy of type 'string'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'StringPolicy', + 'type': 'string', + 'supported_on': ['chrome.win:8-'], + 'features': { 'can_be_recommended': True }, + 'desc': """Description of group. +With a newline.""", + 'caption': 'Caption of policy.', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + POLICY !!StringPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!StringPolicy_Explain + + PART !!StringPolicy_Part EDITTEXT + VALUENAME "StringPolicy" + MAXLEN 1000000 + END PART + END POLICY + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + POLICY !!StringPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!StringPolicy_Explain + + PART !!StringPolicy_Part EDITTEXT + VALUENAME "StringPolicy" + MAXLEN 1000000 + END PART + END POLICY + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended" +StringPolicy_Policy="Caption of policy." +StringPolicy_Explain="Description of group.\\nWith a newline.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=StringPolicy" +StringPolicy_Part="Caption of policy." +''') + self.CompareOutputs(output, expected_output) + + def testIntPolicy(self): + # Tests a policy group with a single policy of type 'int'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'IntPolicy', + 'type': 'int', + 'caption': 'Caption of policy.', + 'features': { 'can_be_recommended': True }, + 'desc': 'Description of policy.', + 'supported_on': ['chrome.win:8-'] + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + POLICY !!IntPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!IntPolicy_Explain + + PART !!IntPolicy_Part NUMERIC + VALUENAME "IntPolicy" + MIN 0 MAX 2000000000 + END PART + END POLICY + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + POLICY !!IntPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!IntPolicy_Explain + + PART !!IntPolicy_Part NUMERIC + VALUENAME "IntPolicy" + MIN 0 MAX 2000000000 + END PART + END POLICY + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended" +IntPolicy_Policy="Caption of policy." +IntPolicy_Explain="Description of policy.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=IntPolicy" +IntPolicy_Part="Caption of policy." +''') + self.CompareOutputs(output, expected_output) + + def testIntPolicyWithWin7(self): + # Tests a policy group with a single policy of type 'int' that is supported + # on Windows 7 only. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'IntPolicy', + 'type': 'int', + 'caption': 'Caption of policy.', + 'features': { 'can_be_recommended': True }, + 'desc': 'Description of policy.', + 'supported_on': ['chrome.win7:8-'], + }, + ], + 'placeholders': [], + 'policy_atomic_group_definitions': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + POLICY !!IntPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7_ONLY + #endif + EXPLAIN !!IntPolicy_Explain + + PART !!IntPolicy_Part NUMERIC + VALUENAME "IntPolicy" + MIN 0 MAX 2000000000 + END PART + END POLICY + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + POLICY !!IntPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7_ONLY + #endif + EXPLAIN !!IntPolicy_Explain + + PART !!IntPolicy_Part NUMERIC + VALUENAME "IntPolicy" + MIN 0 MAX 2000000000 + END PART + END POLICY + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended" +IntPolicy_Policy="Caption of policy." +IntPolicy_Explain="Description of policy.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=IntPolicy" +IntPolicy_Part="Caption of policy." +''') + self.CompareOutputs(output, expected_output) + + def testIntPolicyWithRange(self): + # Tests a policy group with a single policy of type 'int' with a min and + # max value. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'IntPolicy', + 'type': 'int', + 'schema': { 'type': 'integer', 'minimum': 5, 'maximum': 10 }, + 'caption': 'Caption of policy.', + 'features': { 'can_be_recommended': True }, + 'desc': 'Description of policy.', + 'supported_on': ['chrome.win:8-'] + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + POLICY !!IntPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!IntPolicy_Explain + + PART !!IntPolicy_Part NUMERIC + VALUENAME "IntPolicy" + MIN 5 MAX 10 + END PART + END POLICY + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + POLICY !!IntPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!IntPolicy_Explain + + PART !!IntPolicy_Part NUMERIC + VALUENAME "IntPolicy" + MIN 5 MAX 10 + END PART + END POLICY + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended" +IntPolicy_Policy="Caption of policy." +IntPolicy_Explain="Description of policy.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=IntPolicy" +IntPolicy_Part="Caption of policy." +''') + self.CompareOutputs(output, expected_output) + + def testIntEnumPolicy(self): + # Tests a policy group with a single policy of type 'int-enum'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'EnumPolicy', + 'type': 'int-enum', + 'items': [ + { + 'name': 'ProxyServerDisabled', + 'value': 0, + 'caption': 'Option1', + }, + { + 'name': 'ProxyServerAutoDetect', + 'value': 1, + 'caption': 'Option2', + }, + ], + 'desc': 'Description of policy.', + 'caption': 'Caption of policy.', + 'supported_on': ['chrome.win:8-'], + 'features': { 'can_be_recommended': True }, + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!Google:Cat_Google + CATEGORY !!googlechrome + KEYNAME "Software\\Policies\\Google\\Chrome" + + POLICY !!EnumPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!EnumPolicy_Explain + + PART !!EnumPolicy_Part DROPDOWNLIST + VALUENAME "EnumPolicy" + ITEMLIST + NAME !!EnumPolicy_ProxyServerDisabled_DropDown VALUE NUMERIC 0 + NAME !!EnumPolicy_ProxyServerAutoDetect_DropDown VALUE NUMERIC 1 + END ITEMLIST + END PART + END POLICY + + END CATEGORY + END CATEGORY + + CATEGORY !!Google:Cat_Google + CATEGORY !!googlechrome_recommended + KEYNAME "Software\\Policies\\Google\\Chrome\\Recommended" + + POLICY !!EnumPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!EnumPolicy_Explain + + PART !!EnumPolicy_Part DROPDOWNLIST + VALUENAME "EnumPolicy" + ITEMLIST + NAME !!EnumPolicy_ProxyServerDisabled_DropDown VALUE NUMERIC 0 + NAME !!EnumPolicy_ProxyServerAutoDetect_DropDown VALUE NUMERIC 1 + END ITEMLIST + END PART + END POLICY + + END CATEGORY + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +Google:Cat_Google="Google" +googlechrome="Google Chrome" +googlechrome_recommended="Google Chrome - Recommended" +EnumPolicy_Policy="Caption of policy." +EnumPolicy_Explain="Description of policy.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=EnumPolicy" +EnumPolicy_Part="Caption of policy." +EnumPolicy_ProxyServerDisabled_DropDown="Option1" +EnumPolicy_ProxyServerAutoDetect_DropDown="Option2" +''') + self.CompareOutputs(output, expected_output) + + def testStringEnumPolicy(self): + # Tests a policy group with a single policy of type 'int-enum'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'EnumPolicy', + 'type': 'string-enum', + 'caption': 'Caption of policy.', + 'desc': 'Description of policy.', + 'items': [ + {'name': 'ProxyServerDisabled', 'value': 'one', + 'caption': 'Option1'}, + {'name': 'ProxyServerAutoDetect', 'value': 'two', + 'caption': 'Option2'}, + ], + 'supported_on': ['chrome.win:8-'], + 'features': { 'can_be_recommended': True }, + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!Google:Cat_Google + CATEGORY !!googlechrome + KEYNAME "Software\\Policies\\Google\\Chrome" + + POLICY !!EnumPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!EnumPolicy_Explain + + PART !!EnumPolicy_Part DROPDOWNLIST + VALUENAME "EnumPolicy" + ITEMLIST + NAME !!EnumPolicy_ProxyServerDisabled_DropDown VALUE "one" + NAME !!EnumPolicy_ProxyServerAutoDetect_DropDown VALUE "two" + END ITEMLIST + END PART + END POLICY + + END CATEGORY + END CATEGORY + + CATEGORY !!Google:Cat_Google + CATEGORY !!googlechrome_recommended + KEYNAME "Software\\Policies\\Google\\Chrome\\Recommended" + + POLICY !!EnumPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!EnumPolicy_Explain + + PART !!EnumPolicy_Part DROPDOWNLIST + VALUENAME "EnumPolicy" + ITEMLIST + NAME !!EnumPolicy_ProxyServerDisabled_DropDown VALUE "one" + NAME !!EnumPolicy_ProxyServerAutoDetect_DropDown VALUE "two" + END ITEMLIST + END PART + END POLICY + + END CATEGORY + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +Google:Cat_Google="Google" +googlechrome="Google Chrome" +googlechrome_recommended="Google Chrome - Recommended" +EnumPolicy_Policy="Caption of policy." +EnumPolicy_Explain="Description of policy.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=EnumPolicy" +EnumPolicy_Part="Caption of policy." +EnumPolicy_ProxyServerDisabled_DropDown="Option1" +EnumPolicy_ProxyServerAutoDetect_DropDown="Option2" +''') + self.CompareOutputs(output, expected_output) + + def testListPolicy(self): + # Tests a policy group with a single policy of type 'list'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'ListPolicy', + 'type': 'list', + 'supported_on': ['chrome.win:8-'], + 'features': { 'can_be_recommended': True }, + 'desc': """Description of list policy. +With a newline.""", + 'caption': 'Caption of list policy.', + 'label': 'Label of list policy.' + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s, + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + POLICY !!ListPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!ListPolicy_Explain + + PART !!ListPolicy_Part LISTBOX + KEYNAME "Software\\Policies\\Chromium\\ListPolicy" + VALUEPREFIX "" + END PART + END POLICY + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + POLICY !!ListPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!ListPolicy_Explain + + PART !!ListPolicy_Part LISTBOX + KEYNAME "Software\\Policies\\Chromium\\Recommended\\ListPolicy" + VALUEPREFIX "" + END PART + END POLICY + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended" +ListPolicy_Policy="Caption of list policy." +ListPolicy_Explain="Description of list policy.\\nWith a newline.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ListPolicy" +ListPolicy_Part="Label of list policy." +''') + self.CompareOutputs(output, expected_output) + + def testStringEnumListPolicy(self): + # Tests a policy group with a single policy of type 'string-enum-list'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'ListPolicy', + 'type': 'string-enum-list', + 'supported_on': ['chrome.win:8-'], + 'features': { 'can_be_recommended': True }, + 'desc': """Description of list policy. +With a newline.""", + 'items': [ + {'name': 'ProxyServerDisabled', 'value': 'one', + 'caption': 'Option1'}, + {'name': 'ProxyServerAutoDetect', 'value': 'two', + 'caption': 'Option2'}, + ], + 'caption': 'Caption of list policy.', + 'label': 'Label of list policy.' + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + POLICY !!ListPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!ListPolicy_Explain + + PART !!ListPolicy_Part LISTBOX + KEYNAME "Software\\Policies\\Chromium\\ListPolicy" + VALUEPREFIX "" + END PART + END POLICY + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + POLICY !!ListPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!ListPolicy_Explain + + PART !!ListPolicy_Part LISTBOX + KEYNAME "Software\\Policies\\Chromium\\Recommended\\ListPolicy" + VALUEPREFIX "" + END PART + END POLICY + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended" +ListPolicy_Policy="Caption of list policy." +ListPolicy_Explain="Description of list policy.\\nWith a newline.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ListPolicy" +ListPolicy_Part="Label of list policy." +''') + self.CompareOutputs(output, expected_output) + + def testDictionaryPolicy(self): + # Tests a policy group with a single policy of type 'dict'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'DictionaryPolicy', + 'type': 'dict', + 'supported_on': ['chrome.win:8-'], + 'features': { 'can_be_recommended': True }, + 'desc': 'Description of group.', + 'caption': 'Caption of policy.', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + POLICY !!DictionaryPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!DictionaryPolicy_Explain + + PART !!DictionaryPolicy_Part EDITTEXT + VALUENAME "DictionaryPolicy" + MAXLEN 1000000 + END PART + END POLICY + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + POLICY !!DictionaryPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!DictionaryPolicy_Explain + + PART !!DictionaryPolicy_Part EDITTEXT + VALUENAME "DictionaryPolicy" + MAXLEN 1000000 + END PART + END POLICY + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended" +DictionaryPolicy_Policy="Caption of policy." +DictionaryPolicy_Explain="Description of group.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=DictionaryPolicy" +DictionaryPolicy_Part="Caption of policy." +''') + self.CompareOutputs(output, expected_output) + + def testExternalPolicy(self): + # Tests a policy group with a single policy of type 'external'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'ExternalPolicy', + 'type': 'external', + 'supported_on': ['chrome.win:8-'], + 'features': { 'can_be_recommended': True }, + 'desc': 'Description of group.', + 'caption': 'Caption of policy.', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + POLICY !!ExternalPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!ExternalPolicy_Explain + + PART !!ExternalPolicy_Part EDITTEXT + VALUENAME "ExternalPolicy" + MAXLEN 1000000 + END PART + END POLICY + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + POLICY !!ExternalPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!ExternalPolicy_Explain + + PART !!ExternalPolicy_Part EDITTEXT + VALUENAME "ExternalPolicy" + MAXLEN 1000000 + END PART + END POLICY + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended" +ExternalPolicy_Policy="Caption of policy." +ExternalPolicy_Explain="Description of group.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ExternalPolicy" +ExternalPolicy_Part="Caption of policy." +''') + self.CompareOutputs(output, expected_output) + + def testNonSupportedPolicy(self): + # Tests a policy that is not supported on Windows, so it shouldn't + # be included in the ADM file. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'NonWinGroup', + 'type': 'group', + 'policies': ['NonWinPolicy'], + 'caption': 'Group caption.', + 'desc': 'Group description.', + }, + { + 'name': 'NonWinPolicy', + 'type': 'list', + 'supported_on': ['chrome.linux:8-', 'chrome.mac:8-'], + 'caption': 'Caption of list policy.', + 'desc': 'Desc of list policy.', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended" +''') + self.CompareOutputs(output, expected_output) + + def testNonRecommendedPolicy(self): + # Tests a policy that is not recommended, so it should be included. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'MainPolicy', + 'type': 'main', + 'supported_on': ['chrome.win:8-'], + 'caption': 'Caption of main.', + 'desc': 'Description of main.', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!Google:Cat_Google + CATEGORY !!googlechrome + KEYNAME "Software\\Policies\\Google\\Chrome" + + POLICY !!MainPolicy_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!MainPolicy_Explain + VALUENAME "MainPolicy" + VALUEON NUMERIC 1 + VALUEOFF NUMERIC 0 + END POLICY + + END CATEGORY + END CATEGORY + + CATEGORY !!Google:Cat_Google + CATEGORY !!googlechrome_recommended + KEYNAME "Software\\Policies\\Google\\Chrome\\Recommended" + + END CATEGORY + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +Google:Cat_Google="Google" +googlechrome="Google Chrome" +googlechrome_recommended="Google Chrome - Recommended" +MainPolicy_Policy="Caption of main." +MainPolicy_Explain="Description of main.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=MainPolicy"''') + self.CompareOutputs(output, expected_output) + + def testPolicyGroup(self): + # Tests a policy group that has more than one policies. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'Group1', + 'type': 'group', + 'desc': 'Description of group.', + 'caption': 'Caption of group.', + 'policies': ['Policy1', 'Policy2'], + }, + { + 'name': 'Policy1', + 'type': 'list', + 'supported_on': ['chrome.win:8-'], + 'features': { 'can_be_recommended': True }, + 'caption': 'Caption of policy1.', + 'desc': """Description of policy1. +With a newline.""" + }, + { + 'name': 'Policy2', + 'type': 'string', + 'supported_on': ['chrome.win:8-'], + 'caption': 'Caption of policy2.', + 'desc': """Description of policy2. +With a newline.""" + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + CATEGORY !!Group1_Category + POLICY !!Policy1_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!Policy1_Explain + + PART !!Policy1_Part LISTBOX + KEYNAME "Software\\Policies\\Chromium\\Policy1" + VALUEPREFIX "" + END PART + END POLICY + + POLICY !!Policy2_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!Policy2_Explain + + PART !!Policy2_Part EDITTEXT + VALUENAME "Policy2" + MAXLEN 1000000 + END PART + END POLICY + + END CATEGORY + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + CATEGORY !!Group1_Category + POLICY !!Policy1_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!Policy1_Explain + + PART !!Policy1_Part LISTBOX + KEYNAME "Software\\Policies\\Chromium\\Recommended\\Policy1" + VALUEPREFIX "" + END PART + END POLICY + + END CATEGORY + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended" +Group1_Category="Caption of group." +Policy1_Policy="Caption of policy1." +Policy1_Explain="Description of policy1.\\nWith a newline.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=Policy1" +Policy1_Part="Caption of policy1." +Policy2_Policy="Caption of policy2." +Policy2_Explain="Description of policy2.\\nWith a newline.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=Policy2" +Policy2_Part="Caption of policy2." +''') + self.CompareOutputs(output, expected_output) + + def testDuplicatedStringEnumPolicy(self): + # Verifies that duplicated enum constants with different descriptions are + # allowed. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'EnumPolicy.A', + 'type': 'string-enum', + 'caption': 'Caption of policy A.', + 'desc': 'Description of policy A.', + 'items': [ + {'name': 'tls1.2', 'value': 'tls1.2', 'caption': 'tls1.2' }, + ], + 'supported_on': ['chrome.win:39-'], + }, + { + 'name': 'EnumPolicy.B', + 'type': 'string-enum', + 'caption': 'Caption of policy B.', + 'desc': 'Description of policy B.', + 'items': [ + {'name': 'tls1.2', 'value': 'tls1.2', 'caption': 'tls1.2' }, + ], + 'supported_on': ['chrome.win:39-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!Google:Cat_Google + CATEGORY !!googlechrome + KEYNAME "Software\\Policies\\Google\\Chrome" + + POLICY !!EnumPolicy_A_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!EnumPolicy_A_Explain + + PART !!EnumPolicy_A_Part DROPDOWNLIST + VALUENAME "EnumPolicy.A" + ITEMLIST + NAME !!EnumPolicy_A_tls1_2_DropDown VALUE "tls1.2" + END ITEMLIST + END PART + END POLICY + + POLICY !!EnumPolicy_B_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!EnumPolicy_B_Explain + + PART !!EnumPolicy_B_Part DROPDOWNLIST + VALUENAME "EnumPolicy.B" + ITEMLIST + NAME !!EnumPolicy_B_tls1_2_DropDown VALUE "tls1.2" + END ITEMLIST + END PART + END POLICY + + END CATEGORY + END CATEGORY + + CATEGORY !!Google:Cat_Google + CATEGORY !!googlechrome_recommended + KEYNAME "Software\\Policies\\Google\\Chrome\\Recommended" + + END CATEGORY + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +Google:Cat_Google="Google" +googlechrome="Google Chrome" +googlechrome_recommended="Google Chrome - Recommended" +EnumPolicy_A_Policy="Caption of policy A." +EnumPolicy_A_Explain="Description of policy A.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=EnumPolicy.A" +EnumPolicy_A_Part="Caption of policy A." +EnumPolicy_A_tls1_2_DropDown="tls1.2" +EnumPolicy_B_Policy="Caption of policy B." +EnumPolicy_B_Explain="Description of policy B.\\n\\n\ +Reference: \ +https://cloud.google.com/docs/chrome-enterprise/policies/?policy=EnumPolicy.B" +EnumPolicy_B_Part="Caption of policy B." +EnumPolicy_B_tls1_2_DropDown="tls1.2" +''') + self.CompareOutputs(output, expected_output) + + def testDeprecatedPolicy(self): + # Tests that a deprecated policy gets placed in the special + # 'DeprecatedPolicies' group. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'Policy1', + 'type': 'string', + 'deprecated': True, + 'features': { 'can_be_recommended': True }, + 'supported_on': ['chrome.win:8-'], + 'caption': 'Caption of policy1.', + 'desc': """Description of policy1.""" + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + CATEGORY !!DeprecatedPolicies_Category + POLICY !!Policy1_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!Policy1_Explain + + PART !!Policy1_Part EDITTEXT + VALUENAME "Policy1" + MAXLEN 1000000 + END PART + END POLICY + + END CATEGORY + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + CATEGORY !!DeprecatedPolicies_Category + POLICY !!Policy1_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!Policy1_Explain + + PART !!Policy1_Part EDITTEXT + VALUENAME "Policy1" + MAXLEN 1000000 + END PART + END POLICY + + END CATEGORY + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended" +DeprecatedPolicies_Category="Deprecated policies" +Policy1_Policy="Caption of policy1." +Policy1_Explain="This policy is deprecated. blah blah blah\\n\\n" +Policy1_Part="Caption of policy1." +''') + self.CompareOutputs(output, expected_output) + + def testRemovedPolicy(self): + # Tests that a deprecated policy gets placed in the special + # 'RemovedPolicies' group. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'Policy1', + 'type': 'string', + 'deprecated': True, + 'features': { 'can_be_recommended': True }, + 'supported_on': ['chrome.win:40-83'], + 'caption': 'Caption of policy1.', + 'desc': """Description of policy1.""" + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': %s + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1', + 'major_version': 84}, 'adm') + expected_output = self.ConstructOutput(['MACHINE', 'USER'], ''' + CATEGORY !!chromium + KEYNAME "Software\\Policies\\Chromium" + + CATEGORY !!RemovedPolicies_Category + POLICY !!Policy1_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!Policy1_Explain + + PART !!Policy1_Part EDITTEXT + VALUENAME "Policy1" + MAXLEN 1000000 + END PART + END POLICY + + END CATEGORY + + END CATEGORY + + CATEGORY !!chromium_recommended + KEYNAME "Software\\Policies\\Chromium\\Recommended" + + CATEGORY !!RemovedPolicies_Category + POLICY !!Policy1_Policy + #if version >= 4 + SUPPORTED !!SUPPORTED_WIN7 + #endif + EXPLAIN !!Policy1_Explain + + PART !!Policy1_Part EDITTEXT + VALUENAME "Policy1" + MAXLEN 1000000 + END PART + END POLICY + + END CATEGORY + + END CATEGORY + + +''', '''[Strings] +SUPPORTED_WIN7="Microsoft Windows 7 or later" +SUPPORTED_WIN7_ONLY="Microsoft Windows 7" +chromium="Chromium" +chromium_recommended="Chromium - Recommended" +RemovedPolicies_Category="Removed policies" +Policy1_Policy="Caption of policy1." +Policy1_Explain="This policy is removed. blah blah blah\\n\\n" +Policy1_Part="Caption of policy1." +''') + self.CompareOutputs(output, expected_output) + + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/adml_writer.py b/chromium/components/policy/tools/template_writers/writers/adml_writer.py new file mode 100755 index 00000000000..6f7233a0366 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/adml_writer.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +# 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. + +from xml.dom import minidom +from writers import gpo_editor_writer, xml_formatted_writer +from writers.admx_writer import AdmxElementType +import json +import re + + +def GetWriter(config): + '''Factory method for instanciating the ADMLWriter. Every Writer needs a + GetWriter method because the TemplateFormatter uses this method to + instantiate a Writer. + ''' + return ADMLWriter(['win', 'win7'], config) + + +class ADMLWriter(xml_formatted_writer.XMLFormattedWriter, + gpo_editor_writer.GpoEditorWriter): + ''' Class for generating an ADML policy template. It is used by the + PolicyTemplateGenerator to write the ADML file. + ''' + + # DOM root node of the generated ADML document. + _doc = None + + # The string-table contains all ADML "string" elements. + _string_table_elem = None + + # The presentation-table is the container for presentation elements, that + # describe the presentation of Policy-Groups and Policies. + _presentation_table_elem = None + + def _AddString(self, id, text): + ''' Adds an ADML "string" element to _string_table_elem. The following + ADML snippet contains an example: + + <string id="$(id)">$(text)</string> + + Args: + id: ID of the newly created "string" element. + text: Value of the newly created "string" element. + ''' + id = id.replace('.', '_') + if id in self.strings_seen: + assert text == self.strings_seen[id] + else: + self.strings_seen[id] = text + string_elem = self.AddElement(self._string_table_elem, 'string', + {'id': id}) + string_elem.appendChild(self._doc.createTextNode(text)) + + def _GetAdmxElementType(self, policy): + '''Returns the ADMX element type for a particular Policy.''' + return AdmxElementType.GetType(policy, allow_multi_strings=False) + + def WritePolicy(self, policy): + '''Generates the ADML elements for a Policy. + <stringTable> + ... + <string id="$(policy_group_name)">$(caption)</string> + <string id="$(policy_group_name)_Explain">$(description)</string> + </stringTable> + + <presentationTables> + ... + <presentation id=$(policy_group_name)/> + </presentationTables> + + Args: + policy: The Policy to generate ADML elements for. + ''' + policy_name = policy['name'] + policy_caption = policy.get('caption', policy_name) + policy_label = policy.get('label', policy_name) + + policy_desc = policy.get('desc') + example_value_text = self._GetExampleValueText(policy) + + if policy_desc is not None and self.HasExpandedPolicyDescription(policy): + policy_desc += '\n' + self.GetExpandedPolicyDescription(policy) + '\n' + + if (policy_desc is not None and example_value_text is not None and + not self._IsRemovedPolicy(policy)): + policy_explain = policy_desc + '\n\n' + example_value_text + elif policy_desc is not None: + policy_explain = policy_desc + elif example_value_text is not None: + policy_explain = example_value_text + else: + # No explanation found at all. + policy_explain = policy_name + + self._AddString(policy_name, policy_caption) + self._AddString(policy_name + '_Explain', policy_explain) + presentation_elem = self.AddElement(self._presentation_table_elem, + 'presentation', {'id': policy_name}) + + admx_element_type = self._GetAdmxElementType(policy) + if admx_element_type == AdmxElementType.MAIN: + pass + elif admx_element_type == AdmxElementType.STRING: + textbox_elem = self.AddElement(presentation_elem, 'textBox', + {'refId': policy_name}) + label_elem = self.AddElement(textbox_elem, 'label') + label_elem.appendChild(self._doc.createTextNode(policy_label)) + elif admx_element_type == AdmxElementType.MULTI_STRING: + # We currently also show a single-line textbox - see http://crbug/829328 + textbox_elem = self.AddElement(presentation_elem, 'textBox', + {'refId': policy_name + '_Legacy'}) + label_elem = self.AddElement(textbox_elem, 'label') + legacy_label = self._GetLegacySingleLineLabel(policy_label) + self._AddString(policy_name + '_Legacy', legacy_label) + label_elem.appendChild(self._doc.createTextNode(legacy_label)) + # New multi-line textbox, easier to use than old single-line textbox: + multitextbox_elem = self.AddElement(presentation_elem, 'multiTextBox', { + 'refId': policy_name, + 'defaultHeight': '8' + }) + multitextbox_elem.appendChild(self._doc.createTextNode(policy_label)) + elif admx_element_type == AdmxElementType.INT: + textbox_elem = self.AddElement(presentation_elem, 'decimalTextBox', + {'refId': policy_name}) + textbox_elem.appendChild(self._doc.createTextNode(policy_label + ':')) + elif admx_element_type == AdmxElementType.ENUM: + for item in policy['items']: + self._AddString(policy_name + "_" + item['name'], item['caption']) + dropdownlist_elem = self.AddElement(presentation_elem, 'dropdownList', + {'refId': policy_name}) + dropdownlist_elem.appendChild(self._doc.createTextNode(policy_label)) + elif admx_element_type == AdmxElementType.LIST: + self._AddString(policy_name + 'Desc', policy_caption) + listbox_elem = self.AddElement(presentation_elem, 'listBox', + {'refId': policy_name + 'Desc'}) + listbox_elem.appendChild(self._doc.createTextNode(policy_label)) + elif admx_element_type == AdmxElementType.GROUP: + pass + else: + raise Exception('Unknown element type %s.' % admx_element_type) + + def BeginPolicyGroup(self, group): + '''Generates ADML elements for a Policy-Group. For each Policy-Group two + ADML "string" elements are added to the string-table. One contains the + caption of the Policy-Group and the other a description. A Policy-Group also + requires an ADML "presentation" element that must be added to the + presentation-table. The "presentation" element is the container for the + elements that define the visual presentation of the Policy-Goup's Policies. + The following ADML snippet shows an example: + + Args: + group: The Policy-Group to generate ADML elements for. + ''' + # Add ADML "string" elements to the string-table that are required by a + # Policy-Group. + self._AddString(group['name'] + '_group', group['caption']) + + def _AddBaseStrings(self): + ''' Adds ADML "string" elements to the string-table that are referenced by + the ADMX file but not related to any specific Policy-Group or Policy. + ''' + self._AddString(self.config['win_supported_os'], + self.messages['win_supported_all']['text']) + self._AddString(self.config['win_supported_os_win7'], + self.messages['win_supported_win7']['text']) + categories = self.winconfig['mandatory_category_path'] + \ + self.winconfig['recommended_category_path'] + strings = self.winconfig['category_path_strings'] + for category in categories: + if (category in strings): + # Replace {...} by localized messages. + string = re.sub(r"\{(\w+)\}", \ + lambda m: self.messages[m.group(1)]['text'], \ + strings[category]) + self._AddString(category, string) + + def _GetExampleValueText(self, policy): + '''Generates a string that describes the example value, if needed. + Returns None if no string is needed. For instance, if the setting is a + boolean, the user can only select true or false, so example text is not + useful.''' + example_value = policy.get('example_value') + # If there is no example_value, we show nothing. + if not example_value: + return None + + # Strings are simple - just return them as-is, on the same line. + if isinstance(example_value, str): + return self.GetLocalizedMessage('example_value') + ' ' + example_value + + # Dicts are pretty simple - json.dumps them onto multiple lines. + if isinstance(example_value, dict): + value_as_text = json.dumps(example_value, indent=2) + return self.GetLocalizedMessage('example_value') + '\n\n' + value_as_text + + # Lists are the more complicated - the example value we show the user + # depends on if they need to enter the list into a textbox (using JSON + # array syntax) or into a listbox (which doesn't need JSON array syntax, + # but does need exactly one entry per line). + if isinstance(example_value, list): + policy_type = policy.get('type') + if policy_type == 'dict': + # If the policy type is dict, that means they get to enter in the + # whole policy as JSON, including the JSON array square brackets: + value_as_text = json.dumps(example_value, indent=2) + + elif policy_type is not None and 'list' in policy_type: + # But if the policy type is list, then they get to enter each item + # into a listbox, one item per line. + if isinstance(example_value[0], str): + # Items are strings. These don't need quotes when in a listbox. + value_as_text = '\n'.join([str(v) for v in example_value]) + else: + # Items are dicts. We dump each item onto a single line, since the + # user has to enter one item per line into the listbox. + value_as_text = '\n'.join([json.dumps(v) for v in example_value]) + + else: + # Lists should be type 'dict', 'list', or something like '...enum-list' + raise Exception( + 'Unexpected policy type with list example value: %s' % policy_type) + + return self.GetLocalizedMessage('example_value') + '\n\n' + value_as_text + + # Other types - mostly booleans - we don't show example values. + return None + + def _GetLegacySingleLineLabel(self, policy_label): + '''Generates a label for a legacy single-line textbox.''' + return (self.GetLocalizedMessage('legacy_single_line_label').replace( + '$6', policy_label)) + + def BeginTemplate(self): + dom_impl = minidom.getDOMImplementation('') + self._doc = dom_impl.createDocument(None, 'policyDefinitionResources', None) + if self._GetChromiumVersionString() is not None: + self.AddComment(self._doc.documentElement, self.config['build'] + \ + ' version: ' + self._GetChromiumVersionString()) + policy_definitions_resources_elem = self._doc.documentElement + policy_definitions_resources_elem.attributes['revision'] = '1.0' + policy_definitions_resources_elem.attributes['schemaVersion'] = '1.0' + + self.AddElement(policy_definitions_resources_elem, 'displayName') + self.AddElement(policy_definitions_resources_elem, 'description') + resources_elem = self.AddElement(policy_definitions_resources_elem, + 'resources') + self._string_table_elem = self.AddElement(resources_elem, 'stringTable') + self._AddBaseStrings() + self._presentation_table_elem = self.AddElement(resources_elem, + 'presentationTable') + + def Init(self): + # Map of all strings seen. + self.strings_seen = {} + # Shortcut to platform-specific ADMX/ADM specific configuration. + assert len(self.platforms) <= 2 + self.winconfig = self.config['win_config'][self.platforms[0]] + + def GetTemplateText(self): + # Using "toprettyxml()" confuses the Windows Group Policy Editor + # (gpedit.msc) because it interprets whitespace characters in text between + # the "string" tags. This prevents gpedit.msc from displaying the category + # names correctly. + return self._doc.toxml() diff --git a/chromium/components/policy/tools/template_writers/writers/adml_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/adml_writer_unittest.py new file mode 100755 index 00000000000..2f1afd14502 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/adml_writer_unittest.py @@ -0,0 +1,521 @@ +#!/usr/bin/env python3 +# 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. +"""Unittests for writers.adml_writer.""" + +import os +import sys +import unittest +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +from writers import adml_writer +from writers import xml_writer_base_unittest + + +class AdmlWriterUnittest(xml_writer_base_unittest.XmlWriterBaseTest): + + def setUp(self): + config = { + 'app_name': 'test', + 'build': 'test', + 'win_supported_os': 'SUPPORTED_TESTOS', + 'win_supported_os_win7': 'SUPPORTED_TESTOS_2', + 'win_config': { + 'win': { + 'mandatory_category_path': ['test_category'], + 'recommended_category_path': ['test_category_recommended'], + 'category_path_strings': { + 'test_category': 'TestCategory', + 'test_category_recommended': 'TestCategory - recommended', + }, + }, + 'chrome_os': { + 'mandatory_category_path': ['cros_test_category'], + 'recommended_category_path': ['cros_test_category_recommended'], + 'category_path_strings': { + 'cros_test_category': + 'CrOSTestCategory', + 'cros_test_category_recommended': + 'CrOSTestCategory - recommended', + }, + }, + }, + } + self.writer = self._GetWriter(config) + self.writer.messages = { + 'win_supported_all': { + 'text': 'Supported on Test OS or higher', + 'desc': 'blah' + }, + 'win_supported_win7': { + 'text': 'Supported on Test OS', + 'desc': 'blah' + }, + 'doc_recommended': { + 'text': 'Recommended', + 'desc': 'bleh' + }, + 'doc_example_value': { + 'text': 'Example value:', + 'desc': 'bluh' + }, + 'doc_legacy_single_line_label': { + 'text': '$6 (deprecated)', + }, + 'doc_schema_description_link': { + 'text': '''See $6''' + }, + } + self.writer.Init() + + def _GetWriter(self, config): + return adml_writer.GetWriter(config) + + def GetCategory(self): + return "test_category" + + def GetCategoryString(self): + return "TestCategory" + + def _InitWriterForAddingPolicyGroups(self, writer): + '''Initialize the writer for adding policy groups. This method must be + called before the method "BeginPolicyGroup" can be called. It initializes + attributes of the writer. + ''' + writer.BeginTemplate() + + def _InitWriterForAddingPolicies(self, writer, policy): + '''Initialize the writer for adding policies. This method must be + called before the method "WritePolicy" can be called. It initializes + attributes of the writer. + ''' + self._InitWriterForAddingPolicyGroups(writer) + policy_group = { + 'name': 'PolicyGroup', + 'caption': 'Test Caption', + 'desc': 'This is the test description of the test policy group.', + 'policies': policy, + } + writer.BeginPolicyGroup(policy_group) + + string_elements = \ + self.writer._string_table_elem.getElementsByTagName('string') + for elem in string_elements: + self.writer._string_table_elem.removeChild(elem) + + def testEmpty(self): + self.writer.BeginTemplate() + self.writer.EndTemplate() + output = self.writer.GetTemplateText() + expected_output = ( + '<?xml version="1.0" ?><policyDefinitionResources' + ' revision="1.0" schemaVersion="1.0"><displayName/><description/>' + '<resources><stringTable><string id="SUPPORTED_TESTOS">Supported on' + ' Test OS or higher</string>' + '<string id="SUPPORTED_TESTOS_2">Supported on Test OS</string>' + '<string id="' + self.GetCategory() + '">' + \ + self.GetCategoryString() + '</string>' + '<string id="' + self.GetCategory() + '_recommended">' + \ + self.GetCategoryString() + ' - recommended</string>' + '</stringTable><presentationTable/>' + '</resources></policyDefinitionResources>') + self.AssertXMLEquals(output, expected_output) + + def testVersionAnnotation(self): + self.writer.config['version'] = '39.0.0.0' + self.writer.BeginTemplate() + self.writer.EndTemplate() + output = self.writer.GetTemplateText() + expected_output = ( + '<?xml version="1.0" ?><policyDefinitionResources' + ' revision="1.0" schemaVersion="1.0"><!--test version: 39.0.0.0-->' + '<displayName/><description/><resources><stringTable>' + '<string id="SUPPORTED_TESTOS">Supported on' + ' Test OS or higher</string>' + '<string id="SUPPORTED_TESTOS_2">Supported on Test OS</string>' + '<string id="' + self.GetCategory() + '">' + \ + self.GetCategoryString() + '</string>' + '<string id="' + self.GetCategory() + '_recommended">' + \ + self.GetCategoryString() + ' - recommended</string>' + '</stringTable><presentationTable/>' + '</resources></policyDefinitionResources>') + self.AssertXMLEquals(output, expected_output) + + def testPolicyGroup(self): + empty_policy_group = { + 'name': + 'PolicyGroup', + 'caption': + 'Test Group Caption', + 'desc': + 'This is the test description of the test policy group.', + 'policies': [ + { + 'name': 'PolicyStub2', + 'type': 'main' + }, + { + 'name': 'PolicyStub1', + 'type': 'main' + }, + ], + } + self._InitWriterForAddingPolicyGroups(self.writer) + self.writer.BeginPolicyGroup(empty_policy_group) + self.writer.EndPolicyGroup() + # Assert generated string elements. + output = self.GetXMLOfChildren(self.writer._string_table_elem) + expected_output = ( + '<string id="SUPPORTED_TESTOS">' + 'Supported on Test OS or higher</string>\n' + \ + '<string id="SUPPORTED_TESTOS_2">Supported on Test OS</string>\n' + \ + '<string id="' + self.GetCategory() + '">' + \ + self.GetCategoryString() + '</string>\n' + '<string id="' + self.GetCategory() + '_recommended">' + \ + self.GetCategoryString() + ' - recommended</string>\n' + '<string id="PolicyGroup_group">Test Group Caption</string>') + self.AssertXMLEquals(output, expected_output) + # Assert generated presentation elements. + output = self.GetXMLOfChildren(self.writer._presentation_table_elem) + expected_output = '' + self.AssertXMLEquals(output, expected_output) + + def testMainPolicy(self): + main_policy = { + 'name': 'DummyMainPolicy', + 'type': 'main', + 'caption': 'Main policy caption', + 'desc': 'Main policy test description.' + } + self._InitWriterForAddingPolicies(self.writer, main_policy) + self.writer.WritePolicy(main_policy) + # Assert generated string elements. + output = self.GetXMLOfChildren(self.writer._string_table_elem) + expected_output = ( + '<string id="DummyMainPolicy">Main policy caption</string>\n' + '<string id="DummyMainPolicy_Explain">' + 'Main policy test description.</string>') + self.AssertXMLEquals(output, expected_output) + # Assert generated presentation elements. + output = self.GetXMLOfChildren(self.writer._presentation_table_elem) + expected_output = '<presentation id="DummyMainPolicy"/>' + self.AssertXMLEquals(output, expected_output) + + def testStringPolicy(self): + string_policy = { + 'name': 'StringPolicyStub', + 'type': 'string', + 'caption': 'String policy caption', + 'label': 'String policy label', + 'desc': 'This is a test description.', + 'supported_on': [{'platform': 'win'}, {'platform': 'chrome_os'}], + 'example_value': '01:23:45:67:89:ab', + } + self._InitWriterForAddingPolicies(self.writer, string_policy) + self.writer.WritePolicy(string_policy) + # Assert generated string elements. + output = self.GetXMLOfChildren(self.writer._string_table_elem) + expected_output = ( + '<string id="StringPolicyStub">String policy caption</string>\n' + '<string id="StringPolicyStub_Explain">' + 'This is a test description.\n\n' + 'Example value: 01:23:45:67:89:ab</string>') + self.AssertXMLEquals(output, expected_output) + # Assert generated presentation elements. + output = self.GetXMLOfChildren(self.writer._presentation_table_elem) + expected_output = ('<presentation id="StringPolicyStub">\n' + ' <textBox refId="StringPolicyStub">\n' + ' <label>String policy label</label>\n' + ' </textBox>\n' + '</presentation>') + self.AssertXMLEquals(output, expected_output) + + def testIntPolicy(self): + int_policy = { + 'name': 'IntPolicyStub', + 'type': 'int', + 'caption': 'Int policy caption', + 'label': 'Int policy label', + 'desc': 'This is a test description.', + } + self._InitWriterForAddingPolicies(self.writer, int_policy) + self.writer.WritePolicy(int_policy) + # Assert generated string elements. + output = self.GetXMLOfChildren(self.writer._string_table_elem) + expected_output = ( + '<string id="IntPolicyStub">Int policy caption</string>\n' + '<string id="IntPolicyStub_Explain">' + 'This is a test description.</string>') + self.AssertXMLEquals(output, expected_output) + # Assert generated presentation elements. + output = self.GetXMLOfChildren(self.writer._presentation_table_elem) + expected_output = ('<presentation id="IntPolicyStub">\n' + ' <decimalTextBox refId="IntPolicyStub">' + 'Int policy label:</decimalTextBox>\n' + '</presentation>') + self.AssertXMLEquals(output, expected_output) + + def testIntEnumPolicy(self): + enum_policy = { + 'name': + 'EnumPolicyStub', + 'type': + 'int-enum', + 'caption': + 'Enum policy caption', + 'label': + 'Enum policy label', + 'desc': + 'This is a test description.', + 'items': [ + { + 'name': 'item 1', + 'value': 1, + 'caption': 'Caption Item 1', + }, + { + 'name': 'item 2', + 'value': 2, + 'caption': 'Caption Item 2', + }, + ], + } + self._InitWriterForAddingPolicies(self.writer, enum_policy) + self.writer.WritePolicy(enum_policy) + # Assert generated string elements. + output = self.GetXMLOfChildren(self.writer._string_table_elem) + expected_output = ( + '<string id="EnumPolicyStub">Enum policy caption</string>\n' + '<string id="EnumPolicyStub_Explain">' + 'This is a test description.</string>\n' + '<string id="EnumPolicyStub_item 1">Caption Item 1</string>\n' + '<string id="EnumPolicyStub_item 2">Caption Item 2</string>') + self.AssertXMLEquals(output, expected_output) + # Assert generated presentation elements. + output = self.GetXMLOfChildren(self.writer._presentation_table_elem) + expected_output = ('<presentation id="EnumPolicyStub">\n' + ' <dropdownList refId="EnumPolicyStub">' + 'Enum policy label</dropdownList>\n' + '</presentation>') + self.AssertXMLEquals(output, expected_output) + + def testStringEnumPolicy(self): + enum_policy = { + 'name': + 'EnumPolicyStub', + 'type': + 'string-enum', + 'caption': + 'Enum policy caption', + 'label': + 'Enum policy label', + 'desc': + 'This is a test description.', + 'items': [ + { + 'name': 'item 1', + 'value': 'value 1', + 'caption': 'Caption Item 1', + }, + { + 'name': 'item 2', + 'value': 'value 2', + 'caption': 'Caption Item 2', + }, + ], + } + self._InitWriterForAddingPolicies(self.writer, enum_policy) + self.writer.WritePolicy(enum_policy) + # Assert generated string elements. + output = self.GetXMLOfChildren(self.writer._string_table_elem) + expected_output = ( + '<string id="EnumPolicyStub">Enum policy caption</string>\n' + '<string id="EnumPolicyStub_Explain">' + 'This is a test description.</string>\n' + '<string id="EnumPolicyStub_item 1">Caption Item 1</string>\n' + '<string id="EnumPolicyStub_item 2">Caption Item 2</string>') + self.AssertXMLEquals(output, expected_output) + # Assert generated presentation elements. + output = self.GetXMLOfChildren(self.writer._presentation_table_elem) + expected_output = ('<presentation id="EnumPolicyStub">\n' + ' <dropdownList refId="EnumPolicyStub">' + 'Enum policy label</dropdownList>\n' + '</presentation>') + self.AssertXMLEquals(output, expected_output) + + def testListPolicy(self): + list_policy = { + 'name': 'ListPolicyStub', + 'type': 'list', + 'caption': 'List policy caption', + 'label': 'List policy label', + 'desc': 'This is a test description.', + } + self._InitWriterForAddingPolicies(self.writer, list_policy) + self.writer.WritePolicy(list_policy) + # Assert generated string elements. + output = self.GetXMLOfChildren(self.writer._string_table_elem) + expected_output = ( + '<string id="ListPolicyStub">List policy caption</string>\n' + '<string id="ListPolicyStub_Explain">' + 'This is a test description.</string>\n' + '<string id="ListPolicyStubDesc">List policy caption</string>') + self.AssertXMLEquals(output, expected_output) + # Assert generated presentation elements. + output = self.GetXMLOfChildren(self.writer._presentation_table_elem) + expected_output = ( + '<presentation id="ListPolicyStub">\n' + ' <listBox refId="ListPolicyStubDesc">List policy label</listBox>\n' + '</presentation>') + self.AssertXMLEquals(output, expected_output) + + def testStringEnumListPolicy(self): + list_policy = { + 'name': + 'ListPolicyStub', + 'type': + 'string-enum-list', + 'caption': + 'List policy caption', + 'label': + 'List policy label', + 'desc': + 'This is a test description.', + 'items': [ + { + 'name': 'item 1', + 'value': 'value 1', + 'caption': 'Caption Item 1', + }, + { + 'name': 'item 2', + 'value': 'value 2', + 'caption': 'Caption Item 2', + }, + ], + } + self._InitWriterForAddingPolicies(self.writer, list_policy) + self.writer.WritePolicy(list_policy) + # Assert generated string elements. + output = self.GetXMLOfChildren(self.writer._string_table_elem) + expected_output = ( + '<string id="ListPolicyStub">List policy caption</string>\n' + '<string id="ListPolicyStub_Explain">' + 'This is a test description.</string>\n' + '<string id="ListPolicyStubDesc">List policy caption</string>') + self.AssertXMLEquals(output, expected_output) + # Assert generated presentation elements. + output = self.GetXMLOfChildren(self.writer._presentation_table_elem) + expected_output = ( + '<presentation id="ListPolicyStub">\n' + ' <listBox refId="ListPolicyStubDesc">List policy label</listBox>\n' + '</presentation>') + self.AssertXMLEquals(output, expected_output) + + def testDictionaryPolicy(self, is_external=False): + dict_policy = { + 'name': 'DictionaryPolicyStub', + 'type': 'external' if is_external else 'dict', + 'caption': 'Dictionary policy caption', + 'label': 'Dictionary policy label', + 'desc': 'This is a test description.', + } + self._InitWriterForAddingPolicies(self.writer, dict_policy) + self.writer.WritePolicy(dict_policy) + # Assert generated string elements. + output = self.GetXMLOfChildren(self.writer._string_table_elem) + expected_output = ( + '<string id="DictionaryPolicyStub">Dictionary policy caption</string>\n' + '<string id="DictionaryPolicyStub_Explain">' + 'This is a test description.\n' + 'See https://cloud.google.com/docs/chrome-enterprise/policies/?policy=' + 'DictionaryPolicyStub\n</string>') + self.AssertXMLEquals(output, expected_output) + # Assert generated presentation elements. + output = self.GetXMLOfChildren(self.writer._presentation_table_elem) + expected_output = ('<presentation id="DictionaryPolicyStub">\n' + ' <textBox refId="DictionaryPolicyStub">\n' + ' <label>Dictionary policy label</label>\n' + ' </textBox>\n' + '</presentation>') + self.AssertXMLEquals(output, expected_output) + + def testExternalPolicy(self): + self.testDictionaryPolicy(is_external=True) + + def testPlatform(self): + # Test that the writer correctly chooses policies of platform Windows. + self.assertTrue( + self.writer.IsPolicySupported({ + 'supported_on': [{ + 'platform': 'win' + }, { + 'platform': 'aaa' + }] + })) + self.assertFalse( + self.writer.IsPolicySupported({ + 'supported_on': [{ + 'platform': 'mac', + }, { + 'platform': 'aaa' + }] + })) + + def testStringEncodings(self): + enum_policy_a = { + 'name': 'EnumPolicy.A', + 'type': 'string-enum', + 'caption': 'Enum policy A caption', + 'label': 'Enum policy A label', + 'desc': 'This is a test description.', + 'items': [{ + 'name': 'same_item', + 'value': '1', + 'caption': 'caption_a', + }], + } + enum_policy_b = { + 'name': 'EnumPolicy.B', + 'type': 'string-enum', + 'caption': 'Enum policy B caption', + 'label': 'Enum policy B label', + 'desc': 'This is a test description.', + 'items': [{ + 'name': 'same_item', + 'value': '2', + 'caption': 'caption_b', + }], + } + self._InitWriterForAddingPolicies(self.writer, enum_policy_a) + self.writer.WritePolicy(enum_policy_a) + self.writer.WritePolicy(enum_policy_b) + # Assert generated string elements. + output = self.GetXMLOfChildren(self.writer._string_table_elem) + expected_output = ( + '<string id="EnumPolicy_A">Enum policy A caption</string>\n' + '<string id="EnumPolicy_A_Explain">' + 'This is a test description.</string>\n' + '<string id="EnumPolicy_A_same_item">caption_a</string>\n' + '<string id="EnumPolicy_B">Enum policy B caption</string>\n' + '<string id="EnumPolicy_B_Explain">' + 'This is a test description.</string>\n' + '<string id="EnumPolicy_B_same_item">caption_b</string>\n') + self.AssertXMLEquals(output, expected_output) + # Assert generated presentation elements. + output = self.GetXMLOfChildren(self.writer._presentation_table_elem) + expected_output = ('<presentation id="EnumPolicy.A">\n' + ' <dropdownList refId="EnumPolicy.A">' + 'Enum policy A label</dropdownList>\n' + '</presentation>\n' + '<presentation id="EnumPolicy.B">\n' + ' <dropdownList refId="EnumPolicy.B">' + 'Enum policy B label</dropdownList>\n' + '</presentation>') + self.AssertXMLEquals(output, expected_output) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/admx_writer.py b/chromium/components/policy/tools/template_writers/writers/admx_writer.py new file mode 100755 index 00000000000..d6dbf4fbc99 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/admx_writer.py @@ -0,0 +1,500 @@ +#!/usr/bin/env python3 +# 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. + +from xml.dom import minidom +from writers import gpo_editor_writer, xml_formatted_writer + + +class AdmxElementType: + '''The different types of ADMX elements that can be used to display a policy. + This is related to the 'type' field in policy_templates.json, but there isn't + a perfect 1:1 mapping. This class is also used when writing ADML files, to + ensure that the ADML generated from policy_templates.json is compatible with + the ADMX generated from policy_templates.json""" + ''' + MAIN = 1 + STRING = 2 + MULTI_STRING = 3 + INT = 4 + ENUM = 5 + LIST = 6 + GROUP = 7 + + @staticmethod + def GetType(policy, allow_multi_strings=False): + '''Returns the ADMX element type that should be used for the given policy. + This logic is shared between the ADMX writer and the ADML writer, to ensure + that the ADMX and ADML generated from policy_tempates.json are compatible. + + Args: + policy: A dict describing the policy, as found in policy_templates.json. + allow_multi_strings: If true, the use of multi-line textbox elements is + allowed, so this function will sometimes return MULTI_STRING. If false + it falls back to single-line textboxes instead by returning STRING. + + Returns: + One of the enum values of AdmxElementType. + + Raises: + Exception: If policy['type'] is not recognized. + ''' + policy_type = policy['type'] + policy_example = policy.get('example_value') + + # TODO(olsen): Some policies are defined in policy_templates.json as type + # string, but the string is actually a JSON object. We should change the + # schema so they are 'dict' or similar, but until then, we use this + # heuristic to decide whether they are actually JSON and so could benefit + # from being displayed to the user as a multi-line string: + if (policy_type == 'string' and allow_multi_strings and + policy_example is not None and policy_example.strip().startswith('{')): + return AdmxElementType.MULTI_STRING + + admx_element_type = AdmxElementType._POLICY_TYPE_MAP.get(policy_type) + if admx_element_type is None: + raise Exception('Unknown policy type %s.' % policy_type) + + if (admx_element_type == AdmxElementType.MULTI_STRING and + not allow_multi_strings): + return AdmxElementType.STRING + + return admx_element_type + + +AdmxElementType._POLICY_TYPE_MAP = { + 'main': AdmxElementType.MAIN, + 'string': AdmxElementType.STRING, + 'dict': AdmxElementType.MULTI_STRING, + 'external': AdmxElementType.MULTI_STRING, + 'int': AdmxElementType.INT, + 'int-enum': AdmxElementType.ENUM, + 'string-enum': AdmxElementType.ENUM, + 'list': AdmxElementType.LIST, + 'string-enum-list': AdmxElementType.LIST, + 'group': AdmxElementType.GROUP +} + + +def GetWriter(config): + '''Factory method for instanciating the ADMXWriter. Every Writer needs a + GetWriter method because the TemplateFormatter uses this method to + instantiate a Writer. + ''' + return ADMXWriter(['win', 'win7'], config) + + +class ADMXWriter(xml_formatted_writer.XMLFormattedWriter, + gpo_editor_writer.GpoEditorWriter): + '''Class for generating an ADMX policy template. It is used by the + PolicyTemplateGenerator to write the admx file. + ''' + + # DOM root node of the generated ADMX document. + _doc = None + + # The ADMX "policies" element that contains the ADMX "policy" elements that + # are generated. + _active_policies_elem = None + + def Init(self): + # Shortcut to platform-specific ADMX/ADM specific configuration. + assert len(self.platforms) <= 2 + self.winconfig = self.config['win_config'][self.platforms[0]] + + def _AdmlString(self, name): + '''Creates a reference to the named string in an ADML file. + Args: + name: Name of the referenced ADML string. + ''' + name = name.replace('.', '_') + return '$(string.' + name + ')' + + def _AdmlStringExplain(self, name): + '''Creates a reference to the named explanation string in an ADML file. + Args: + name: Name of the referenced ADML explanation. + ''' + name = name.replace('.', '_') + return '$(string.' + name + '_Explain)' + + def _AdmlPresentation(self, name): + '''Creates a reference to the named presentation element in an ADML file. + Args: + name: Name of the referenced ADML presentation element. + ''' + return '$(presentation.' + name + ')' + + def _AddPolicyNamespaces(self, parent, prefix, namespace): + '''Generates the ADMX "policyNamespace" element and adds the elements to the + passed parent element. The namespace of the generated ADMX document is + define via the ADMX "target" element. Used namespaces are declared with an + ADMX "using" element. ADMX "target" and "using" elements are children of the + ADMX "policyNamespace" element. + + Args: + parent: The parent node to which all generated elements are added. + prefix: A logical name that can be used in the generated ADMX document to + refere to this namespace. + namespace: Namespace of the generated ADMX document. + ''' + policy_namespaces_elem = self.AddElement(parent, 'policyNamespaces') + attributes = { + 'prefix': prefix, + 'namespace': namespace, + } + self.AddElement(policy_namespaces_elem, 'target', attributes) + if 'admx_using_namespaces' in self.config: + prefix_namespace_map = self.config['admx_using_namespaces'] + for prefix in prefix_namespace_map: + attributes = { + 'prefix': prefix, + 'namespace': prefix_namespace_map[prefix], + } + self.AddElement(policy_namespaces_elem, 'using', attributes) + attributes = { + 'prefix': 'windows', + 'namespace': 'Microsoft.Policies.Windows', + } + self.AddElement(policy_namespaces_elem, 'using', attributes) + + def _AddCategory(self, parent, name, display_name, parent_category_name=None): + '''Adds an ADMX category element to the passed parent node. The following + snippet shows an example of a category element where "chromium" is the value + of the parameter name: + + <category displayName="$(string.chromium)" name="chromium"/> + + Each parent node can have only one category with a given name. Adding the + same category again with the same attributes is ignored, but adding it + again with different attributes is an error. + + Args: + parent: The parent node to which all generated elements are added. + name: Name of the category. + display_name: Display name of the category. + parent_category_name: Name of the parent category. Defaults to None. + ''' + existing = list( + filter(lambda e: e.getAttribute('name') == name, + parent.getElementsByTagName('category'))) + if existing: + assert len(existing) == 1 + assert existing[0].getAttribute('name') == name + assert existing[0].getAttribute('displayName') == display_name + return + attributes = { + 'name': name, + 'displayName': display_name, + } + category_elem = self.AddElement(parent, 'category', attributes) + if parent_category_name: + attributes = {'ref': parent_category_name} + self.AddElement(category_elem, 'parentCategory', attributes) + + def _AddCategories(self, categories): + '''Generates the ADMX "categories" element and adds it to the categories + main node. The "categories" element defines the category for the policies + defined in this ADMX document. Here is an example of an ADMX "categories" + element: + + <categories> + <category displayName="$(string.googlechrome)" name="googlechrome"> + <parentCategory ref="Google:Cat_Google"/> + </category> + </categories> + + Args: + categories_path: The categories path e.g. ['google', 'googlechrome']. For + each level in the path a "category" element will be generated, unless + the level contains a ':', in which case it is treated as external + references and no element is generated. Except for the root level, each + level refers to its parent. Since the root level category has no parent + it does not require a parent reference. + ''' + category_name = None + for category in categories: + parent_category_name = category_name + category_name = category + if (":" not in category_name): + self._AddCategory(self._categories_elem, category_name, + self._AdmlString(category_name), parent_category_name) + + def _AddSupportedOn(self, parent, supported_os_list): + '''Generates the "supportedOn" ADMX element and adds it to the passed + parent node. The "supportedOn" element contains information about supported + Windows OS versions. The following code snippet contains an example of a + "supportedOn" element: + + <supportedOn> + <definitions> + <definition name="$(supported_os)" + displayName="$(string.$(supported_os))"/> + </definitions> + ... + </supportedOn> + + Args: + parent: The parent element to which all generated elements are added. + supported_os: List with all supported Win OSes. + ''' + supported_on_elem = self.AddElement(parent, 'supportedOn') + definitions_elem = self.AddElement(supported_on_elem, 'definitions') + for supported_os in supported_os_list: + attributes = { + 'name': supported_os, + 'displayName': self._AdmlString(supported_os) + } + self.AddElement(definitions_elem, 'definition', attributes) + + def _AddStringPolicy(self, parent, name, id=None): + '''Generates ADMX elements for a String-Policy and adds them to the + passed parent node. + ''' + attributes = { + 'id': id or name, + 'valueName': name, + 'maxLength': '1000000', + } + self.AddElement(parent, 'text', attributes) + + def _AddMultiStringPolicy(self, parent, name): + '''Generates ADMX elements for a multi-line String-Policy and adds them to + the passed parent node. + ''' + # We currently also show a single-line textbox - see http://crbug/829328 + self._AddStringPolicy(parent, name, id=name + '_Legacy') + attributes = { + 'id': name, + 'valueName': name, + 'maxLength': '1000000', + } + self.AddElement(parent, 'multiText', attributes) + + def _AddIntPolicy(self, parent, policy): + '''Generates ADMX elements for an Int-Policy and adds them to the passed + parent node. + ''' + #default max value for an integer + max = 2000000000 + min = 0 + if self.PolicyHasRestrictions(policy): + schema = policy['schema'] + if 'minimum' in schema and schema['minimum'] >= 0: + min = schema['minimum'] + if 'maximum' in schema and schema['maximum'] >= 0: + max = schema['maximum'] + assert type(min) == int + assert type(max) == int + attributes = { + 'id': policy['name'], + 'valueName': policy['name'], + 'maxValue': str(max), + 'minValue': str(min), + } + self.AddElement(parent, 'decimal', attributes) + + def _AddEnumPolicy(self, parent, policy): + '''Generates ADMX elements for an Enum-Policy and adds them to the + passed parent element. + ''' + name = policy['name'] + items = policy['items'] + attributes = { + 'id': name, + 'valueName': name, + } + enum_elem = self.AddElement(parent, 'enum', attributes) + for item in items: + attributes = {'displayName': self._AdmlString(name + "_" + item['name'])} + item_elem = self.AddElement(enum_elem, 'item', attributes) + value_elem = self.AddElement(item_elem, 'value') + value_string = str(item['value']) + if policy['type'] == 'int-enum': + self.AddElement(value_elem, 'decimal', {'value': value_string}) + else: + self.AddElement(value_elem, 'string', {}, value_string) + + def _AddListPolicy(self, parent, key, name): + '''Generates ADMX XML elements for a List-Policy and adds them to the + passed parent element. + ''' + attributes = { + # The ID must be in sync with ID of the corresponding element in the + # ADML file. + 'id': name + 'Desc', + 'valuePrefix': '', + 'key': key + '\\' + name, + } + self.AddElement(parent, 'list', attributes) + + def _AddMainPolicy(self, parent): + '''Generates ADMX elements for a Main-Policy amd adds them to the + passed parent element. + ''' + enabled_value_elem = self.AddElement(parent, 'enabledValue') + self.AddElement(enabled_value_elem, 'decimal', {'value': '1'}) + disabled_value_elem = self.AddElement(parent, 'disabledValue') + self.AddElement(disabled_value_elem, 'decimal', {'value': '0'}) + + def PolicyHasRestrictions(self, policy): + if 'schema' in policy: + return any(keyword in policy['schema'] \ + for keyword in ['minimum', 'maximum']) + return False + + def _GetElements(self, policy_group_elem): + '''Returns the ADMX "elements" child from an ADMX "policy" element. If the + "policy" element has no "elements" child yet, a new child is created. + + Args: + policy_group_elem: The ADMX "policy" element from which the child element + "elements" is returned. + + Raises: + Exception: The policy_group_elem does not contain a ADMX "policy" element. + ''' + if policy_group_elem.tagName != 'policy': + raise Exception('Expected a "policy" element but got a "%s" element' % + policy_group_elem.tagName) + elements_list = policy_group_elem.getElementsByTagName('elements') + if len(elements_list) == 0: + return self.AddElement(policy_group_elem, 'elements') + elif len(elements_list) == 1: + return elements_list[0] + else: + raise Exception('There is supposed to be only one "elements" node but' + ' there are %s.' % str(len(elements_list))) + + def _GetAdmxElementType(self, policy): + '''Returns the ADMX element type for a particular Policy.''' + return AdmxElementType.GetType(policy, allow_multi_strings=False) + + def _WritePolicy(self, policy, name, key, parent): + '''Generates ADMX elements for a Policy.''' + policies_elem = self._active_policies_elem + policy_name = policy['name'] + attributes = { + 'name': name, + 'class': self.GetClass(policy), + 'displayName': self._AdmlString(policy_name), + 'explainText': self._AdmlStringExplain(policy_name), + 'presentation': self._AdmlPresentation(policy_name), + 'key': key, + } + is_win7_only = self.IsPolicyOnWin7Only(policy) + supported_key = ('win_supported_os_win7' + if is_win7_only else 'win_supported_os') + supported_on_text = self.config[supported_key] + + # Store the current "policy" AMDX element in self for later use by the + # WritePolicy method. + policy_elem = self.AddElement(policies_elem, 'policy', attributes) + self.AddElement(policy_elem, 'parentCategory', {'ref': parent}) + self.AddElement(policy_elem, 'supportedOn', {'ref': supported_on_text}) + + element_type = self._GetAdmxElementType(policy) + if element_type == AdmxElementType.MAIN: + self.AddAttribute(policy_elem, 'valueName', policy_name) + self._AddMainPolicy(policy_elem) + elif element_type == AdmxElementType.STRING: + parent = self._GetElements(policy_elem) + self._AddStringPolicy(parent, policy_name) + elif element_type == AdmxElementType.MULTI_STRING: + parent = self._GetElements(policy_elem) + self._AddMultiStringPolicy(parent, policy_name) + elif element_type == AdmxElementType.INT: + parent = self._GetElements(policy_elem) + self._AddIntPolicy(parent, policy) + elif element_type == AdmxElementType.ENUM: + parent = self._GetElements(policy_elem) + self._AddEnumPolicy(parent, policy) + elif element_type == AdmxElementType.LIST: + parent = self._GetElements(policy_elem) + self._AddListPolicy(parent, key, policy_name) + elif element_type == AdmxElementType.GROUP: + pass + else: + raise Exception('Unknown element type %s.' % element_type) + + def WritePolicy(self, policy): + if self.CanBeMandatory(policy): + self._WritePolicy(policy, policy['name'], + self.winconfig['reg_mandatory_key_name'], + self._active_mandatory_policy_group_name) + + def WriteRecommendedPolicy(self, policy): + self._WritePolicy(policy, policy['name'] + '_recommended', + self.winconfig['reg_recommended_key_name'], + self._active_recommended_policy_group_name) + + def _BeginPolicyGroup(self, group, name, parent): + '''Generates ADMX elements for a Policy-Group. + ''' + attributes = { + 'name': name, + 'displayName': self._AdmlString(group['name'] + '_group'), + } + category_elem = self.AddElement(self._categories_elem, 'category', + attributes) + attributes = {'ref': parent} + self.AddElement(category_elem, 'parentCategory', attributes) + + def BeginPolicyGroup(self, group): + self._BeginPolicyGroup(group, group['name'], + self.winconfig['mandatory_category_path'][-1]) + self._active_mandatory_policy_group_name = group['name'] + + def EndPolicyGroup(self): + self._active_mandatory_policy_group_name = \ + self.winconfig['mandatory_category_path'][-1] + + def BeginRecommendedPolicyGroup(self, group): + self._BeginPolicyGroup(group, group['name'] + '_recommended', + self.winconfig['recommended_category_path'][-1]) + self._active_recommended_policy_group_name = group['name'] + '_recommended' + + def EndRecommendedPolicyGroup(self): + self._active_recommended_policy_group_name = \ + self.winconfig['recommended_category_path'][-1] + + def BeginTemplate(self): + '''Generates the skeleton of the ADMX template. An ADMX template contains + an ADMX "PolicyDefinitions" element with four child nodes: "policies" + "policyNamspaces", "resources", "supportedOn" and "categories" + ''' + dom_impl = minidom.getDOMImplementation('') + self._doc = dom_impl.createDocument(None, 'policyDefinitions', None) + if self._GetChromiumVersionString() is not None: + self.AddComment(self._doc.documentElement, self.config['build'] + \ + ' version: ' + self._GetChromiumVersionString()) + policy_definitions_elem = self._doc.documentElement + + policy_definitions_elem.attributes['revision'] = '1.0' + policy_definitions_elem.attributes['schemaVersion'] = '1.0' + + self._AddPolicyNamespaces(policy_definitions_elem, + self.config['admx_prefix'], + self.winconfig['namespace']) + self.AddElement(policy_definitions_elem, 'resources', + {'minRequiredRevision': '1.0'}) + self._AddSupportedOn( + policy_definitions_elem, + [self.config['win_supported_os'], self.config['win_supported_os_win7']]) + self._categories_elem = self.AddElement(policy_definitions_elem, + 'categories') + self._AddCategories(self.winconfig['mandatory_category_path']) + self._AddCategories(self.winconfig['recommended_category_path']) + self._active_policies_elem = self.AddElement(policy_definitions_elem, + 'policies') + self._active_mandatory_policy_group_name = \ + self.winconfig['mandatory_category_path'][-1] + self._active_recommended_policy_group_name = \ + self.winconfig['recommended_category_path'][-1] + + def GetTemplateText(self): + return self.ToPrettyXml(self._doc) + + def GetClass(self, policy): + return 'Both' diff --git a/chromium/components/policy/tools/template_writers/writers/admx_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/admx_writer_unittest.py new file mode 100755 index 00000000000..1543a016bb9 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/admx_writer_unittest.py @@ -0,0 +1,700 @@ +#!/usr/bin/env python3 +# 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. +"""Unittests for writers.admx_writer.""" + +import os +import sys +import unittest +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +from writers import admx_writer +from writers import xml_writer_base_unittest +from xml.dom import minidom + + +class AdmxWriterUnittest(xml_writer_base_unittest.XmlWriterBaseTest): + + def _CreateDocumentElement(self): + dom_impl = minidom.getDOMImplementation('') + doc = dom_impl.createDocument(None, 'root', None) + return doc.documentElement + + def setUp(self): + # Writer configuration. This dictionary contains parameter used by the ADMX + # Writer + config = { + 'win_supported_os': 'SUPPORTED_TESTOS', + 'win_supported_os_win7': 'SUPPORTED_TESTOS_2', + 'win_config': { + 'win': { + 'reg_mandatory_key_name': + 'Software\\Policies\\Test', + 'reg_recommended_key_name': + 'Software\\Policies\\Test\\Recommended', + 'mandatory_category_path': ['test_category'], + 'recommended_category_path': ['test_recommended_category'], + 'category_path_strings': { + 'test_category': 'TestCategory', + 'test_recommended_category': 'TestCategory - recommended', + }, + 'namespace': + 'ADMXWriter.Test.Namespace', + }, + 'chrome_os': { + 'reg_mandatory_key_name': + 'Software\\Policies\\CrOSTest', + 'reg_recommended_key_name': + 'Software\\Policies\\CrOSTest\\Recommended', + 'mandatory_category_path': ['cros_test_category'], + 'recommended_category_path': ['cros_test_recommended_category'], + 'category_path_strings': { + 'cros_test_category': + 'CrOSTestCategory', + 'cros_test_recommended_category': + 'CrOSTestCategory - recommended', + }, + 'namespace': + 'ADMXWriter.Test.Namespace.ChromeOS', + }, + }, + 'admx_prefix': 'test_prefix', + 'build': 'test_product', + } + self.writer = self._GetWriter(config) + self.writer.Init() + + def _GetWriter(self, config): + return admx_writer.GetWriter(config) + + def _GetKey(self): + return "Test" + + def _GetCategory(self): + return "test_category" + + def _GetCategoryRec(self): + return "test_recommended_category" + + def _GetNamespace(self): + return "ADMXWriter.Test.Namespace" + + def _GetPoliciesElement(self, doc): + node_list = doc.getElementsByTagName('policies') + self.assertTrue(node_list.length == 1) + return node_list.item(0) + + def _GetCategoriesElement(self, doc): + node_list = doc.getElementsByTagName('categories') + self.assertTrue(node_list.length == 1) + return node_list.item(0) + + def testEmpty(self): + self.writer.BeginTemplate() + self.writer.EndTemplate() + + output = self.writer.GetTemplateText() + expected_output = ( + '<?xml version="1.0" ?>\n' + '<policyDefinitions revision="1.0" schemaVersion="1.0">\n' + ' <policyNamespaces>\n' + ' <target namespace="' + self._GetNamespace() + '"' + ' prefix="test_prefix"/>\n' + ' <using namespace="Microsoft.Policies.Windows" prefix="windows"/>\n' + ' </policyNamespaces>\n' + ' <resources minRequiredRevision="1.0"/>\n' + ' <supportedOn>\n' + ' <definitions>\n' + ' <definition displayName="' + '$(string.SUPPORTED_TESTOS)" name="SUPPORTED_TESTOS"/>\n' + ' <definition displayName="' + '$(string.SUPPORTED_TESTOS_2)" name="SUPPORTED_TESTOS_2"/>\n' + ' </definitions>\n' + ' </supportedOn>\n' + ' <categories>\n' + ' <category displayName="$(string.' + self._GetCategory() + ')"' + ' name="' + self._GetCategory() + '"/>\n' + ' <category displayName="$(string.' + self._GetCategoryRec() + ')"' + ' name="' + self._GetCategoryRec() + '"/>\n' + ' </categories>\n' + ' <policies/>\n' + '</policyDefinitions>') + self.AssertXMLEquals(output, expected_output) + + def testEmptyVersion(self): + self.writer.config['version'] = '39.0.0.0' + self.writer.BeginTemplate() + self.writer.EndTemplate() + + output = self.writer.GetTemplateText() + expected_output = ( + '<?xml version="1.0" ?>\n' + '<policyDefinitions revision="1.0" schemaVersion="1.0">\n' + ' <!--test_product version: 39.0.0.0-->\n' + ' <policyNamespaces>\n' + ' <target namespace="' + self._GetNamespace() + '"' + ' prefix="test_prefix"/>\n' + ' <using namespace="Microsoft.Policies.Windows" prefix="windows"/>\n' + ' </policyNamespaces>\n' + ' <resources minRequiredRevision="1.0"/>\n' + ' <supportedOn>\n' + ' <definitions>\n' + ' <definition displayName="' + '$(string.SUPPORTED_TESTOS)" name="SUPPORTED_TESTOS"/>\n' + ' <definition displayName="' + '$(string.SUPPORTED_TESTOS_2)" name="SUPPORTED_TESTOS_2"/>\n' + ' </definitions>\n' + ' </supportedOn>\n' + ' <categories>\n' + ' <category displayName="$(string.' + self._GetCategory() + ')"' + ' name="' + self._GetCategory() + '"/>\n' + ' <category displayName="$(string.' + self._GetCategoryRec() + ')"' + ' name="' + self._GetCategoryRec() + '"/>\n' + ' </categories>\n' + ' <policies/>\n' + '</policyDefinitions>') + self.AssertXMLEquals(output, expected_output) + + def testEmptyPolicyGroup(self): + empty_policy_group = {'name': 'PolicyGroup', 'policies': []} + # Initialize writer to write a policy group. + self.writer.BeginTemplate() + # Write policy group + self.writer.BeginPolicyGroup(empty_policy_group) + self.writer.EndPolicyGroup() + + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = '' + self.AssertXMLEquals(output, expected_output) + + output = self.GetXMLOfChildren(self._GetCategoriesElement(self.writer._doc)) + expected_output = ( + '<category displayName="$(string.' + self._GetCategory() + ')"' + ' name="' + self._GetCategory() + '"/>\n' + '<category displayName="$(string.' + self._GetCategoryRec() + ')"' + ' name="' + self._GetCategoryRec() + '"/>\n' + '<category displayName="$(string.PolicyGroup_group)"' + ' name="PolicyGroup">\n' + ' <parentCategory ref="' + self._GetCategory() + '"/>\n' + '</category>') + + self.AssertXMLEquals(output, expected_output) + + def testPolicyGroup(self): + empty_policy_group = { + 'name': + 'PolicyGroup', + 'policies': [ + { + 'name': 'PolicyStub2', + 'type': 'main' + }, + { + 'name': 'PolicyStub1', + 'type': 'main' + }, + ] + } + # Initialize writer to write a policy group. + self.writer.BeginTemplate() + # Write policy group + self.writer.BeginPolicyGroup(empty_policy_group) + self.writer.EndPolicyGroup() + + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = '' + self.AssertXMLEquals(output, expected_output) + + output = self.GetXMLOfChildren(self._GetCategoriesElement(self.writer._doc)) + expected_output = ( + '<category displayName="$(string.' + self._GetCategory() + ')"' + ' name="' + self._GetCategory() + '"/>\n' + '<category displayName="$(string.' + self._GetCategoryRec() + ')"' + ' name="' + self._GetCategoryRec() + '"/>\n' + '<category displayName="$(string.PolicyGroup_group)"' + ' name="PolicyGroup">\n' + ' <parentCategory ref="' + self._GetCategory() + '"/>\n' + '</category>') + self.AssertXMLEquals(output, expected_output) + + def _initWriterForPolicy(self, writer, policy): + '''Initializes the writer to write the given policy next. + ''' + policy_group = {'name': 'PolicyGroup', 'policies': [policy]} + writer.BeginTemplate() + writer.BeginPolicyGroup(policy_group) + + def testMainPolicy(self): + main_policy = { + 'name': 'DummyMainPolicy', + 'type': 'main', + } + + self._initWriterForPolicy(self.writer, main_policy) + + self.writer.WritePolicy(main_policy) + + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = ( + '<policy class="' + self.writer.GetClass(main_policy) + '"' + ' displayName="$(string.DummyMainPolicy)"' + ' explainText="$(string.DummyMainPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="DummyMainPolicy"' + ' presentation="$(presentation.DummyMainPolicy)"' + ' valueName="DummyMainPolicy">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <enabledValue>\n' + ' <decimal value="1"/>\n' + ' </enabledValue>\n' + ' <disabledValue>\n' + ' <decimal value="0"/>\n' + ' </disabledValue>\n' + '</policy>') + + self.AssertXMLEquals(output, expected_output) + + def testRecommendedPolicy(self): + main_policy = { + 'name': 'DummyMainPolicy', + 'type': 'main', + } + + policy_group = { + 'name': 'PolicyGroup', + 'policies': [main_policy], + } + self.writer.BeginTemplate() + self.writer.BeginRecommendedPolicyGroup(policy_group) + + self.writer.WriteRecommendedPolicy(main_policy) + + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = ( + '<policy class="' + self.writer.GetClass(main_policy) + '"' + ' displayName="$(string.DummyMainPolicy)"' + ' explainText="$(string.DummyMainPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '\\Recommended"' + ' name="DummyMainPolicy_recommended"' + ' presentation="$(presentation.DummyMainPolicy)"' + ' valueName="DummyMainPolicy">\n' + ' <parentCategory ref="PolicyGroup_recommended"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <enabledValue>\n' + ' <decimal value="1"/>\n' + ' </enabledValue>\n' + ' <disabledValue>\n' + ' <decimal value="0"/>\n' + ' </disabledValue>\n' + '</policy>') + + self.AssertXMLEquals(output, expected_output) + + def testRecommendedOnlyPolicy(self): + main_policy = { + 'name': 'DummyMainPolicy', + 'type': 'main', + 'features': { + 'can_be_recommended': True, + 'can_be_mandatory': False, + } + } + + policy_group = { + 'name': 'PolicyGroup', + 'policies': [main_policy], + } + self.writer.BeginTemplate() + self.writer.BeginRecommendedPolicyGroup(policy_group) + + self.writer.WritePolicy(main_policy) + self.writer.WriteRecommendedPolicy(main_policy) + + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = ( + '<policy class="' + self.writer.GetClass(main_policy) + '"' + ' displayName="$(string.DummyMainPolicy)"' + ' explainText="$(string.DummyMainPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '\\Recommended"' + ' name="DummyMainPolicy_recommended"' + ' presentation="$(presentation.DummyMainPolicy)"' + ' valueName="DummyMainPolicy">\n' + ' <parentCategory ref="PolicyGroup_recommended"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <enabledValue>\n' + ' <decimal value="1"/>\n' + ' </enabledValue>\n' + ' <disabledValue>\n' + ' <decimal value="0"/>\n' + ' </disabledValue>\n' + '</policy>') + + self.AssertXMLEquals(output, expected_output) + + def testStringPolicy(self): + string_policy = { + 'name': 'SampleStringPolicy', + 'type': 'string', + } + self._initWriterForPolicy(self.writer, string_policy) + + self.writer.WritePolicy(string_policy) + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = ( + '<policy class="' + self.writer.GetClass(string_policy) + '"' + ' displayName="$(string.SampleStringPolicy)"' + ' explainText="$(string.SampleStringPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="SampleStringPolicy"' + ' presentation="$(presentation.SampleStringPolicy)">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <elements>\n' + ' <text id="SampleStringPolicy" maxLength="1000000"' + ' valueName="SampleStringPolicy"/>\n' + ' </elements>\n' + '</policy>') + self.AssertXMLEquals(output, expected_output) + + def testIntPolicy(self): + int_policy = { + 'name': 'SampleIntPolicy', + 'type': 'int', + } + self._initWriterForPolicy(self.writer, int_policy) + + self.writer.WritePolicy(int_policy) + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = ( + '<policy class="' + self.writer.GetClass(int_policy) + '"' + ' displayName="$(string.SampleIntPolicy)"' + ' explainText="$(string.SampleIntPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="SampleIntPolicy"' + ' presentation="$(presentation.SampleIntPolicy)">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <elements>\n' + ' <decimal id="SampleIntPolicy" maxValue="2000000000" minValue="0" ' + 'valueName="SampleIntPolicy"/>\n' + ' </elements>\n' + '</policy>') + self.AssertXMLEquals(output, expected_output) + + def testIntPolicyWithWin7Only(self): + int_policy = { + 'name': 'SampleIntPolicy', + 'type': 'int', + 'supported_on': [{ + 'platform': 'win7', + }] + } + self._initWriterForPolicy(self.writer, int_policy) + + self.writer.WritePolicy(int_policy) + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = ( + '<policy class="' + self.writer.GetClass(int_policy) + '"' + ' displayName="$(string.SampleIntPolicy)"' + ' explainText="$(string.SampleIntPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="SampleIntPolicy"' + ' presentation="$(presentation.SampleIntPolicy)">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS_2"/>\n' + ' <elements>\n' + ' <decimal id="SampleIntPolicy" maxValue="2000000000" minValue="0" ' + 'valueName="SampleIntPolicy"/>\n' + ' </elements>\n' + '</policy>') + self.AssertXMLEquals(output, expected_output) + + + def testIntEnumPolicy(self): + enum_policy = { + 'name': + 'SampleEnumPolicy', + 'type': + 'int-enum', + 'items': [ + { + 'name': 'item_1', + 'value': 0 + }, + { + 'name': 'item_2', + 'value': 1 + }, + ] + } + + self._initWriterForPolicy(self.writer, enum_policy) + self.writer.WritePolicy(enum_policy) + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = ( + '<policy class="' + self.writer.GetClass(enum_policy) + '"' + ' displayName="$(string.SampleEnumPolicy)"' + ' explainText="$(string.SampleEnumPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="SampleEnumPolicy"' + ' presentation="$(presentation.SampleEnumPolicy)">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <elements>\n' + ' <enum id="SampleEnumPolicy" valueName="SampleEnumPolicy">\n' + ' <item displayName="$(string.SampleEnumPolicy_item_1)">\n' + ' <value>\n' + ' <decimal value="0"/>\n' + ' </value>\n' + ' </item>\n' + ' <item displayName="$(string.SampleEnumPolicy_item_2)">\n' + ' <value>\n' + ' <decimal value="1"/>\n' + ' </value>\n' + ' </item>\n' + ' </enum>\n' + ' </elements>\n' + '</policy>') + self.AssertXMLEquals(output, expected_output) + + def testStringEnumPolicy(self): + enum_policy = { + 'name': + 'SampleEnumPolicy', + 'type': + 'string-enum', + 'items': [ + { + 'name': 'item_1', + 'value': 'one' + }, + { + 'name': 'item_2', + 'value': 'two' + }, + ] + } + + # This test is different than the others because it also tests that space + # usage inside <string> nodes is correct. + dom_impl = minidom.getDOMImplementation('') + self.writer._doc = dom_impl.createDocument(None, 'policyDefinitions', None) + self.writer._active_policies_elem = self.writer._doc.documentElement + self.writer._active_mandatory_policy_group_name = 'PolicyGroup' + self.writer.WritePolicy(enum_policy) + output = self.writer.GetTemplateText() + expected_output = ( + '<?xml version="1.0" ?>\n' + '<policyDefinitions>\n' + ' <policy class="' + self.writer.GetClass(enum_policy) + '"' + ' displayName="$(string.SampleEnumPolicy)"' + ' explainText="$(string.SampleEnumPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="SampleEnumPolicy"' + ' presentation="$(presentation.SampleEnumPolicy)">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <elements>\n' + ' <enum id="SampleEnumPolicy" valueName="SampleEnumPolicy">\n' + ' <item displayName="$(string.SampleEnumPolicy_item_1)">\n' + ' <value>\n' + ' <string>one</string>\n' + ' </value>\n' + ' </item>\n' + ' <item displayName="$(string.SampleEnumPolicy_item_2)">\n' + ' <value>\n' + ' <string>two</string>\n' + ' </value>\n' + ' </item>\n' + ' </enum>\n' + ' </elements>\n' + ' </policy>\n' + '</policyDefinitions>') + self.AssertXMLEquals(output, expected_output) + + def testListPolicy(self): + list_policy = { + 'name': 'SampleListPolicy', + 'type': 'list', + } + self._initWriterForPolicy(self.writer, list_policy) + self.writer.WritePolicy(list_policy) + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = ( + '<policy class="' + self.writer.GetClass(list_policy) + '"' + ' displayName="$(string.SampleListPolicy)"' + ' explainText="$(string.SampleListPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="SampleListPolicy"' + ' presentation="$(presentation.SampleListPolicy)">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <elements>\n' + ' <list id="SampleListPolicyDesc"' + ' key="Software\Policies\\' + self._GetKey() + '\SampleListPolicy"' + ' valuePrefix=""/>\n' + ' </elements>\n' + '</policy>') + + self.AssertXMLEquals(output, expected_output) + + def testStringEnumListPolicy(self): + list_policy = { + 'name': + 'SampleListPolicy', + 'type': + 'string-enum-list', + 'items': [ + { + 'name': 'item_1', + 'value': 'one' + }, + { + 'name': 'item_2', + 'value': 'two' + }, + ] + } + self._initWriterForPolicy(self.writer, list_policy) + self.writer.WritePolicy(list_policy) + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = ( + '<policy class="' + self.writer.GetClass(list_policy) + '"' + ' displayName="$(string.SampleListPolicy)"' + ' explainText="$(string.SampleListPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="SampleListPolicy"' + ' presentation="$(presentation.SampleListPolicy)">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <elements>\n' + ' <list id="SampleListPolicyDesc"' + ' key="Software\Policies\\' + self._GetKey() + '\SampleListPolicy"' + ' valuePrefix=""/>\n' + ' </elements>\n' + '</policy>') + + self.AssertXMLEquals(output, expected_output) + + def testDictionaryPolicy(self, is_external=False): + dict_policy = { + 'name': 'SampleDictionaryPolicy', + 'type': 'external' if is_external else 'dict', + } + self._initWriterForPolicy(self.writer, dict_policy) + + self.writer.WritePolicy(dict_policy) + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = ( + '<policy class="' + self.writer.GetClass(dict_policy) + '"' + ' displayName="$(string.SampleDictionaryPolicy)"' + ' explainText="$(string.SampleDictionaryPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="SampleDictionaryPolicy"' + ' presentation="$(presentation.SampleDictionaryPolicy)">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <elements>\n' + ' <text id="SampleDictionaryPolicy" maxLength="1000000"' + ' valueName="SampleDictionaryPolicy"/>\n' + ' </elements>\n' + '</policy>') + self.AssertXMLEquals(output, expected_output) + + def testExternalPolicy(self): + self.testDictionaryPolicy(is_external=True) + + def testPlatform(self): + # Test that the writer correctly chooses policies of platform Windows. + self.assertTrue( + self.writer.IsPolicySupported({ + 'supported_on': [{ + 'platform': 'win' + }, { + 'platform': 'aaa' + }] + })) + self.assertFalse( + self.writer.IsPolicySupported({ + 'supported_on': [{ + 'platform': 'mac' + }, { + 'platform': 'aaa' + }, { + 'platform': 'linux' + }] + })) + + def testStringEncodings(self): + enum_policy_a = { + 'name': 'SampleEnumPolicy.A', + 'type': 'string-enum', + 'items': [{ + 'name': 'tls1.2', + 'value': 'tls1.2' + }] + } + enum_policy_b = { + 'name': 'SampleEnumPolicy.B', + 'type': 'string-enum', + 'items': [{ + 'name': 'tls1.2', + 'value': 'tls1.2' + }] + } + + dom_impl = minidom.getDOMImplementation('') + self.writer._doc = dom_impl.createDocument(None, 'policyDefinitions', None) + self.writer._active_policies_elem = self.writer._doc.documentElement + self.writer._active_mandatory_policy_group_name = 'PolicyGroup' + self.writer.WritePolicy(enum_policy_a) + self.writer.WritePolicy(enum_policy_b) + output = self.writer.GetTemplateText() + expected_output = ( + '<?xml version="1.0" ?>\n' + '<policyDefinitions>\n' + ' <policy class="' + self.writer.GetClass(enum_policy_a) + '"' + ' displayName="$(string.SampleEnumPolicy_A)"' + ' explainText="$(string.SampleEnumPolicy_A_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="SampleEnumPolicy.A"' + ' presentation="$(presentation.SampleEnumPolicy.A)">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <elements>\n' + ' <enum id="SampleEnumPolicy.A" valueName="SampleEnumPolicy.A">\n' + ' <item displayName="$(string.SampleEnumPolicy_A_tls1_2)">\n' + ' <value>\n' + ' <string>tls1.2</string>\n' + ' </value>\n' + ' </item>\n' + ' </enum>\n' + ' </elements>\n' + ' </policy>\n' + ' <policy class="' + self.writer.GetClass(enum_policy_b) + '"' + ' displayName="$(string.SampleEnumPolicy_B)"' + ' explainText="$(string.SampleEnumPolicy_B_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="SampleEnumPolicy.B"' + ' presentation="$(presentation.SampleEnumPolicy.B)">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <elements>\n' + ' <enum id="SampleEnumPolicy.B" valueName="SampleEnumPolicy.B">\n' + ' <item displayName="$(string.SampleEnumPolicy_B_tls1_2)">\n' + ' <value>\n' + ' <string>tls1.2</string>\n' + ' </value>\n' + ' </item>\n' + ' </enum>\n' + ' </elements>\n' + ' </policy>\n' + '</policyDefinitions>') + self.AssertXMLEquals(output, expected_output) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/android_policy_writer.py b/chromium/components/policy/tools/template_writers/writers/android_policy_writer.py new file mode 100755 index 00000000000..5536a41fca3 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/android_policy_writer.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015 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. + +from writers import xml_formatted_writer +from xml.dom import minidom +from xml.sax import saxutils as xml_escape + + +def GetWriter(config): + '''Factory method for creating AndroidPolicyWriter objects. + See the constructor of TemplateWriter for description of + arguments. + ''' + return AndroidPolicyWriter(['android'], config) + + +def _EscapeResource(resource): + '''Escape the resource for usage in an Android resource XML file. + This includes standard XML escaping as well as those specific to Android. + ''' + if resource == None or type(resource) in (int, bool): + return str(resource) + return xml_escape.escape( + resource, + { + # Written order is matter to prevent "'" becomes "\\\\'" instead of + # "\\'". + "\\": "\\\\", + "'": "\\'", + '"': '\\"', + }) + + +class AndroidPolicyWriter(xml_formatted_writer.XMLFormattedWriter): + '''Outputs localized Android Resource XML files. + The policy strings are localized and exposed as string resources for + consumption through Android's App restriction Schema. + ''' + + # DOM root node of the generated XML document. + _doc = None + # The resources node contains all resource 'string' and 'string-array' + # elements. + _resources = None + + def AddStringResource(self, name, string): + '''Add a string resource of the given name. + ''' + string_node = self._doc.createElement('string') + string_node.setAttribute('name', name) + string_node.appendChild(self._doc.createTextNode(_EscapeResource(string))) + self._resources.appendChild(string_node) + + def AddStringArrayResource(self, name, string_items): + '''Add a string-array resource of the given name and + elements from string_items. + ''' + string_array_node = self._doc.createElement('string-array') + string_array_node.setAttribute('name', name) + self._resources.appendChild(string_array_node) + for item in string_items: + string_node = self._doc.createElement('item') + string_node.appendChild(self._doc.createTextNode(_EscapeResource(item))) + string_array_node.appendChild(string_node) + + def PreprocessPolicies(self, policy_list): + return self.FlattenGroupsAndSortPolicies(policy_list) + + def CanBeRecommended(self, policy): + return False + + def WritePolicy(self, policy): + name = policy['name'] + self.AddStringResource(name + 'Title', policy['caption']) + + # Get the policy description. + description = policy['desc'] + self.AddStringResource(name + 'Desc', description) + + items = policy.get('items') + if items is not None: + items = [ + item for item in items + if ('supported_on' not in item or + self.IsPolicyOrItemSupportedOnPlatform(item, 'android')) + ] + entries = [item['caption'] for item in items] + values = [item['value'] for item in items] + self.AddStringArrayResource(name + 'Entries', entries) + self.AddStringArrayResource(name + 'Values', values) + + def BeginTemplate(self): + comment_text = 'DO NOT MODIFY THIS FILE DIRECTLY!\n' \ + 'IT IS GENERATED FROM policy_templates.json.' + if self._GetChromiumVersionString(): + comment_text += '\n' + self.config['build'] + ' version: '\ + + self._GetChromiumVersionString() + comment_node = self._doc.createComment(comment_text) + self._doc.insertBefore(comment_node, self._resources) + + def Init(self): + impl = minidom.getDOMImplementation() + self._doc = impl.createDocument(None, 'resources', None) + self._resources = self._doc.documentElement + + def GetTemplateText(self): + return self.ToPrettyXml(self._doc) diff --git a/chromium/components/policy/tools/template_writers/writers/android_policy_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/android_policy_writer_unittest.py new file mode 100755 index 00000000000..a282bc5aab8 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/android_policy_writer_unittest.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015 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. +'''Unit tests for writers.android_policy_writer''' + +import os +import sys +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +import unittest +from xml.dom import minidom + +from writers import writer_unittest_common +from writers import android_policy_writer + + +class AndroidPolicyWriterUnittest(writer_unittest_common.WriterUnittestCommon): + '''Unit tests to test assumptions in Android Policy Writer''' + + def testPolicyWithoutItems(self): + # Test an example policy without items. + policy = { + 'name': '_policy_name', + 'caption': '_policy_caption', + 'desc': 'This is a long policy caption. More than one sentence ' + 'in a single line because it is very important.\n' + 'Second line, also important' + } + writer = android_policy_writer.GetWriter({}) + writer.Init() + writer.BeginTemplate() + writer.WritePolicy(policy) + self.assertEquals( + writer._resources.toxml(), '<resources>' + '<string name="_policy_nameTitle">_policy_caption</string>' + '<string name="_policy_nameDesc">This is a long policy caption. More ' + 'than one sentence in a single line because it is very ' + 'important.\nSecond line, also important' + '</string>' + '</resources>') + + def testPolicyWithItems(self): + # Test an example policy without items. + policy = { + 'name': + '_policy_name', + 'caption': + '_policy_caption', + 'desc': + '_policy_desc_first.\nadditional line', + 'items': [{ + 'caption': '_caption1', + 'value': '_value1', + }, { + 'caption': '_caption2', + 'value': '_value2', + }, + { + 'caption': '_caption3', + 'value': '_value3', + 'supported_on': [{ + 'platform': 'win' + }, { + 'platform': 'win7' + }] + }, + { + 'caption': + '_caption4', + 'value': + '_value4', + 'supported_on': [{ + 'platform': 'android' + }, { + 'platform': 'win7' + }] + }] + } + writer = android_policy_writer.GetWriter({}) + writer.Init() + writer.BeginTemplate() + writer.WritePolicy(policy) + self.assertEquals( + writer._resources.toxml(), '<resources>' + '<string name="_policy_nameTitle">_policy_caption</string>' + '<string name="_policy_nameDesc">_policy_desc_first.\n' + 'additional line</string>' + '<string-array name="_policy_nameEntries">' + '<item>_caption1</item>' + '<item>_caption2</item>' + '<item>_caption4</item>' + '</string-array>' + '<string-array name="_policy_nameValues">' + '<item>_value1</item>' + '<item>_value2</item>' + '<item>_value4</item>' + '</string-array>' + '</resources>') + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/chromeos_adml_writer.py b/chromium/components/policy/tools/template_writers/writers/chromeos_adml_writer.py new file mode 100755 index 00000000000..72e4dfc5832 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/chromeos_adml_writer.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# Copyright 2017 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. + +import base64 + +from writers import adml_writer +from writers.admx_writer import AdmxElementType + + +def GetWriter(config): + '''Factory method for creating ADMLWriter objects for the Chrome OS platform. + See the constructor of TemplateWriter for description of arguments. + ''' + return ChromeOSADMLWriter(['chrome_os'], config) + + +class ChromeOSADMLWriter(adml_writer.ADMLWriter): + ''' Class for generating Chrome OS ADML policy templates. It is used by the + PolicyTemplateGenerator to write the ADML file. + ''' + + # Overridden. + # These ADML files are used to generate GPO for Active Directory managed + # Chrome OS devices. + def IsPolicySupported(self, policy): + return self.IsCrOSManagementSupported(policy, 'active_directory') and \ + super(ChromeOSADMLWriter, self).IsPolicySupported(policy) + + # Overridden. + def _GetAdmxElementType(self, policy): + return AdmxElementType.GetType(policy, allow_multi_strings=True) diff --git a/chromium/components/policy/tools/template_writers/writers/chromeos_adml_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/chromeos_adml_writer_unittest.py new file mode 100755 index 00000000000..f07d299270d --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/chromeos_adml_writer_unittest.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# Copyright 2017 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. +"""Unittests for writers.chromeos_adml_writer.""" + +import os +import sys +import unittest +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +from writers import chromeos_adml_writer +from writers import adml_writer_unittest +from writers.admx_writer import AdmxElementType + + +class ChromeOsAdmlWriterUnittest(adml_writer_unittest.AdmlWriterUnittest): + + # Overridden. + def _GetWriter(self, config): + return chromeos_adml_writer.GetWriter(config) + + # Overridden + def GetCategory(self): + return "cros_test_category" + + # Overridden + def GetCategoryString(self): + return "CrOSTestCategory" + + # Overridden. + def testPlatform(self): + # Test that the writer correctly chooses policies of platform Chrome OS. + self.assertTrue( + self.writer.IsPolicySupported({ + 'supported_on': [{ + 'platform': 'chrome_os' + }, { + 'platform': 'aaa' + }] + })) + self.assertFalse( + self.writer.IsPolicySupported({ + 'supported_on': [{ + 'platform': 'win' + }, { + 'platform': 'aaa' + }] + })) + + def testOnlySupportsAdPolicies(self): + # Tests whether only Active Directory managed policies are supported (Google + # cloud only managed polices are not put in the ADMX file). + policy = { + 'name': + 'PolicyName', + 'supported_on': [{ + 'product': 'chrome_os', + 'platform': 'chrome_os', + 'since_version': '8', + 'until_version': '', + }], + } + self.assertTrue(self.writer.IsPolicySupported(policy)) + + policy['supported_chrome_os_management'] = ['google_cloud'] + self.assertFalse(self.writer.IsPolicySupported(policy)) + + policy['supported_chrome_os_management'] = ['active_directory'] + self.assertTrue(self.writer.IsPolicySupported(policy)) + + # Overridden. + def testDictionaryPolicy(self, is_external=False): + dict_policy = { + 'name': 'DictionaryPolicyStub', + 'type': 'external' if is_external else 'dict', + 'caption': 'Dictionary policy caption', + 'label': 'Dictionary policy label', + 'desc': 'This is a test description.', + } + self._InitWriterForAddingPolicies(self.writer, dict_policy) + self.writer.WritePolicy(dict_policy) + # Assert generated string elements. + output = self.GetXMLOfChildren(self.writer._string_table_elem) + expected_output = ( + '<string id="DictionaryPolicyStub">Dictionary policy caption</string>\n' + '<string id="DictionaryPolicyStub_Explain">' + 'This is a test description.\n' + 'See https://cloud.google.com/docs/chrome-enterprise/policies/?policy=' + 'DictionaryPolicyStub\n</string>\n' + '<string id="DictionaryPolicyStub_Legacy">' + 'Dictionary policy label (deprecated)</string>') + self.AssertXMLEquals(output, expected_output) + # Assert generated presentation elements. + output = self.GetXMLOfChildren(self.writer._presentation_table_elem) + expected_output = ( + '<presentation id="DictionaryPolicyStub">\n' + ' <textBox refId="DictionaryPolicyStub_Legacy">\n' + ' <label>Dictionary policy label (deprecated)</label>\n' + ' </textBox>\n' + ' <multiTextBox defaultHeight="8" refId="DictionaryPolicyStub">' + 'Dictionary policy label</multiTextBox>\n' + '</presentation>') + self.AssertXMLEquals(output, expected_output) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/chromeos_admx_writer.py b/chromium/components/policy/tools/template_writers/writers/chromeos_admx_writer.py new file mode 100755 index 00000000000..c9f1c2c5052 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/chromeos_admx_writer.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# Copyright 2017 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. + +import base64 + +from writers import admx_writer +from writers.admx_writer import AdmxElementType + + +def GetWriter(config): + '''Factory method for creating ADMXWriter objects for the Chrome OS platform + See the constructor of TemplateWriter for description of arguments. + ''' + return ChromeOSADMXWriter(['chrome_os'], config) + + +class ChromeOSADMXWriter(admx_writer.ADMXWriter): + '''Class for generating Chrome OS policy templates in the ADMX format. + It is used by PolicyTemplateGenerator to write ADMX files. + ''' + + # Overridden. + def GetClass(self, policy): + is_device_only = 'device_only' in policy and policy['device_only'] + return 'Machine' if is_device_only else 'User' + + # Overridden. + # These ADMX templates are used to generate GPO for Active Directory managed + # Chrome OS devices. + def IsPolicySupported(self, policy): + return self.IsCrOSManagementSupported(policy, 'active_directory') and \ + super(ChromeOSADMXWriter, self).IsPolicySupported(policy) + + # Overridden. + def _GetAdmxElementType(self, policy): + return AdmxElementType.GetType(policy, allow_multi_strings=True) diff --git a/chromium/components/policy/tools/template_writers/writers/chromeos_admx_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/chromeos_admx_writer_unittest.py new file mode 100755 index 00000000000..b6b4f83a426 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/chromeos_admx_writer_unittest.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# Copyright 2017 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. +"""Unittests for writers.chromeos_admx_writer.""" + +import os +import sys +import unittest +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +from writers import chromeos_admx_writer +from writers import admx_writer_unittest +from writers.admx_writer import AdmxElementType + + +class ChromeOsAdmxWriterUnittest(admx_writer_unittest.AdmxWriterUnittest): + + # Overridden. + def _GetWriter(self, config): + return chromeos_admx_writer.GetWriter(config) + + # Overridden. + def _GetKey(self): + return "CrOSTest" + + # Overridden. + def _GetCategory(self): + return "cros_test_category" + + # Overridden. + def _GetCategoryRec(self): + return "cros_test_recommended_category" + + # Overridden. + def _GetNamespace(self): + return "ADMXWriter.Test.Namespace.ChromeOS" + + # Overridden. + def testPlatform(self): + # Test that the writer correctly chooses policies of platform Chrome OS. + self.assertTrue( + self.writer.IsPolicySupported({ + 'supported_on': [{ + 'platform': 'chrome_os' + }, { + 'platform': 'aaa' + }] + })) + self.assertFalse( + self.writer.IsPolicySupported({ + 'supported_on': [{ + 'platform': 'win' + }, { + 'platform': 'aaa' + }] + })) + + def testUserPolicy(self): + self.doTestUserOrDevicePolicy(False) + + def testDevicePolicy(self): + self.doTestUserOrDevicePolicy(True) + + def doTestUserOrDevicePolicy(self, is_device_only): + # Tests whether CLASS attribute is 'User' for user policies and 'Machine' + # for device policies. + main_policy = { + 'name': 'DummyMainPolicy', + 'type': 'main', + 'device_only': is_device_only, + } + + expected_class = 'Machine' if is_device_only else 'User' + + self._initWriterForPolicy(self.writer, main_policy) + self.writer.WritePolicy(main_policy) + + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = ('<policy class="' + expected_class + '"' + ' displayName="$(string.DummyMainPolicy)"' + ' explainText="$(string.DummyMainPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="DummyMainPolicy"' + ' presentation="$(presentation.DummyMainPolicy)"' + ' valueName="DummyMainPolicy">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <enabledValue>\n' + ' <decimal value="1"/>\n' + ' </enabledValue>\n' + ' <disabledValue>\n' + ' <decimal value="0"/>\n' + ' </disabledValue>\n' + '</policy>') + + self.AssertXMLEquals(output, expected_output) + + def testOnlySupportsAdPolicies(self): + # Tests whether only Active Directory managed policies are supported (Google + # cloud only managed polices are not put in the ADMX file). + policy = { + 'name': + 'PolicyName', + 'supported_on': [{ + 'product': 'chrome_os', + 'platform': 'chrome_os', + 'since_version': '8', + 'until_version': '', + }], + } + self.assertTrue(self.writer.IsPolicySupported(policy)) + + policy['supported_chrome_os_management'] = ['google_cloud'] + self.assertFalse(self.writer.IsPolicySupported(policy)) + + policy['supported_chrome_os_management'] = ['active_directory'] + self.assertTrue(self.writer.IsPolicySupported(policy)) + + #Overridden + def testDictionaryPolicy(self, is_external=False): + dict_policy = { + 'name': 'SampleDictionaryPolicy', + 'type': 'external' if is_external else 'dict', + } + self._initWriterForPolicy(self.writer, dict_policy) + + self.writer.WritePolicy(dict_policy) + output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc)) + expected_output = ( + '<policy class="' + self.writer.GetClass(dict_policy) + '"' + ' displayName="$(string.SampleDictionaryPolicy)"' + ' explainText="$(string.SampleDictionaryPolicy_Explain)"' + ' key="Software\\Policies\\' + self._GetKey() + '"' + ' name="SampleDictionaryPolicy"' + ' presentation="$(presentation.SampleDictionaryPolicy)">\n' + ' <parentCategory ref="PolicyGroup"/>\n' + ' <supportedOn ref="SUPPORTED_TESTOS"/>\n' + ' <elements>\n' + ' <text id="SampleDictionaryPolicy_Legacy" maxLength="1000000"' + ' valueName="SampleDictionaryPolicy"/>\n' + ' <multiText id="SampleDictionaryPolicy" maxLength="1000000"' + ' valueName="SampleDictionaryPolicy"/>\n' + ' </elements>\n' + '</policy>') + self.AssertXMLEquals(output, expected_output) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py b/chromium/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py new file mode 100755 index 00000000000..9f92347a6cb --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# Copyright 2019 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. + +from writers import doc_writer + + +def GetWriter(config): + '''Factory method for creating DocAtomicGroupsWriter objects. + See the constructor of TemplateWriter for description of + arguments. + ''' + return DocAtomicGroupsWriter(['*'], config) + + +class DocAtomicGroupsWriter(doc_writer.DocWriter): + '''Class for generating atomic policy group templates in HTML format. + The intended use of the generated file is to upload it on + https://www.chromium.org, therefore its format has some limitations: + - No HTML and body tags. + - Restricted set of element attributes: for example no 'class'. + Because of the latter the output is styled using the 'style' + attributes of HTML elements. This is supported by the dictionary + self._STYLES[] and the method self._AddStyledElement(), they try + to mimic the functionality of CSS classes. (But without inheritance.) + + This class is invoked by PolicyTemplateGenerator to create the HTML + files. + ''' + + def _AddPolicyRow(self, parent, policy): + '''Adds a row for the policy in the summary table. + + Args: + parent: The DOM node of the summary table. + policy: The data structure of the policy. + ''' + tr = self._AddStyledElement(parent, 'tr', ['tr']) + indent = 'padding-left: %dpx;' % (7 + self._indent_level * 14) + if policy['type'] != 'group': + # Normal policies get two columns with name and caption. + name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'], + {'style': indent}) + policy_ref = './' + if self.config.get('local', False): + policy_ref = './chrome_policy_list.html' + self.AddElement(name_td, 'a', {'href': policy_ref + '#' + policy['name']}, + policy['name']) + self._AddStyledElement(tr, 'td', ['td', 'td.right'], {}, + policy['caption']) + else: + # Groups get one column with caption. + name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'], { + 'style': indent, + 'colspan': '2' + }) + self.AddElement(name_td, 'a', {'name': policy['name']}, policy['caption']) + + # + # Implementation of abstract methods of TemplateWriter: + # + + def WritePolicy(self, policy): + self._AddPolicyRow(self._summary_tbody, policy) + + def BeginTemplate(self): + self._BeginTemplate('group_intro', 'banner') + + def WriteTemplate(self, template): + '''Writes the given template definition. + + Args: + template: Template definition to write. + + Returns: + Generated output for the passed template definition. + ''' + self.messages = template['messages'] + self.Init() + + policies = self.PreprocessPolicies( + template['policy_atomic_group_definitions']) + + self.BeginTemplate() + for policy in policies: + if policy['type'] != 'atomic_group': + continue + self.BeginPolicyGroup(policy) + for child_policy in policy['policies']: + # Nesting of groups is currently not supported. + self.WritePolicy(child_policy) + self.EndPolicyGroup() + self.EndTemplate() + + return self.GetTemplateText() diff --git a/chromium/components/policy/tools/template_writers/writers/doc_writer.py b/chromium/components/policy/tools/template_writers/writers/doc_writer.py new file mode 100755 index 00000000000..c5a0104bc9e --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/doc_writer.py @@ -0,0 +1,883 @@ +#!/usr/bin/env python3 +# Copyright 2019 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. + +import json +import re +from xml.dom import minidom +from xml.sax.saxutils import escape +from writers import xml_formatted_writer + + +def GetWriter(config): + '''Factory method for creating DocWriter objects. + See the constructor of TemplateWriter for description of + arguments. + ''' + return DocWriter(['*'], config) + + +class DocWriter(xml_formatted_writer.XMLFormattedWriter): + '''Class for generating policy templates in HTML format. + The intended use of the generated file is to upload it on + http://dev.chromium.org, therefore its format has some limitations: + - No HTML and body tags. + - Restricted set of element attributes: for example no 'class'. + Because of the latter the output is styled using the 'style' + attributes of HTML elements. This is supported by the dictionary + self._STYLES[] and the method self._AddStyledElement(), they try + to mimic the functionality of CSS classes. (But without inheritance.) + + This class is invoked by PolicyTemplateGenerator to create the HTML + files. + ''' + + def _AddTextWithLinks(self, parent, text): + '''Parse a string for URLs and add it to a DOM node with the URLs replaced + with <a> HTML links. + + Args: + parent: The DOM node to which the text will be added. + text: The string to be added. + ''' + # A simple regexp to search for URLs. It is enough for now. + url_matcher = re.compile('(https?://[^\\s]*[^\\s\\.\\)\\"])') + + # Iterate through all the URLs and replace them with links. + while True: + # Look for the first URL. + res = url_matcher.search(text) + if not res: + break + # Calculate positions of the substring of the URL. + url = res.group(0) + start = res.start(0) + end = res.end(0) + # Add the text prior to the URL. + self.AddText(parent, text[:start]) + # Add a link for the URL. + self.AddElement(parent, 'a', {'href': url}, url) + # Drop the part of text that is added. + text = text[end:] + self.AddText(parent, text) + + def _AddParagraphs(self, parent, text): + '''Break description into paragraphs and replace URLs with links. + + Args: + parent: The DOM node to which the text will be added. + text: The string to be added. + ''' + # Split text into list of paragraphs. + entries = text.split('\n\n') + for entry in entries: + # Create a new paragraph node. + paragraph = self.AddElement(parent, 'p') + # Insert text to the paragraph with processing the URLs. + self._AddTextWithLinks(paragraph, entry) + + def _AddStyledElement(self, parent, name, style_ids, attrs=None, text=None): + '''Adds an XML element to a parent, with CSS style-sheets included. + + Args: + parent: The parent DOM node. + name: Name of the element to add. + style_ids: A list of CSS style strings from self._STYLE[]. + attrs: Dictionary of attributes for the element. + text: Text content for the element. + ''' + if attrs == None: + attrs = {} + + style = ''.join([self._STYLE[x] for x in style_ids]) + if style != '': + # Apply the style specified by style_ids. + attrs['style'] = style + attrs.get('style', '') + return self.AddElement(parent, name, attrs, text) + + def _AddDescription(self, parent, policy): + '''Adds a string containing the description of the policy. URLs are + replaced with links and the possible choices are enumerated in case + of 'string-enum' and 'int-enum' type policies. + + Args: + parent: The DOM node for which the feature list will be added. + policy: The data structure of a policy. + ''' + # Add description by paragraphs (URLs will be substituted by links). + self._AddParagraphs(parent, policy['desc']) + # Add list of enum items. + if policy['type'] in ('string-enum', 'int-enum', 'string-enum-list'): + ul = self.AddElement(parent, 'ul') + for item in policy['items']: + if policy['type'] == 'int-enum': + value_string = str(item['value']) + else: + value_string = '"%s"' % item['value'] + self.AddElement(ul, 'li', {}, + '%s = %s' % (value_string, item['caption'])) + + def _AddSchema(self, parent, schema): + '''Adds a schema to a DOM node. + + Args: + parent: The DOM node for which the schema will be added. + schema: The schema of a policy. + ''' + dd = self._AddPolicyAttribute(parent, 'schema', None, + ['.monospace', '.pre-wrap']) + # Explicitly specify separators since defaults depend on python version. + schema_json = json.dumps(schema, + indent=2, + sort_keys=True, + separators=(", ", ": ")) + self.AddText(dd, schema_json) + + def _AddFeatures(self, parent, policy): + '''Adds a string containing the list of supported features of a policy + to a DOM node. The text will look like as: + Feature_X: Yes, Feature_Y: No + + Args: + parent: The DOM node for which the feature list will be added. + policy: The data structure of a policy. + ''' + features = [] + # The sorting is to make the order well-defined for testing. + keys = sorted(policy['features'].keys()) + for key in keys: + key_name = self._FEATURE_MAP[key] + if policy['features'][key]: + value_name = self.GetLocalizedMessage('supported') + else: + value_name = self.GetLocalizedMessage('not_supported') + features.append('%s: %s' % (key_name, value_name)) + self.AddText(parent, ', '.join(features)) + + def _AddListExampleMac(self, parent, policy): + '''Adds an example value for Mac of a 'list' policy to a DOM node. + + Args: + parent: The DOM node for which the example will be added. + policy: A policy of type 'list', for which the Mac example value + is generated. + ''' + example_value = policy['example_value'] + self.AddElement(parent, 'dt', {}, 'Mac:') + mac = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre-wrap']) + + mac_text = ['<array>'] + for item in example_value: + mac_text.append(' <string>%s</string>' % item) + mac_text.append('</array>') + self.AddText(mac, '\n'.join(mac_text)) + + def _AddListExampleWindowsChromeOS(self, parent, policy, is_win): + '''Adds an example value for Windows or Chromium/Google Chrome OS of a + 'list' policy to a DOM node. + + Args: + parent: The DOM node for which the example will be added. + policy: A policy of type 'list', for which the Windows example value + is generated. + is_win: True for Windows, False for Chromium/Google Chrome OS. + ''' + example_value = policy['example_value'] + os_header = self.GetLocalizedMessage('win_example_value') if is_win else \ + self.GetLocalizedMessage('chrome_os_example_value') + self.AddElement(parent, 'dt', {}, os_header) + element = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre-wrap']) + element_text = [] + cnt = 1 + key_name = self._GetRegistryKeyName(policy, is_win) + for item in example_value: + element_text.append( + '%s\\%s\\%d = "%s"' % (key_name, policy['name'], cnt, item)) + cnt = cnt + 1 + self.AddText(element, '\n'.join(element_text)) + + def _GetRegistryKeyName(self, policy, is_win): + use_recommended_key = self.CanBeRecommended(policy) and not \ + self.CanBeMandatory(policy) + platform = 'win' if is_win else 'chrome_os' + key = 'reg_recommended_key_name' if use_recommended_key else \ + 'reg_mandatory_key_name' + return self.config['win_config'][platform][key] + + def _GetOmaUriPath(self, policy): + product = 'googlechrome' if self.config['build'] == 'chrome' else 'chromium' + group = '~' + policy['group'] if 'group' in policy else '' + return '.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~%s%s\\%s' % ( + product, group, policy['name']) + + def _AddListExampleAndroidLinux(self, parent, policy): + '''Adds an example value for Android/Linux of a 'list' policy to a DOM node. + + Args: + parent: The DOM node for which the example will be added. + policy: A policy of type 'list', for which the Android/Linux example value + is generated. + ''' + example_value = policy['example_value'] + self.AddElement(parent, 'dt', {}, 'Android/Linux:') + element = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre-wrap']) + self.AddText( + element, + '[\n%s\n]' % ',\n'.join(' "%s"' % item for item in example_value)) + + def _AddListExample(self, parent, policy): + r'''Adds the example value of a 'list' policy to a DOM node. Example output: + <dl> + <dt>Windows (Windows clients):</dt> + <dd> + Software\Policies\Chromium\URLAllowlist\0 = "www.example.com" + Software\Policies\Chromium\URLAllowlist\1 = "www.google.com" + </dd> + <dt>Windows (Chromium OS clients):</dt> + <dd> + Software\Policies\ChromiumOS\URLAllowlist\0 = "www.example.com" + Software\Policies\ChromiumOS\URLAllowlist\1 = "www.google.com" + </dd> + <dt>Android/Linux:</dt> + <dd> + [ + "www.example.com", + "www.google.com" + ] + </dd> + <dt>Mac:</dt> + <dd> + <array> + <string>www.example.com</string> + <string>www.google.com</string> + </array> + </dd> + </dl> + + Args: + parent: The DOM node for which the example will be added. + policy: The data structure of a policy. + ''' + examples = self._AddStyledElement(parent, 'dl', ['dd dl']) + if self.IsPolicySupportedOnWindows(policy): + self._AddListExampleWindowsChromeOS(examples, policy, True) + if self.IsPolicyOrItemSupportedOnPlatform( + policy, 'chrome_os', management='active_directory'): + self._AddListExampleWindowsChromeOS(examples, policy, False) + if (self.IsPolicyOrItemSupportedOnPlatform(policy, 'android') or + self.IsPolicyOrItemSupportedOnPlatform(policy, 'linux')): + self._AddListExampleAndroidLinux(examples, policy) + if self.IsPolicyOrItemSupportedOnPlatform(policy, 'mac'): + self._AddListExampleMac(examples, policy) + + def _PythonObjectToPlist(self, obj, indent=''): + '''Converts a python object to an equivalent XML plist. + + Returns a list of lines.''' + obj_type = type(obj) + if obj_type == bool: + return ['%s<%s/>' % (indent, 'true' if obj else 'false')] + elif obj_type == int: + return ['%s<integer>%s</integer>' % (indent, obj)] + elif obj_type == str: + return ['%s<string>%s</string>' % (indent, escape(obj))] + elif obj_type == list: + result = ['%s<array>' % indent] + for item in obj: + result += self._PythonObjectToPlist(item, indent + ' ') + result.append('%s</array>' % indent) + return result + elif obj_type == dict: + result = ['%s<dict>' % indent] + for key in sorted(obj.keys()): + result.append('%s<key>%s</key>' % (indent + ' ', key)) + result += self._PythonObjectToPlist(obj[key], indent + ' ') + result.append('%s</dict>' % indent) + return result + else: + raise Exception('Invalid object to convert: %s' % obj) + + def _AddDictionaryExampleMac(self, parent, policy): + '''Adds an example value for Mac of a 'dict' or 'external' policy to a DOM + node. + + Args: + parent: The DOM node for which the example will be added. + policy: A policy of type 'dict', for which the Mac example value + is generated. + ''' + example_value = policy['example_value'] + self.AddElement(parent, 'dt', {}, 'Mac:') + mac = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre-wrap']) + mac_text = ['<key>%s</key>' % (policy['name'])] + mac_text += self._PythonObjectToPlist(example_value) + self.AddText(mac, '\n'.join(mac_text)) + + def _AddDictionaryExampleWindowsChromeOS(self, parent, policy, is_win): + '''Adds an example value for Windows of a 'dict' or 'external' policy to a + DOM node. + + Args: + parent: The DOM node for which the example will be added. + policy: A policy of type 'dict', for which the Windows example value + is generated. + ''' + os_header = self.GetLocalizedMessage('win_example_value') if is_win else \ + self.GetLocalizedMessage('chrome_os_example_value') + self.AddElement(parent, 'dt', {}, os_header) + element = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre-wrap']) + key_name = self._GetRegistryKeyName(policy, is_win) + # Explicitly specify separators since defaults depend on python version. + example = json.dumps(policy['example_value'], + indent=2, + sort_keys=True, + separators=(", ", ": ")) + self.AddText(element, '%s\\%s = %s' % (key_name, policy['name'], example)) + + def _AddDictionaryExampleAndroidLinux(self, parent, policy): + '''Adds an example value for Android/Linux of a 'dict' or 'external' policy + to a DOM node. + + Args: + parent: The DOM node for which the example will be added. + policy: A policy of type 'dict', for which the Android/Linux example value + is generated. + ''' + self.AddElement(parent, 'dt', {}, 'Android/Linux:') + element = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre-wrap']) + # Explicitly specify separators since defaults depend on python version. + example = json.dumps(policy['example_value'], + indent=2, + sort_keys=True, + separators=(", ", ": ")) + self.AddText(element, '%s: %s' % (policy['name'], example)) + + def _AddDictionaryExample(self, parent, policy): + '''Adds the example value of a 'dict' or 'external' policy to a DOM node. + + Example output: + <dl> + <dt>Windows (Windows clients):</dt> + <dd> + Software\Policies\Chromium\ProxySettings = { + "ProxyMode": "direct" + } + </dd> + <dt>Windows (Chromium OS clients):</dt> + <dd> + Software\Policies\ChromiumOS\ProxySettings = { + "ProxyMode": "direct" + } + </dd> + <dt>Android/Linux:</dt> + <dd> + ProxySettings: { + "ProxyMode": "direct" + } + </dd> + <dt>Mac:</dt> + <dd> + <key>ProxySettings</key> + <dict> + <key>ProxyMode</key> + <string>direct</string> + </dict> + </dd> + </dl> + + Args: + parent: The DOM node for which the example will be added. + policy: The data structure of a policy. + ''' + examples = self._AddStyledElement(parent, 'dl', ['dd dl']) + if self.IsPolicySupportedOnWindows(policy): + self._AddDictionaryExampleWindowsChromeOS(examples, policy, True) + if self.IsPolicyOrItemSupportedOnPlatform( + policy, 'chrome_os', management='active_directory'): + self._AddDictionaryExampleWindowsChromeOS(examples, policy, False) + if (self.IsPolicyOrItemSupportedOnPlatform(policy, 'android') or + self.IsPolicyOrItemSupportedOnPlatform(policy, 'linux')): + self._AddDictionaryExampleAndroidLinux(examples, policy) + if self.IsPolicyOrItemSupportedOnPlatform(policy, 'mac'): + self._AddDictionaryExampleMac(examples, policy) + + def _AddIntuneExample(self, parent, policy): + example_value = policy['example_value'] + policy_type = policy['type'] + + container = self._AddStyledElement(parent, 'dl', []) + self.AddElement(container, 'dt', {}, 'Windows (Intune):') + + if policy_type == 'main': + self._AddStyledElement( + container, + 'dd', ['.monospace', '.pre-wrap'], + text='<enabled/>' if example_value else '<disabled/>') + return + + self._AddStyledElement( + container, 'dd', ['.monospace', '.pre-wrap'], text='<enabled/>') + if policy_type == 'list': + values = [ + '%s%s' % (index, value) + for index, value in enumerate(example_value, start=1) + ] + self._AddStyledElement( + container, + 'dd', ['.monospace', '.pre-wrap'], + text='<data id="%s" value="%s"/>' % (policy['name'] + 'Desc', + ''.join(values))) + return + elif policy_type == 'int' or policy_type == 'int-enum': + self._AddStyledElement( + container, + 'dd', ['.monospace', '.pre-wrap'], + text='<data id="%s" value="%s"/>' % (policy['name'], example_value)) + else: + self._AddStyledElement( + container, + 'dd', ['.monospace', '.pre-wrap'], + text='<data id="%s" value="%s"/>' % (policy['name'], + json.dumps(example_value)[1:-1])) + return + + def _AddExample(self, parent, policy): + '''Adds the HTML DOM representation of the example value of a policy to + a DOM node. It is simple text for boolean policies, like + '0x00000001 (Windows), true (Linux), true (Android), <true /> (Mac)' + in case of boolean policies, but it may also contain other HTML elements. + (See method _AddListExample.) + + Args: + parent: The DOM node for which the example will be added. + policy: The data structure of a policy. + + Raises: + Exception: If the type of the policy is unknown or the example value + of the policy is out of its expected range. + ''' + example_value = policy['example_value'] + policy_type = policy['type'] + if policy_type == 'main': + pieces = [] + if self.IsPolicySupportedOnWindows(policy) or \ + self.IsPolicyOrItemSupportedOnPlatform(policy, 'chrome_os', + management='active_directory'): + value = '0x00000001' if example_value else '0x00000000' + pieces.append(value + ' (Windows)') + if self.IsPolicyOrItemSupportedOnPlatform(policy, 'linux'): + value = 'true' if example_value else 'false' + pieces.append(value + ' (Linux)') + if self.IsPolicyOrItemSupportedOnPlatform(policy, 'android'): + value = 'true' if example_value else 'false' + pieces.append(value + ' (Android)') + if self.IsPolicyOrItemSupportedOnPlatform(policy, 'mac'): + value = '<true />' if example_value else '<false />' + pieces.append(value + ' (Mac)') + self.AddText(parent, ', '.join(pieces)) + elif policy_type == 'string': + self.AddText(parent, '"%s"' % example_value) + elif policy_type in ('int', 'int-enum'): + pieces = [] + if self.IsPolicySupportedOnWindows(policy) or \ + self.IsPolicyOrItemSupportedOnPlatform(policy, 'chrome_os', + management='active_directory'): + pieces.append('0x%08x (Windows)' % example_value) + if self.IsPolicyOrItemSupportedOnPlatform(policy, 'linux'): + pieces.append('%d (Linux)' % example_value) + if self.IsPolicyOrItemSupportedOnPlatform(policy, 'android'): + pieces.append('%d (Android)' % example_value) + if self.IsPolicyOrItemSupportedOnPlatform(policy, 'mac'): + pieces.append('%d (Mac)' % example_value) + self.AddText(parent, ', '.join(pieces)) + elif policy_type == 'string-enum': + self.AddText(parent, '"%s"' % (example_value)) + elif policy_type in ('list', 'string-enum-list'): + self._AddListExample(parent, policy) + elif policy_type in ('dict', 'external'): + self._AddDictionaryExample(parent, policy) + else: + raise Exception('Unknown policy type: ' + policy_type) + + if self.IsPolicySupportedOnWindows(policy): + self._AddIntuneExample(parent, policy) + + def _AddPolicyAttribute(self, + dl, + term_id, + definition=None, + definition_style=None): + '''Adds a term-definition pair to a HTML DOM <dl> node. This method is + used by _AddPolicyDetails. Its result will have the form of: + <dt style="...">...</dt> + <dd style="...">...</dd> + + Args: + dl: The DOM node of the <dl> list. + term_id: A key to self._STRINGS[] which specifies the term of the pair. + definition: The text of the definition. (Optional.) + definition_style: List of references to values self._STYLE[] that specify + the CSS stylesheet of the <dd> (definition) element. + + Returns: + The DOM node representing the definition <dd> element. + ''' + # Avoid modifying the default value of definition_style. + if definition_style == None: + definition_style = [] + term = self.GetLocalizedMessage(term_id) + self._AddStyledElement(dl, 'dt', ['dt'], {}, term) + return self._AddStyledElement(dl, 'dd', definition_style, {}, definition) + + def _AddSupportedOnList(self, parent, supported_on_list): + '''Creates a HTML list containing the platforms, products and versions + that are specified in the list of supported_on. + + Args: + parent: The DOM node for which the list will be added. + supported_on_list: The list of supported products, as a list of + dictionaries. + ''' + ul = self._AddStyledElement(parent, 'ul', ['ul']) + for supported_on in supported_on_list: + text = [] + product = supported_on['product'] + platform = supported_on['platform'] + text.append(self._PRODUCT_MAP[product]) + text.append('(%s)' % (self._PLATFORM_MAP[platform])) + if supported_on['since_version']: + since_version = self.GetLocalizedMessage('since_version') + text.append(since_version.replace('$6', supported_on['since_version'])) + if supported_on['until_version']: + until_version = self.GetLocalizedMessage('until_version') + text.append(until_version.replace('$6', supported_on['until_version'])) + # Add the list element: + self.AddElement(ul, 'li', {}, ' '.join(text)) + + def _AddRangeRestrictionsList(self, parent, schema): + '''Creates a HTML list containing range restrictions for an integer type + policy. + + Args: + parent: The DOM node for which the list will be added. + schema: The schema of the policy. + ''' + ul = self._AddStyledElement(parent, 'ul', ['ul']) + if 'minimum' in schema: + text_min = self.GetLocalizedMessage('range_minimum') + self.AddElement(ul, 'li', {}, text_min + str(schema['minimum'])) + if 'maximum' in schema: + text_max = self.GetLocalizedMessage('range_maximum') + self.AddElement(ul, 'li', {}, text_max + str(schema['maximum'])) + + def _AddPolicyDetails(self, parent, policy): + '''Adds the list of attributes of a policy to the HTML DOM node parent. + It will have the form: + <dl> + <dt>Attribute:</dt><dd>Description</dd> + ... + </dl> + + Args: + parent: A DOM element for which the list will be added. + policy: The data structure of the policy. + ''' + + dl = self.AddElement(parent, 'dl') + data_type = [self._TYPE_MAP[policy['type']]] + qualified_types = [] + is_complex_policy = False + if (self.IsPolicyOrItemSupportedOnPlatform(policy, 'android') and + self._RESTRICTION_TYPE_MAP.get(policy['type'], None)): + qualified_types.append( + 'Android:%s' % self._RESTRICTION_TYPE_MAP[policy['type']]) + if policy['type'] in ('dict', 'external', 'list'): + is_complex_policy = True + if ((self.IsPolicySupportedOnWindows(policy) or + self.IsPolicyOrItemSupportedOnPlatform( + policy, 'chrome_os', management='active_directory')) and + self._REG_TYPE_MAP.get(policy['type'], None)): + qualified_types.append('Windows:%s' % self._REG_TYPE_MAP[policy['type']]) + if policy['type'] in ('dict', 'external'): + is_complex_policy = True + if qualified_types: + data_type.append('[%s]' % ', '.join(qualified_types)) + if is_complex_policy: + data_type.append( + '(%s)' % self.GetLocalizedMessage('complex_policies_on_windows')) + self._AddPolicyAttribute(dl, 'data_type', ' '.join(data_type)) + if self.IsPolicySupportedOnWindows(policy): + registry_key_name = self._GetRegistryKeyName(policy, True) + self._AddPolicyAttribute(dl, 'win_reg_loc', + registry_key_name + '\\' + policy['name'], + ['.monospace']) + self._AddPolicyAttribute(dl, 'oma_uri', self._GetOmaUriPath(policy), + ['.monospace']) + + if self.IsPolicyOrItemSupportedOnPlatform( + policy, 'chrome_os', management='active_directory'): + key_name = self._GetRegistryKeyName(policy, False) + self._AddPolicyAttribute(dl, 'chrome_os_reg_loc', + key_name + '\\' + policy['name'], ['.monospace']) + if (self.IsPolicyOrItemSupportedOnPlatform(policy, 'linux') or + self.IsPolicyOrItemSupportedOnPlatform(policy, 'mac')): + self._AddPolicyAttribute(dl, 'mac_linux_pref_name', policy['name'], + ['.monospace']) + if self.IsPolicyOrItemSupportedOnPlatform( + policy, 'android', product='chrome'): + self._AddPolicyAttribute(dl, 'android_restriction_name', policy['name'], + ['.monospace']) + if self.IsPolicyOrItemSupportedOnPlatform( + policy, 'android', product='webview'): + restriction_prefix = self.config['android_webview_restriction_prefix'] + self._AddPolicyAttribute(dl, 'android_webview_restriction_name', + restriction_prefix + policy['name'], + ['.monospace']) + dd = self._AddPolicyAttribute(dl, 'supported_on') + self._AddSupportedOnList(dd, policy['supported_on']) + dd = self._AddPolicyAttribute(dl, 'supported_features') + self._AddFeatures(dd, policy) + dd = self._AddPolicyAttribute(dl, 'description') + self._AddDescription(dd, policy) + if 'schema' in policy: + if self.SchemaHasRangeRestriction(policy['schema']): + dd = self._AddPolicyAttribute(dl, 'policy_restriction') + self._AddRangeRestrictionsList(dd, policy['schema']) + if 'arc_support' in policy: + dd = self._AddPolicyAttribute(dl, 'arc_support') + self._AddParagraphs(dd, policy['arc_support']) + if policy['type'] in ('dict', 'external') and 'schema' in policy: + self._AddSchema(dl, policy['schema']) + if 'validation_schema' in policy: + self._AddSchema(dl, policy['validation_schema']) + if 'description_schema' in policy: + self._AddSchema(dl, policy['description_schema']) + if 'url_schema' in policy: + dd = self._AddPolicyAttribute(dl, 'url_schema') + self._AddTextWithLinks(dd, policy['url_schema']) + if (self.IsPolicySupportedOnWindows(policy) or + self.IsPolicyOrItemSupportedOnPlatform(policy, 'linux') or + self.IsPolicyOrItemSupportedOnPlatform(policy, 'android') or + self.IsPolicyOrItemSupportedOnPlatform(policy, 'mac') or + self.IsPolicyOrItemSupportedOnPlatform( + policy, 'chrome_os', management='active_directory')): + # Don't add an example for Google cloud managed ChromeOS policies. + dd = self._AddPolicyAttribute(dl, 'example_value') + self._AddExample(dd, policy) + if 'atomic_group' in policy: + dd = self._AddPolicyAttribute(dl, 'policy_atomic_group') + policy_group_ref = './policy-list-3/atomic_groups' + if 'local' in self.config and self.config['local']: + policy_group_ref = './chrome_policy_atomic_groups_list.html' + self.AddText(dd, self.GetLocalizedMessage('policy_in_atomic_group') + ' ') + self.AddElement(dd, 'a', + {'href': policy_group_ref + '#' + policy['atomic_group']}, + policy['atomic_group']) + + def _AddPolicyRow(self, parent, policy): + '''Adds a row for the policy in the summary table. + + Args: + parent: The DOM node of the summary table. + policy: The data structure of the policy. + ''' + tr = self._AddStyledElement(parent, 'tr', ['tr']) + indent = 'padding-left: %dpx;' % (7 + self._indent_level * 14) + if policy['type'] != 'group': + # Normal policies get two columns with name and caption. + name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'], + {'style': indent}) + self.AddElement(name_td, 'a', {'href': '#' + policy['name']}, + policy['name']) + self._AddStyledElement(tr, 'td', ['td', 'td.right'], {}, + policy['caption']) + else: + # Groups get one column with caption. + name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'], { + 'style': indent, + 'colspan': '2' + }) + self.AddElement(name_td, 'a', {'href': '#' + policy['name']}, + policy['caption']) + + def _AddPolicySection(self, parent, policy): + '''Adds a section about the policy in the detailed policy listing. + + Args: + parent: The DOM node of the <div> of the detailed policy list. + policy: The data structure of the policy. + ''' + # Set style according to group nesting level. + indent = 'margin-left: %dpx' % (self._indent_level * 28) + if policy['type'] == 'group': + heading = 'h2' + else: + heading = 'h3' + parent2 = self.AddElement(parent, 'div', {'style': indent}) + + h2 = self.AddElement(parent2, heading) + self.AddElement(h2, 'a', {'name': policy['name']}) + if policy['type'] != 'group': + # Normal policies get a full description. + policy_name_text = policy['name'] + if 'deprecated' in policy and policy['deprecated'] == True: + policy_name_text += " (" + policy_name_text += self.GetLocalizedMessage('deprecated') + ")" + self.AddText(h2, policy_name_text) + self.AddElement(parent2, 'span', {}, policy['caption']) + self._AddPolicyDetails(parent2, policy) + else: + # Groups get a more compact description. + self.AddText(h2, policy['caption']) + self._AddStyledElement(parent2, 'div', ['div.group_desc'], {}, + policy['desc']) + self.AddElement(parent2, 'a', {'href': '#top'}, + self.GetLocalizedMessage('back_to_top')) + + def SchemaHasRangeRestriction(self, schema): + if 'maximum' in schema: + return True + if 'minimum' in schema: + return schema['minimum'] != 0 + return False + + def _BeginTemplate(self, intro_message_id, banner_message_id): + # Add a <div> for the summary section. + if self._GetChromiumVersionString() is not None: + self.AddComment(self._main_div, self.config['build'] + \ + ' version: ' + self._GetChromiumVersionString()) + + banner_div = self._AddStyledElement(self._main_div, 'div', ['div.banner'], + {}, '') + self._AddParagraphs(banner_div, self.GetLocalizedMessage(banner_message_id)) + summary_div = self.AddElement(self._main_div, 'div') + self.AddElement(summary_div, 'a', {'name': 'top'}) + self.AddElement(summary_div, 'br') + self._AddParagraphs(summary_div, self.GetLocalizedMessage(intro_message_id)) + self.AddElement(summary_div, 'br') + self.AddElement(summary_div, 'br') + self.AddElement(summary_div, 'br') + # Add the summary table of policies. + summary_table = self._AddStyledElement(summary_div, 'table', ['table']) + # Add the first row. + thead = self.AddElement(summary_table, 'thead') + tr = self._AddStyledElement(thead, 'tr', ['tr']) + self._AddStyledElement(tr, 'td', ['td', 'td.left', 'thead td'], {}, + self.GetLocalizedMessage('name_column_title')) + self._AddStyledElement(tr, 'td', ['td', 'td.right', 'thead td'], {}, + self.GetLocalizedMessage('description_column_title')) + self._summary_tbody = self.AddElement(summary_table, 'tbody') + + # Add a <div> for the detailed policy listing. + self._details_div = self.AddElement(self._main_div, 'div') + + # + # Implementation of abstract methods of TemplateWriter: + # + + def IsDeprecatedPolicySupported(self, policy): + return True + + def WritePolicy(self, policy): + self._AddPolicyRow(self._summary_tbody, policy) + self._AddPolicySection(self._details_div, policy) + + def BeginPolicyGroup(self, group): + self.WritePolicy(group) + self._indent_level += 1 + + def EndPolicyGroup(self): + self._indent_level -= 1 + + def BeginTemplate(self): + self._BeginTemplate('intro', 'banner') + + def Init(self): + dom_impl = minidom.getDOMImplementation('') + self._doc = dom_impl.createDocument(None, 'html', None) + body = self.AddElement(self._doc.documentElement, 'body') + self._main_div = self.AddElement(body, 'div') + self._indent_level = 0 + + # Human-readable names of supported platforms. + self._PLATFORM_MAP = { + 'win': 'Windows', + 'mac': 'Mac', + 'linux': 'Linux', + 'chrome_os': self.config['os_name'], + 'android': 'Android', + 'win7': 'Windows 7', + 'ios': 'iOS', + } + # Human-readable names of supported products. + self._PRODUCT_MAP = { + 'chrome': self.config['app_name'], + 'chrome_frame': self.config['frame_name'], + 'chrome_os': self.config['os_name'], + 'webview': self.config['webview_name'], + } + # Human-readable names of supported features. Each supported feature has + # a 'doc_feature_X' entry in |self.messages|. + self._FEATURE_MAP = {} + for message in self.messages: + if message.startswith('doc_feature_'): + self._FEATURE_MAP[message[12:]] = self.messages[message]['text'] + # Human-readable names of types. + self._TYPE_MAP = { + 'string': 'String', + 'int': 'Integer', + 'main': 'Boolean', + 'int-enum': 'Integer', + 'string-enum': 'String', + 'list': 'List of strings', + 'string-enum-list': 'List of strings', + 'dict': 'Dictionary', + 'external': 'External data reference', + } + self._REG_TYPE_MAP = { + 'string': 'REG_SZ', + 'int': 'REG_DWORD', + 'main': 'REG_DWORD', + 'int-enum': 'REG_DWORD', + 'string-enum': 'REG_SZ', + 'dict': 'REG_SZ', + 'external': 'REG_SZ', + } + self._RESTRICTION_TYPE_MAP = { + 'int-enum': 'choice', + 'string-enum': 'choice', + 'list': 'string', + 'string-enum-list': 'multi-select', + 'dict': 'string', + 'external': 'string', + } + # The CSS style-sheet used for the document. It will be used in Google + # Sites, which strips class attributes from HTML tags. To work around this, + # the style-sheet is a dictionary and the style attributes will be added + # "by hand" for each element. + self._STYLE = { + 'div.banner': 'background-color: rgb(244,204,204); font-size: x-large; ' + 'border: 1px solid red; padding: 20px; ' + 'text-align: center;', + 'table': 'border-style: none; border-collapse: collapse;', + 'tr': 'height: 0px;', + 'td': 'border: 1px dotted rgb(170, 170, 170); padding: 7px; ' + 'vertical-align: top; width: 236px; height: 15px;', + 'thead td': 'font-weight: bold;', + 'td.left': 'width: 200px;', + 'td.right': 'width: 100%;', + 'dt': 'font-weight: bold;', + 'dd dl': 'margin-top: 0px; margin-bottom: 0px;', + '.monospace': 'font-family: monospace;', + '.pre-wrap': 'white-space: pre-wrap;', + 'div.note': 'border: 2px solid black; padding: 5px; margin: 5px;', + 'div.group_desc': 'margin-top: 20px; margin-bottom: 20px;', + 'ul': 'padding-left: 0px; margin-left: 0px;' + } + + def GetTemplateText(self): + # Return the text representation of the main <div> tag. + return self._main_div.toxml() + # To get a complete HTML file, use the following. + # return self._doc.toxml() diff --git a/chromium/components/policy/tools/template_writers/writers/doc_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/doc_writer_unittest.py new file mode 100755 index 00000000000..8be4dd376c9 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/doc_writer_unittest.py @@ -0,0 +1,1835 @@ +#!/usr/bin/env python3 +# 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. +'''Unit tests for writers.doc_writer''' + +import json +import os +import sys +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +import unittest +from xml.dom import minidom + +from writers import writer_unittest_common +from writers import doc_writer + + +class MockMessageDictionary: + '''A mock dictionary passed to a writer as the dictionary of + localized messages. + ''' + + # Dictionary of messages. + msg_dict = {} + + +class DocWriterUnittest(writer_unittest_common.WriterUnittestCommon): + '''Unit tests for DocWriter.''' + + def setUp(self): + # Create a writer for the tests. + self.writer = doc_writer.GetWriter( + config={ + 'app_name': 'Chrome', + 'frame_name': 'Chrome Frame', + 'os_name': 'Chrome OS', + 'webview_name': 'WebView', + 'android_webview_restriction_prefix': 'mock.prefix:', + 'win_config': { + 'win': { + 'reg_mandatory_key_name': 'MockKey', + 'reg_recommended_key_name': 'MockKeyRec', + }, + 'chrome_os': { + 'reg_mandatory_key_name': 'MockKeyCrOS', + 'reg_recommended_key_name': 'MockKeyCrOSRec', + }, + }, + 'build': 'test_product', + }) + self.writer.messages = { + 'doc_back_to_top': { + 'text': '_test_back_to_top' + }, + 'doc_complex_policies_on_windows': { + 'text': '_test_complex_policies_win' + }, + 'doc_data_type': { + 'text': '_test_data_type' + }, + 'doc_description': { + 'text': '_test_description' + }, + 'doc_schema': { + 'text': '_test_schema' + }, + 'doc_url_schema': { + 'text': '_test_url_schema' + }, + 'doc_arc_support': { + 'text': '_test_arc_support' + }, + 'doc_description_column_title': { + 'text': '_test_description_column_title' + }, + 'doc_example_value': { + 'text': '_test_example_value' + }, + 'doc_win_example_value': { + 'text': '_test_example_value_win' + }, + 'doc_chrome_os_example_value': { + 'text': '_test_example_value_chrome_os' + }, + 'doc_feature_dynamic_refresh': { + 'text': '_test_feature_dynamic_refresh' + }, + 'doc_feature_can_be_recommended': { + 'text': '_test_feature_recommended' + }, + 'doc_feature_can_be_mandatory': { + 'text': '_test_feature_mandatory' + }, + 'doc_banner': { + 'text': '_test_banner' + }, + 'doc_intro': { + 'text': '_test_intro' + }, + 'doc_mac_linux_pref_name': { + 'text': '_test_mac_linux_pref_name' + }, + 'doc_android_restriction_name': { + 'text': '_test_android_restriction_name' + }, + 'doc_android_webview_restriction_name': { + 'text': '_test_android_webview_restriction_name' + }, + 'doc_note': { + 'text': '_test_note' + }, + 'doc_name_column_title': { + 'text': '_test_name_column_title' + }, + 'doc_not_supported': { + 'text': '_test_not_supported' + }, + 'doc_since_version': { + 'text': '..$6..' + }, + 'doc_supported': { + 'text': '_test_supported' + }, + 'doc_supported_features': { + 'text': '_test_supported_features' + }, + 'doc_supported_on': { + 'text': '_test_supported_on' + }, + 'doc_win_reg_loc': { + 'text': '_test_win_reg_loc' + }, + 'doc_oma_uri': { + 'text': '_test_oma_uri' + }, + 'doc_chrome_os_reg_loc': { + 'text': '_test_chrome_os_reg_loc' + }, + 'doc_bla': { + 'text': '_test_bla' + }, + 'doc_policy_atomic_group': { + 'text': '_test_policy_atomic_group' + }, + 'doc_policy_in_atomic_group': { + 'text': '_test_policy_in_atomic_group' + } + } + self.writer.Init() + + # It is not worth testing the exact content of style attributes. + # Therefore we override them here with shorter texts. + for key in self.writer._STYLE.keys(): + self.writer._STYLE[key] = 'style_%s;' % key + # Add some more style attributes for additional testing. + self.writer._STYLE['key1'] = 'style1;' + self.writer._STYLE['key2'] = 'style2;' + + # Create a DOM document for the tests. + dom_impl = minidom.getDOMImplementation('') + self.doc = dom_impl.createDocument(None, 'root', None) + self.doc_root = self.doc.documentElement + + def testSkeleton(self): + # Test if DocWriter creates the skeleton of the document correctly. + self.writer.BeginTemplate() + self.assertEquals( + self.writer._main_div.toxml(), '<div>' + '<div style="style_div.banner;"><p>_test_banner</p></div>' + '<div>' + '<a name="top"/><br/><p>_test_intro</p><br/><br/><br/>' + '<table style="style_table;">' + '<thead><tr style="style_tr;">' + '<td style="style_td;style_td.left;style_thead td;">' + '_test_name_column_title' + '</td>' + '<td style="style_td;style_td.right;style_thead td;">' + '_test_description_column_title' + '</td>' + '</tr></thead>' + '<tbody/>' + '</table>' + '</div>' + '<div/>' + '</div>') + + def testVersionAnnotation(self): + # Test if DocWriter creates the skeleton of the document correctly. + self.writer.config['version'] = '39.0.0.0' + self.writer.BeginTemplate() + self.assertEquals( + self.writer._main_div.toxml(), '<div>' + '<!--test_product version: 39.0.0.0-->' + '<div style="style_div.banner;"><p>_test_banner</p></div>' + '<div>' + '<a name="top"/><br/><p>_test_intro</p><br/><br/><br/>' + '<table style="style_table;">' + '<thead><tr style="style_tr;">' + '<td style="style_td;style_td.left;style_thead td;">' + '_test_name_column_title' + '</td>' + '<td style="style_td;style_td.right;style_thead td;">' + '_test_description_column_title' + '</td>' + '</tr></thead>' + '<tbody/>' + '</table>' + '</div>' + '<div/>' + '</div>') + + def testGetLocalizedMessage(self): + # Test if localized messages are retrieved correctly. + self.writer.messages = {'doc_hello_world': {'text': 'hello, vilag!'}} + self.assertEquals( + self.writer.GetLocalizedMessage('hello_world'), 'hello, vilag!') + + def testAddStyledElement(self): + # Test function DocWriter.AddStyledElement() + + # Test the case of zero style. + e1 = self.writer._AddStyledElement(self.doc_root, 'z', [], {'a': 'b'}, + 'text') + self.assertEquals(e1.toxml(), '<z a="b">text</z>') + + # Test the case of one style. + e2 = self.writer._AddStyledElement(self.doc_root, 'z', ['key1'], {'a': 'b'}, + 'text') + self.assertEquals(e2.toxml(), '<z a="b" style="style1;">text</z>') + + # Test the case of two styles. + e3 = self.writer._AddStyledElement(self.doc_root, 'z', ['key1', 'key2'], + {'a': 'b'}, 'text') + self.assertEquals(e3.toxml(), '<z a="b" style="style1;style2;">text</z>') + + def testAddDescriptionIntEnum(self): + # Test if URLs are replaced and choices of 'int-enum' policies are listed + # correctly. + policy = { + 'type': + 'int-enum', + 'items': [ + { + 'value': 0, + 'caption': 'Disable foo' + }, + { + 'value': 2, + 'caption': 'Solve your problem' + }, + { + 'value': 5, + 'caption': 'Enable bar' + }, + ], + 'desc': + '''This policy disables foo, except in case of bar. +See http://policy-explanation.example.com for more details. +''' + } + self.writer._AddDescription(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), + '''<root><p>This policy disables foo, except in case of bar. +See <a href="http://policy-explanation.example.com">http://policy-explanation.example.com</a> for more details. +</p><ul><li>0 = Disable foo</li><li>2 = Solve your problem</li><li>5 = Enable bar</li></ul></root>''' + ) + + def testAddDescriptionStringEnum(self): + # Test if URLs are replaced and choices of 'int-enum' policies are listed + # correctly. + policy = { + 'type': + 'string-enum', + 'items': [ + { + 'value': "one", + 'caption': 'Disable foo' + }, + { + 'value': "two", + 'caption': 'Solve your problem' + }, + { + 'value': "three", + 'caption': 'Enable bar' + }, + ], + 'desc': + '''This policy disables foo, except in case of bar. +See http://policy-explanation.example.com for more details. +''' + } + self.writer._AddDescription(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), + '''<root><p>This policy disables foo, except in case of bar. +See <a href="http://policy-explanation.example.com">http://policy-explanation.example.com</a> for more details. +</p><ul><li>"one" = Disable foo</li><li>"two" = Solve your problem</li><li>"three" = Enable bar</li></ul></root>''' + ) + + def testAddSchema(self): + # Test if the schema of a policy is handled correctly. + policy = { + 'type': 'dict', + 'schema': { + 'properties': { + 'foo': { + 'type': 'integer' + } + }, + 'type': 'object' + } + } + self.writer._AddSchema(self.doc_root, policy['schema']) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<dt style="style_dt;">_test_schema</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">{\n' + ' "properties": {\n' + ' "foo": {\n' + ' "type": "integer"\n' + ' }\n' + ' }, \n' + ' "type": "object"\n' + '}</dd></root>') + + def testAddUrlSchema(self): + # Test if the expanded schema description of a policy is handled correctly. + policy = {'url_schema': 'https://example.com/details'} + self.writer._AddTextWithLinks(self.doc_root, policy['url_schema']) + self.assertEquals( + self.doc_root.toxml(), + '<root><a href="https://example.com/details">https://example.com/details</a></root>' + ) + + def testAddFeatures(self): + # Test if the list of features of a policy is handled correctly. + policy = { + 'features': { + 'spaceship_docking': False, + 'dynamic_refresh': True, + 'can_be_recommended': True, + } + } + self.writer._FEATURE_MAP = { + 'can_be_recommended': 'Can Be Recommended', + 'dynamic_refresh': 'Dynamic Refresh', + 'spaceship_docking': 'Spaceship Docking', + } + self.writer._AddFeatures(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + 'Can Be Recommended: _test_supported, ' + 'Dynamic Refresh: _test_supported, ' + 'Spaceship Docking: _test_not_supported' + '</root>') + + def testAddListExample(self): + policy = { + 'name': + 'PolicyName', + 'example_value': ['Foo', 'Bar'], + 'supported_on': [{ + 'platform': 'win' + }, { + 'platform': 'mac' + }, { + 'platform': 'linux' + }, { + 'platform': 'chrome_os' + }] + } + self.writer._AddListExample(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<dl style="style_dd dl;">' + '<dt>_test_example_value_win</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + 'MockKey\\PolicyName\\1 = "Foo"\n' + 'MockKey\\PolicyName\\2 = "Bar"' + '</dd>' + '<dt>_test_example_value_chrome_os</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + 'MockKeyCrOS\\PolicyName\\1 = "Foo"\n' + 'MockKeyCrOS\\PolicyName\\2 = "Bar"' + '</dd>' + '<dt>Android/Linux:</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + '[\n' + ' "Foo",\n' + ' "Bar"\n' + ']' + '</dd>' + '<dt>Mac:</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + '<array>\n' + ' <string>Foo</string>\n' + ' <string>Bar</string>\n' + '</array>' + '</dd>' + '</dl>' + '</root>') + + def testBoolExample(self): + # Test representation of boolean example values. + policy = { + 'name': + 'PolicyName', + 'type': + 'main', + 'example_value': + True, + 'supported_on': [{ + 'platform': 'win' + }, { + 'platform': 'mac' + }, { + 'platform': 'linux' + }, { + 'platform': 'android' + }] + } + e1 = self.writer.AddElement(self.doc_root, 'e1') + self.writer._AddExample(e1, policy) + self.assertEquals( + e1.toxml(), '<e1>0x00000001 (Windows),' + ' true (Linux), true (Android),' + ' <true /> (Mac)' + '<dl><dt>Windows (Intune):</dt>' + '<dd style="style_.monospace;style_.pre-wrap;"><enabled/></dd></dl>' + '</e1>') + + policy = { + 'name': + 'PolicyName', + 'type': + 'main', + 'example_value': + False, + 'supported_on': [{ + 'platform': 'win' + }, { + 'platform': 'mac' + }, { + 'platform': 'linux' + }, { + 'platform': 'android' + }] + } + e2 = self.writer.AddElement(self.doc_root, 'e2') + self.writer._AddExample(e2, policy) + self.assertEquals( + e2.toxml(), '<e2>0x00000000 (Windows),' + ' false (Linux), false (Android),' + ' <false /> (Mac)' + '<dl><dt>Windows (Intune):</dt>' + '<dd style="style_.monospace;style_.pre-wrap;"><disabled/></dd></dl>' + '</e2>') + + def testIntEnumExample(self): + # Test representation of 'int-enum' example values. + policy = { + 'name': + 'PolicyName', + 'type': + 'int-enum', + 'example_value': + 16, + 'supported_on': [{ + 'platform': 'win' + }, { + 'platform': 'mac' + }, { + 'platform': 'linux' + }, { + 'platform': 'android' + }] + } + self.writer._AddExample(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), + '<root>0x00000010 (Windows), 16 (Linux), 16 (Android), 16 (Mac)' + '<dl><dt>Windows (Intune):</dt>' + '<dd style="style_.monospace;style_.pre-wrap;"><enabled/></dd>' + '<dd style="style_.monospace;style_.pre-wrap;"><data id="PolicyName" value="16"/></dd></dl>' + '</root>') + + def testStringEnumExample(self): + # Test representation of 'string-enum' example values. + policy = { + 'name': 'PolicyName', + 'type': 'string-enum', + 'example_value': "wacky", + 'supported_on': [] + } + self.writer._AddExample(self.doc_root, policy) + self.assertEquals(self.doc_root.toxml(), '<root>"wacky"</root>') + + def testListExample(self): + # Test representation of 'list' example values. + policy = { + 'name': 'PolicyName', + 'type': 'list', + 'example_value': ['one', 'two'], + 'supported_on': [{ + 'platform': 'linux' + }] + } + self.writer._AddExample(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root><dl style="style_dd dl;">' + '<dt>Android/Linux:</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + '[\n' + ' "one",\n' + ' "two"\n' + ']' + '</dd></dl></root>') + + def testStringEnumListExample(self): + # Test representation of 'string-enum-list' example values. + policy = { + 'name': 'PolicyName', + 'type': 'string-enum-list', + 'example_value': ['one', 'two'], + 'supported_on': [{ + 'platform': 'linux' + }] + } + self.writer._AddExample(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root><dl style="style_dd dl;">' + '<dt>Android/Linux:</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + '[\n' + ' "one",\n' + ' "two"\n' + ']' + '</dd></dl></root>') + + def testStringExample(self): + # Test representation of 'string' example values. + policy = { + 'name': 'PolicyName', + 'type': 'string', + 'example_value': 'awesome-example', + 'supported_on': [] + } + self.writer._AddExample(self.doc_root, policy) + self.assertEquals(self.doc_root.toxml(), + '<root>"awesome-example"</root>') + + def testIntExample(self): + # Test representation of 'int' example values. + policy = { + 'name': + 'PolicyName', + 'type': + 'int', + 'example_value': + 26, + 'supported_on': [{ + 'platform': 'win' + }, { + 'platform': 'mac' + }, { + 'platform': 'linux' + }, { + 'platform': 'android' + }] + } + self.writer._AddExample(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), + '<root>0x0000001a (Windows), 26 (Linux), 26 (Android), 26 (Mac)' + '<dl><dt>Windows (Intune):</dt>' + '<dd style="style_.monospace;style_.pre-wrap;"><enabled/></dd>' + '<dd style="style_.monospace;style_.pre-wrap;"><data id="PolicyName" value="26"/></dd></dl>' + '</root>') + + def testAddPolicyAttribute(self): + # Test creating a policy attribute term-definition pair. + self.writer._AddPolicyAttribute(self.doc_root, 'bla', 'hello, world', + ['key1']) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<dt style="style_dt;">_test_bla</dt>' + '<dd style="style1;">hello, world</dd>' + '</root>') + + def testAddPolicyDetails(self): + # Test if the definition list (<dl>) of policy details is created correctly. + policy = { + 'type': + 'main', + 'name': + 'TestPolicyName', + 'caption': + 'TestPolicyCaption', + 'desc': + 'TestPolicyDesc', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'win', + 'since_version': '8', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'mac', + 'since_version': '8', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'linux', + 'since_version': '8', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'android', + 'since_version': '30', + 'until_version': '', + }, + { + 'product': 'webview', + 'platform': 'android', + 'since_version': '47', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'chrome_os', + 'since_version': '55', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': + False, + 'arc_support': + 'TestArcSupportNote' + } + self.writer._AddPolicyDetails(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root><dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>Boolean [Windows:REG_DWORD]</dd>' + '<dt style="style_dt;">_test_win_reg_loc</dt>' + '<dd style="style_.monospace;">MockKey\TestPolicyName</dd>' + '<dt style="style_dt;">_test_oma_uri</dt>' + '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\TestPolicyName</dd>' + '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>' + '<dd style="style_.monospace;">MockKeyCrOS\TestPolicyName</dd>' + '<dt style="style_dt;">_test_mac_linux_pref_name</dt>' + '<dd style="style_.monospace;">TestPolicyName</dd>' + '<dt style="style_dt;">_test_android_restriction_name</dt>' + '<dd style="style_.monospace;">TestPolicyName</dd>' + '<dt style="style_dt;">_test_android_webview_restriction_name</dt>' + '<dd style="style_.monospace;">mock.prefix:TestPolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome (Windows) ..8..</li>' + '<li>Chrome (Mac) ..8..</li>' + '<li>Chrome (Linux) ..8..</li>' + '<li>Chrome (Android) ..30..</li>' + '<li>WebView (Android) ..47..</li>' + '<li>Chrome (Chrome OS) ..55..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>' + '<dt style="style_dt;">_test_description</dt><dd><p>TestPolicyDesc</p></dd>' + '<dt style="style_dt;">_test_arc_support</dt>' + '<dd><p>TestArcSupportNote</p></dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>0x00000000 (Windows), false (Linux),' + ' false (Android), <false /> (Mac)' + '<dl><dt>Windows (Intune):</dt>' + '<dd style="style_.monospace;style_.pre-wrap;"><disabled/></dd></dl>' + '</dd>' + '</dl></root>') + + def testAddPolicyDetailsNoArcSupport(self): + # Test that the entire Android-on-Chrome-OS sub-section is left out when + # 'arc_support' is not specified. + policy = { + 'type': + 'main', + 'name': + 'TestPolicyName', + 'caption': + 'TestPolicyCaption', + 'desc': + 'TestPolicyDesc', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'linux', + 'since_version': '8', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': + False + } + self.writer._AddPolicyDetails(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root><dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>Boolean</dd>' + '<dt style="style_dt;">_test_mac_linux_pref_name</dt>' + '<dd style="style_.monospace;">TestPolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome (Linux) ..8..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>' + '<dt style="style_dt;">_test_description</dt>' + '<dd><p>TestPolicyDesc</p></dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>false (Linux)</dd>' + '</dl></root>') + + def testAddDictPolicyDetails(self): + # Test if the definition list (<dl>) of policy details is created correctly + # for 'dict' policies. + policy = { + 'type': + 'dict', + 'name': + 'TestPolicyName', + 'caption': + 'TestPolicyCaption', + 'desc': + 'TestPolicyDesc', + 'schema': { + 'properties': { + 'foo': { + 'type': 'integer' + } + }, + 'type': 'object' + }, + 'url_schema': + 'https://example.com/details', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'win', + 'since_version': '8', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'mac', + 'since_version': '8', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'linux', + 'since_version': '8', + 'until_version': '', + }, + { + 'product': 'chrome_os', + 'platform': 'chrome_os', + 'since_version': '8', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': { + 'foo': 123 + } + } + self.writer._AddPolicyDetails(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root><dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>Dictionary [Windows:REG_SZ] (_test_complex_policies_win)</dd>' + '<dt style="style_dt;">_test_win_reg_loc</dt>' + '<dd style="style_.monospace;">MockKey\TestPolicyName</dd>' + '<dt style="style_dt;">_test_oma_uri</dt>' + '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\TestPolicyName</dd>' + '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>' + '<dd style="style_.monospace;">MockKeyCrOS\TestPolicyName</dd>' + '<dt style="style_dt;">_test_mac_linux_pref_name</dt>' + '<dd style="style_.monospace;">TestPolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome (Windows) ..8..</li>' + '<li>Chrome (Mac) ..8..</li>' + '<li>Chrome (Linux) ..8..</li>' + '<li>Chrome OS (Chrome OS) ..8..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>' + '<dt style="style_dt;">_test_description</dt><dd><p>TestPolicyDesc</p></dd>' + '<dt style="style_dt;">_test_schema</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">{\n' + ' "properties": {\n' + ' "foo": {\n' + ' "type": "integer"\n' + ' }\n' + ' }, \n' + ' "type": "object"\n' + '}</dd>' + '<dt style="style_dt;">_test_url_schema</dt>' + '<dd><a href="https://example.com/details">https://example.com/details</a></dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>' + '<dl style="style_dd dl;">' + '<dt>_test_example_value_win</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + 'MockKey\TestPolicyName = {\n' + ' "foo": 123\n' + '}' + '</dd>' + '<dt>_test_example_value_chrome_os</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + 'MockKeyCrOS\TestPolicyName = {\n' + ' "foo": 123\n' + '}' + '</dd>' + '<dt>Android/Linux:</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + 'TestPolicyName: {\n' + ' "foo": 123\n' + '}' + '</dd>' + '<dt>Mac:</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + '<key>TestPolicyName</key>\n' + '<dict>\n' + ' <key>foo</key>\n' + ' <integer>123</integer>\n' + '</dict>' + '</dd>' + '</dl>' + '<dl><dt>Windows (Intune):</dt>' + '<dd style="style_.monospace;style_.pre-wrap;"><enabled/></dd>' + '<dd style="style_.monospace;style_.pre-wrap;"><data id="TestPolicyName" value=""foo": 123"/></dd></dl>' + '</dd>' + '</dl></root>') + + def testAddExternalPolicyDetails(self): + # Test if the definition list (<dl>) of policy details is created correctly + # for 'external' policies. + policy = { + 'type': + 'external', + 'name': + 'TestPolicyName', + 'caption': + 'TestPolicyCaption', + 'desc': + 'TestPolicyDesc', + 'description_schema': { + 'properties': { + 'url': { + 'type': 'string' + }, + 'hash': { + 'type': 'string' + }, + }, + 'type': 'object' + }, + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'win', + 'since_version': '8', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'mac', + 'since_version': '8', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'linux', + 'since_version': '8', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': { + "url": "https://example.com/avatar.jpg", + "hash": "deadbeef", + }, + } + self.writer.messages['doc_since_version'] = {'text': '...$6...'} + self.writer._AddPolicyDetails(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root><dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>External data reference [Windows:REG_SZ] (_test_complex_policies_win)</dd>' + '<dt style="style_dt;">_test_win_reg_loc</dt>' + '<dd style="style_.monospace;">MockKey\TestPolicyName</dd>' + '<dt style="style_dt;">_test_oma_uri</dt>' + '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\TestPolicyName</dd>' + '<dt style="style_dt;">_test_mac_linux_pref_name</dt>' + '<dd style="style_.monospace;">TestPolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome (Windows) ...8...</li>' + '<li>Chrome (Mac) ...8...</li>' + '<li>Chrome (Linux) ...8...</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>' + '<dt style="style_dt;">_test_description</dt><dd><p>TestPolicyDesc</p></dd>' + '<dt style="style_dt;">_test_schema</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">{\n' + ' "properties": {\n' + ' "hash": {\n' + ' "type": "string"\n' + ' }, \n' + ' "url": {\n' + ' "type": "string"\n' + ' }\n' + ' }, \n' + ' "type": "object"\n' + '}</dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>' + '<dl style="style_dd dl;">' + '<dt>_test_example_value_win</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + 'MockKey\TestPolicyName = {\n' + ' "hash": "deadbeef", \n' + ' "url": "https://example.com/avatar.jpg"\n' + '}' + '</dd>' + '<dt>Android/Linux:</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + 'TestPolicyName: {\n' + ' "hash": "deadbeef", \n' + ' "url": "https://example.com/avatar.jpg"\n' + '}' + '</dd>' + '<dt>Mac:</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + '<key>TestPolicyName</key>\n' + '<dict>\n' + ' <key>hash</key>\n' + ' <string>deadbeef</string>\n' + ' <key>url</key>\n' + ' <string>https://example.com/avatar.jpg</string>\n<' + '/dict>' + '</dd>' + '</dl>' + '<dl><dt>Windows (Intune):</dt>' + '<dd style="style_.monospace;style_.pre-wrap;"><enabled/></dd>' + '<dd style="style_.monospace;style_.pre-wrap;"><data id="TestPolicyName" value=""url": "https://example.com/avatar.jpg", "hash": "deadbeef""/></dd></dl>' + '</dd>' + '</dl></root>') + + def testAddPolicyDetailsRecommendedOnly(self): + policy = { + 'type': + 'main', + 'name': + 'TestPolicyName', + 'caption': + 'TestPolicyCaption', + 'desc': + 'TestPolicyDesc', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'win', + 'since_version': '8', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'mac', + 'since_version': '8', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'linux', + 'since_version': '8', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'android', + 'since_version': '30', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'chrome_os', + 'since_version': '53', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False, + 'can_be_mandatory': False, + 'can_be_recommended': True + }, + 'example_value': + False + } + self.writer._AddPolicyDetails(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root><dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>Boolean [Windows:REG_DWORD]</dd>' + '<dt style="style_dt;">_test_win_reg_loc</dt>' + '<dd style="style_.monospace;">MockKeyRec\TestPolicyName</dd>' + '<dt style="style_dt;">_test_oma_uri</dt>' + '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\TestPolicyName</dd>' + '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>' + '<dd style="style_.monospace;">MockKeyCrOSRec\TestPolicyName</dd>' + '<dt style="style_dt;">_test_mac_linux_pref_name</dt>' + '<dd style="style_.monospace;">TestPolicyName</dd>' + '<dt style="style_dt;">_test_android_restriction_name</dt>' + '<dd style="style_.monospace;">TestPolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome (Windows) ..8..</li>' + '<li>Chrome (Mac) ..8..</li>' + '<li>Chrome (Linux) ..8..</li>' + '<li>Chrome (Android) ..30..</li>' + '<li>Chrome (Chrome OS) ..53..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd>_test_feature_mandatory: _test_not_supported,' + ' _test_feature_recommended: _test_supported,' + ' _test_feature_dynamic_refresh: _test_not_supported</dd>' + '<dt style="style_dt;">_test_description</dt><dd><p>TestPolicyDesc</p></dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>0x00000000 (Windows), false (Linux),' + ' false (Android), <false /> (Mac)' + '<dl><dt>Windows (Intune):</dt>' + '<dd style="style_.monospace;style_.pre-wrap;"><disabled/></dd></dl>' + '</dd>' + '</dl></root>') + + def testAddPolicyRow(self): + # Test if policies are correctly added to the summary table. + policy = { + 'name': 'PolicyName', + 'caption': 'PolicyCaption', + 'type': 'string', + } + self.writer._indent_level = 3 + self.writer._AddPolicyRow(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root><tr style="style_tr;">' + '<td style="style_td;style_td.left;padding-left: 49px;">' + '<a href="#PolicyName">PolicyName</a>' + '</td>' + '<td style="style_td;style_td.right;">PolicyCaption</td>' + '</tr></root>') + self.setUp() + policy = { + 'name': 'PolicyName', + 'caption': 'PolicyCaption', + 'type': 'group', + } + self.writer._indent_level = 2 + self.writer._AddPolicyRow(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root><tr style="style_tr;">' + '<td colspan="2" style="style_td;style_td.left;padding-left: 35px;">' + '<a href="#PolicyName">PolicyCaption</a>' + '</td>' + '</tr></root>') + + def testAddPolicySection(self): + # Test if policy details are correctly added to the document. + policy = { + 'name': + 'PolicyName', + 'caption': + 'PolicyCaption', + 'desc': + 'PolicyDesc', + 'type': + 'string', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'win', + 'since_version': '7', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'mac', + 'since_version': '7', + 'until_version': '', + }, + { + 'product': 'chrome_os', + 'platform': 'chrome_os', + 'since_version': '7', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': + 'False' + } + self.writer._AddPolicySection(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<div style="margin-left: 0px">' + '<h3><a name="PolicyName"/>PolicyName</h3>' + '<span>PolicyCaption</span>' + '<dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>String [Windows:REG_SZ]</dd>' + '<dt style="style_dt;">_test_win_reg_loc</dt>' + '<dd style="style_.monospace;">MockKey\\PolicyName</dd>' + '<dt style="style_dt;">_test_oma_uri</dt>' + '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\PolicyName</dd>' + '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>' + '<dd style="style_.monospace;">MockKeyCrOS\\PolicyName</dd>' + '<dt style="style_dt;">_test_mac_linux_pref_name</dt>' + '<dd style="style_.monospace;">PolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome (Windows) ..7..</li>' + '<li>Chrome (Mac) ..7..</li>' + '<li>Chrome OS (Chrome OS) ..7..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>' + '<dt style="style_dt;">_test_description</dt>' + '<dd><p>PolicyDesc</p></dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>"False"' + '<dl><dt>Windows (Intune):</dt>' + '<dd style="style_.monospace;style_.pre-wrap;"><enabled/></dd>' + '<dd style="style_.monospace;style_.pre-wrap;"><data id="PolicyName" value="False"/></dd></dl>' + '</dd>' + '</dl>' + '<a href="#top">_test_back_to_top</a>' + '</div>' + '</root>') + # Test for groups. + self.setUp() + policy['type'] = 'group' + self.writer._AddPolicySection(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<div style="margin-left: 0px">' + '<h2><a name="PolicyName"/>PolicyCaption</h2>' + '<div style="style_div.group_desc;">PolicyDesc</div>' + '<a href="#top">_test_back_to_top</a>' + '</div>' + '</root>') + + def testAddPolicySectionWithAtomicGroup(self): + # Test if policy details are correctly added to the document. + policy = { + 'name': + 'PolicyName', + 'caption': + 'PolicyCaption', + 'desc': + 'PolicyDesc', + 'type': + 'string', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'win', + 'since_version': '7', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'mac', + 'since_version': '7', + 'until_version': '', + }, + { + 'product': 'chrome_os', + 'platform': 'chrome_os', + 'since_version': '7', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': + 'False', + 'atomic_group': + 'PolicyGroup' + } + self.writer._AddPolicySection(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<div style="margin-left: 0px">' + '<h3><a name="PolicyName"/>PolicyName</h3>' + '<span>PolicyCaption</span>' + '<dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>String [Windows:REG_SZ]</dd>' + '<dt style="style_dt;">_test_win_reg_loc</dt>' + '<dd style="style_.monospace;">MockKey\\PolicyName</dd>' + '<dt style="style_dt;">_test_oma_uri</dt>' + '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\PolicyName</dd>' + '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>' + '<dd style="style_.monospace;">MockKeyCrOS\\PolicyName</dd>' + '<dt style="style_dt;">_test_mac_linux_pref_name</dt>' + '<dd style="style_.monospace;">PolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome (Windows) ..7..</li>' + '<li>Chrome (Mac) ..7..</li>' + '<li>Chrome OS (Chrome OS) ..7..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>' + '<dt style="style_dt;">_test_description</dt>' + '<dd><p>PolicyDesc</p></dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>"False"' + '<dl><dt>Windows (Intune):</dt>' + '<dd style="style_.monospace;style_.pre-wrap;"><enabled/></dd>' + '<dd style="style_.monospace;style_.pre-wrap;"><data id="PolicyName" value="False"/></dd></dl>' + '</dd>' + '<dt style="style_dt;">_test_policy_atomic_group</dt>' + '<dd>_test_policy_in_atomic_group <a href="./policy-list-3/atomic_groups#PolicyGroup">PolicyGroup</a></dd>' + '</dl>' + '<a href="#top">_test_back_to_top</a>' + '</div>' + '</root>') + + def testAddPolicySectionForWindowsOnly(self): + policy = { + 'name': + 'PolicyName', + 'caption': + 'PolicyCaption', + 'desc': + 'PolicyDesc', + 'type': + 'int', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'win', + 'since_version': '33', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': + 123 + } + self.writer._AddPolicySection(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<div style="margin-left: 0px">' + '<h3><a name="PolicyName"/>PolicyName</h3>' + '<span>PolicyCaption</span>' + '<dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>Integer [Windows:REG_DWORD]</dd>' + '<dt style="style_dt;">_test_win_reg_loc</dt>' + '<dd style="style_.monospace;">MockKey\\PolicyName</dd>' + '<dt style="style_dt;">_test_oma_uri</dt>' + '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\PolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome (Windows) ..33..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>' + '<dt style="style_dt;">_test_description</dt>' + '<dd><p>PolicyDesc</p></dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>0x0000007b (Windows)' + '<dl><dt>Windows (Intune):</dt>' + '<dd style="style_.monospace;style_.pre-wrap;"><enabled/></dd>' + '<dd style="style_.monospace;style_.pre-wrap;"><data id="PolicyName" value="123"/></dd></dl>' + '</dd>' + '</dl>' + '<a href="#top">_test_back_to_top</a>' + '</div>' + '</root>') + + def testAddPolicySectionForWindows7Only(self): + policy = { + 'name': + 'PolicyName', + 'caption': + 'PolicyCaption', + 'desc': + 'PolicyDesc', + 'type': + 'int', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'win7', + 'since_version': '33', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': + 123 + } + self.writer._AddPolicySection(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<div style="margin-left: 0px">' + '<h3><a name="PolicyName"/>PolicyName</h3>' + '<span>PolicyCaption</span>' + '<dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>Integer [Windows:REG_DWORD]</dd>' + '<dt style="style_dt;">_test_win_reg_loc</dt>' + '<dd style="style_.monospace;">MockKey\\PolicyName</dd>' + '<dt style="style_dt;">_test_oma_uri</dt>' + '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\PolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome (Windows 7) ..33..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>' + '<dt style="style_dt;">_test_description</dt>' + '<dd><p>PolicyDesc</p></dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>0x0000007b (Windows)' + '<dl><dt>Windows (Intune):</dt>' + '<dd style="style_.monospace;style_.pre-wrap;"><enabled/></dd>' + '<dd style="style_.monospace;style_.pre-wrap;"><data id="PolicyName" value="123"/></dd></dl>' + '</dd>' + '</dl>' + '<a href="#top">_test_back_to_top</a>' + '</div>' + '</root>') + + def testAddPolicySectionForMacOnly(self): + policy = { + 'name': + 'PolicyName', + 'caption': + 'PolicyCaption', + 'desc': + 'PolicyDesc', + 'type': + 'int', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'mac', + 'since_version': '33', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': + 123 + } + self.writer._AddPolicySection(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<div style="margin-left: 0px">' + '<h3><a name="PolicyName"/>PolicyName</h3>' + '<span>PolicyCaption</span>' + '<dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>Integer</dd>' + '<dt style="style_dt;">_test_mac_linux_pref_name</dt>' + '<dd style="style_.monospace;">PolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome (Mac) ..33..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>' + '<dt style="style_dt;">_test_description</dt>' + '<dd><p>PolicyDesc</p></dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>123 (Mac)</dd>' + '</dl>' + '<a href="#top">_test_back_to_top</a>' + '</div>' + '</root>') + + def testAddPolicySectionForLinuxOnly(self): + policy = { + 'name': + 'PolicyName', + 'caption': + 'PolicyCaption', + 'desc': + 'PolicyDesc', + 'type': + 'int', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'linux', + 'since_version': '33', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': + 123 + } + self.writer._AddPolicySection(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<div style="margin-left: 0px">' + '<h3><a name="PolicyName"/>PolicyName</h3>' + '<span>PolicyCaption</span>' + '<dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>Integer</dd>' + '<dt style="style_dt;">_test_mac_linux_pref_name</dt>' + '<dd style="style_.monospace;">PolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome (Linux) ..33..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>' + '<dt style="style_dt;">_test_description</dt>' + '<dd><p>PolicyDesc</p></dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>123 (Linux)</dd>' + '</dl>' + '<a href="#top">_test_back_to_top</a>' + '</div>' + '</root>') + + def testAddPolicySectionForAndroidOnly(self): + policy = { + 'name': + 'PolicyName', + 'caption': + 'PolicyCaption', + 'desc': + 'PolicyDesc', + 'type': + 'int', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'android', + 'since_version': '33', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': + 123 + } + self.writer._AddPolicySection(self.doc_root, policy) + self.assertTrue( + self.writer.IsPolicyOrItemSupportedOnPlatform(policy, 'android')) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<div style="margin-left: 0px">' + '<h3><a name="PolicyName"/>PolicyName</h3>' + '<span>PolicyCaption</span>' + '<dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>Integer</dd>' + '<dt style="style_dt;">_test_android_restriction_name</dt>' + '<dd style="style_.monospace;">PolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome (Android) ..33..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>' + '<dt style="style_dt;">_test_description</dt>' + '<dd><p>PolicyDesc</p></dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>123 (Android)</dd>' + '</dl>' + '<a href="#top">_test_back_to_top</a>' + '</div>' + '</root>') + + def testAddDictionaryExample(self): + policy = { + 'name': + 'PolicyName', + 'caption': + 'PolicyCaption', + 'desc': + 'PolicyDesc', + 'type': + 'dict', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'win', + 'since_version': '7', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'mac', + 'since_version': '7', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'linux', + 'since_version': '7', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': { + "ProxyMode": "direct", + "List": ["1", "2", "3"], + "True": True, + "False": False, + "Integer": 123, + "DictList": [ + { + "A": 1, + "B": 2, + }, + { + "C": 3, + "D": 4, + }, + ], + }, + } + self.writer._AddDictionaryExample(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<dl style="style_dd dl;">' + '<dt>_test_example_value_win</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">MockKey\PolicyName = {\n' + ' "DictList": [\n' + ' {\n' + ' "A": 1, \n' + ' "B": 2\n' + ' }, \n' + ' {\n' + ' "C": 3, \n' + ' "D": 4\n' + ' }\n' + ' ], \n' + ' "False": false, \n' + ' "Integer": 123, \n' + ' "List": [\n' + ' "1", \n' + ' "2", \n' + ' "3"\n' + ' ], \n' + ' "ProxyMode": "direct", \n' + ' "True": true\n' + '}' + '</dd>' + '<dt>Android/Linux:</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">PolicyName: {\n' + ' "DictList": [\n' + ' {\n' + ' "A": 1, \n' + ' "B": 2\n' + ' }, \n' + ' {\n' + ' "C": 3, \n' + ' "D": 4\n' + ' }\n' + ' ], \n' + ' "False": false, \n' + ' "Integer": 123, \n' + ' "List": [\n' + ' "1", \n' + ' "2", \n' + ' "3"\n' + ' ], \n' + ' "ProxyMode": "direct", \n' + ' "True": true\n' + '}' + '</dd>' + '<dt>Mac:</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + '<key>PolicyName</key>\n' + '<dict>\n' + ' <key>DictList</key>\n' + ' <array>\n' + ' <dict>\n' + ' <key>A</key>\n' + ' <integer>1</integer>\n' + ' <key>B</key>\n' + ' <integer>2</integer>\n' + ' </dict>\n' + ' <dict>\n' + ' <key>C</key>\n' + ' <integer>3</integer>\n' + ' <key>D</key>\n' + ' <integer>4</integer>\n' + ' </dict>\n' + ' </array>\n' + ' <key>False</key>\n' + ' <false/>\n' + ' <key>Integer</key>\n' + ' <integer>123</integer>\n' + ' <key>List</key>\n' + ' <array>\n' + ' <string>1</string>\n' + ' <string>2</string>\n' + ' <string>3</string>\n' + ' </array>\n' + ' <key>ProxyMode</key>\n' + ' <string>direct</string>\n' + ' <key>True</key>\n' + ' <true/>\n' + '</dict>' + '</dd>' + '</dl>' + '</root>') + + def testAddExternalExample(self): + policy = { + 'name': + 'PolicyName', + 'caption': + 'PolicyCaption', + 'desc': + 'PolicyDesc', + 'type': + 'external', + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'win', + 'since_version': '7', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'mac', + 'since_version': '7', + 'until_version': '', + }, + { + 'product': 'chrome', + 'platform': 'linux', + 'since_version': '7', + 'until_version': '', + }], + 'features': { + 'dynamic_refresh': False + }, + 'example_value': { + "url": "https://example.com/avatar.jpg", + "hash": "deadbeef", + }, + } + self.writer._AddDictionaryExample(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<dl style="style_dd dl;">' + '<dt>_test_example_value_win</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">MockKey\PolicyName = {\n' + ' "hash": "deadbeef", \n' + ' "url": "https://example.com/avatar.jpg"\n' + '}' + '</dd>' + '<dt>Android/Linux:</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">PolicyName: {\n' + ' "hash": "deadbeef", \n' + ' "url": "https://example.com/avatar.jpg"\n' + '}' + '</dd>' + '<dt>Mac:</dt>' + '<dd style="style_.monospace;style_.pre-wrap;">' + '<key>PolicyName</key>\n' + '<dict>\n' + ' <key>hash</key>\n' + ' <string>deadbeef</string>\n' + ' <key>url</key>\n' + ' <string>https://example.com/avatar.jpg</string>\n' + '</dict>' + '</dd>' + '</dl>' + '</root>') + + def testParagraphs(self): + text = 'Paragraph 1\n\nParagraph 2\n\nParagraph 3' + self.writer._AddParagraphs(self.doc_root, text) + self.assertEquals( + self.doc_root.toxml(), + '<root><p>Paragraph 1</p><p>Paragraph 2</p><p>Paragraph 3</p></root>') + + def testGoogleCloudChromeOsPolicies(self): + # Tests whether Chrome OS policies with management type 'google_cloud' + # don't print example values etc. since they are managed through Google's + # Admin console, not Active Directory GPO. + policy = { + 'name': + 'PolicyName', + 'caption': + 'PolicyCaption', + 'desc': + 'PolicyDesc', + 'type': + 'int', + 'features': {}, + 'example_value': + 42, + 'supported_on': [{ + 'product': 'chrome_os', + 'platform': 'chrome_os', + 'since_version': '8', + 'until_version': '', + }], + 'supported_chrome_os_management': ['google_cloud'] + } + self.writer._AddPolicySection(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<div style="margin-left: 0px">' + '<h3><a name="PolicyName"/>PolicyName</h3>' + '<span>PolicyCaption</span>' + '<dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>Integer</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome OS (Chrome OS) ..8..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd></dd>' + '<dt style="style_dt;">_test_description</dt>' + '<dd><p>PolicyDesc</p></dd>' + '</dl>' + '<a href="#top">_test_back_to_top</a>' + '</div>' + '</root>') + + def testActiveDirectoryChromeOsPolicies(self): + # Tests whether Chrome OS policies with management type 'active_directory' + # print example values etc. + policy = { + 'name': + 'PolicyName', + 'caption': + 'PolicyCaption', + 'desc': + 'PolicyDesc', + 'type': + 'int', + 'features': {}, + 'example_value': + 42, + 'supported_on': [{ + 'product': 'chrome_os', + 'platform': 'chrome_os', + 'since_version': '8', + 'until_version': '', + }], + 'supported_chrome_os_management': ['active_directory'] + } + self.writer._AddPolicySection(self.doc_root, policy) + self.assertEquals( + self.doc_root.toxml(), '<root>' + '<div style="margin-left: 0px">' + '<h3><a name="PolicyName"/>PolicyName</h3>' + '<span>PolicyCaption</span>' + '<dl>' + '<dt style="style_dt;">_test_data_type</dt>' + '<dd>Integer [Windows:REG_DWORD]</dd>' + '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>' + '<dd style="style_.monospace;">MockKeyCrOS\\PolicyName</dd>' + '<dt style="style_dt;">_test_supported_on</dt>' + '<dd>' + '<ul style="style_ul;">' + '<li>Chrome OS (Chrome OS) ..8..</li>' + '</ul>' + '</dd>' + '<dt style="style_dt;">_test_supported_features</dt>' + '<dd></dd>' + '<dt style="style_dt;">_test_description</dt>' + '<dd><p>PolicyDesc</p></dd>' + '<dt style="style_dt;">_test_example_value</dt>' + '<dd>0x0000002a (Windows)</dd>' + '</dl>' + '<a href="#top">_test_back_to_top</a>' + '</div>' + '</root>') + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/google_adml_writer.py b/chromium/components/policy/tools/template_writers/writers/google_adml_writer.py new file mode 100755 index 00000000000..a126d34096b --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/google_adml_writer.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# Copyright 2017 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. + +from writers import template_writer + + +def GetWriter(config): + '''Factory method for instanciating the GoogleADMLWriter. Every Writer needs a + GetWriter method because the TemplateFormatter uses this method to + instantiate a Writer. + ''' + return GoogleADMLWriter(None, config) # platforms unused + + +class GoogleADMLWriter(template_writer.TemplateWriter): + '''Simple writer that writes fixed google.adml files. + ''' + + def WriteTemplate(self, template): + '''Returns the contents of the google.adml file. It's independent of + policy_templates.json. + ''' + + return '''<?xml version="1.0" ?> +<policyDefinitionResources revision="1.0" schemaVersion="1.0"> + <displayName/> + <description/> + <resources> + <stringTable> + <string id="google">Google</string> + </stringTable> + </resources> +</policyDefinitionResources> +''' diff --git a/chromium/components/policy/tools/template_writers/writers/google_adml_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/google_adml_writer_unittest.py new file mode 100755 index 00000000000..3170f57f5f2 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/google_adml_writer_unittest.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# Copyright 2017 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. +"""Unittests for writers.google_adml_writer.""" + +import unittest +from writers import google_adml_writer + + +class GoogleAdmlWriterUnittest(unittest.TestCase): + + def setUp(self): + self.writer = google_adml_writer.GetWriter(None) # Config unused + + def testGoogleAdml(self): + output = self.writer.WriteTemplate(None) # Template unused + + # No point to duplicate the full XML. + self.assertTrue('<string id="google">Google</string>' in output) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/google_admx_writer.py b/chromium/components/policy/tools/template_writers/writers/google_admx_writer.py new file mode 100755 index 00000000000..e1f885ff600 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/google_admx_writer.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# Copyright 2017 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. + +from writers import template_writer + + +def GetWriter(config): + '''Factory method for instanciating the GoogleADMXWriter. Every Writer needs a + GetWriter method because the TemplateFormatter uses this method to + instantiate a Writer. + ''' + return GoogleADMXWriter(None, config) # platforms unused + + +class GoogleADMXWriter(template_writer.TemplateWriter): + '''Simple writer that writes fixed google.admx files. + ''' + + def WriteTemplate(self, template): + '''Returns the contents of the google.admx file. It's independent of + policy_templates.json. + ''' + + return '''<?xml version="1.0" ?> +<policyDefinitions revision="1.0" schemaVersion="1.0"> + <policyNamespaces> + <target namespace="Google.Policies" prefix="Google"/> + </policyNamespaces> + <resources minRequiredRevision="1.0" /> + <categories> + <category displayName="$(string.google)" name="Cat_Google"/> + </categories> +</policyDefinitions> +''' diff --git a/chromium/components/policy/tools/template_writers/writers/google_admx_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/google_admx_writer_unittest.py new file mode 100755 index 00000000000..f4f1e6dcdc2 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/google_admx_writer_unittest.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# Copyright 2017 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. +"""Unittests for writers.google_admx_writer.""" + +import unittest +from writers import google_admx_writer + + +class GoogleAdmxWriterUnittest(unittest.TestCase): + + def setUp(self): + self.writer = google_admx_writer.GetWriter(None) # Config unused + + def testGoogleAdmx(self): + output = self.writer.WriteTemplate(None) # Template unused + + # No point to duplicate the full XML. + self.assertTrue('namespace="Google.Policies"' in output) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/gpo_editor_writer.py b/chromium/components/policy/tools/template_writers/writers/gpo_editor_writer.py new file mode 100755 index 00000000000..ff36b98df5d --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/gpo_editor_writer.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 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. + +from writers import template_writer + + +class GpoEditorWriter(template_writer.TemplateWriter): + '''Abstract class for ADM and ADMX writers. + + It includes deprecated policies in its output, and places them in a dedicated + 'DeprecatedPolicies' group. Every deprecated policy has the same description. + + It is a superclass for AdmWriter and AdmxWriter. + ''' + + def IsDeprecatedPolicySupported(self, policy): + # Include deprecated policies in the output. + return True + + def IsVersionSupported(self, policy, supported_on): + # Include deprecated policies in the 'DeprecatedPolicies' group, even if + # they aren't supported anymore. + major_version = self._GetChromiumMajorVersion() + if not major_version: + return True + + since_version = supported_on.get('since_version', None) + + return not since_version or major_version >= int(since_version) + + def IsPolicyOnWin7Only(self, policy): + ''' Returns true if the policy is supported on win7 only.''' + for suppported_on in policy.get('supported_on', []): + if 'win7' == suppported_on.get('platform', []): + return True + return False + + def _IsRemovedPolicy(self, policy): + major_version = self._GetChromiumMajorVersion() + for supported_on in policy.get('supported_on', []): + if '*' in self.platforms or supported_on['platform'] in self.platforms: + until_version = supported_on.get('until_version', None) + if not until_version or major_version <= int(until_version): + # The policy is still supported, return False. + return False + # No platform+version combo supports this version, return True. + return True + + def _FilterPolicies(self, predicate, policy_list): + filtered_policies = [] + for policy in policy_list: + if policy['type'] == 'group': + for p in policy['policies']: + if predicate(p): + filtered_policies.append(p) + else: + if predicate(policy): + filtered_policies.append(policy) + return filtered_policies + + def _RemovePoliciesFromList(self, policy_list, policies_to_remove): + '''Remove policies_to_remove from groups and the top-level list.''' + # We only compare the 'name' property. + policies_to_remove = set([p['name'] for p in policies_to_remove]) + + # Remove from top-level list. + policy_list[:] = [ + p for p in policy_list if p['name'] not in policies_to_remove + ] + + # Remove from groups. + for group in policy_list: + if group['type'] != 'group': + continue + group['policies'] = [ + p for p in group['policies'] if p['name'] not in policies_to_remove + ] + + # Remove empty groups. + policy_list[:] = [ + p for p in policy_list if p['type'] != 'group' or p['policies'] + ] + + def _MovePolicyGroup(self, policy_list, predicate, policy_desc, group): + '''Remove policies from |policy_list| that satisfy |predicate| and add them + to |group|.''' + filtered_policies = self._FilterPolicies(predicate, policy_list) + self._RemovePoliciesFromList(policy_list, filtered_policies) + + for p in filtered_policies: + p['desc'] = policy_desc + + group['policies'] = filtered_policies + + def PreprocessPolicies(self, policy_list): + '''Put policies under the DeprecatedPolicies/RemovedPolicies groups.''' + removed_policies_group = { + 'name': 'RemovedPolicies', + 'type': 'group', + 'caption': self.messages['removed_policy_group_caption']['text'], + 'desc': self.messages['removed_policy_group_desc']['text'], + 'policies': [] + } + self._MovePolicyGroup( + policy_list, + lambda p: self._IsRemovedPolicy(p), + self.messages['removed_policy_desc']['text'], + removed_policies_group) + + deprecated_policies_group = { + 'name': 'DeprecatedPolicies', + 'type': 'group', + 'caption': self.messages['deprecated_policy_group_caption']['text'], + 'desc': self.messages['deprecated_policy_group_desc']['text'], + 'policies': [] + } + self._MovePolicyGroup( + policy_list, + lambda p: p.get('deprecated', False), + self.messages['deprecated_policy_desc']['text'], + deprecated_policies_group) + + policy_list.append(deprecated_policies_group) + policy_list.append(removed_policies_group) + + return super(GpoEditorWriter, self).SortPoliciesGroupsFirst(policy_list) diff --git a/chromium/components/policy/tools/template_writers/writers/ios_app_config_writer.py b/chromium/components/policy/tools/template_writers/writers/ios_app_config_writer.py new file mode 100755 index 00000000000..a5a61f9d404 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/ios_app_config_writer.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +# Copyright 2020 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. + +from xml.dom import minidom +import json +from writers import xml_formatted_writer + +_POLICY_TYPE_TO_XML_TAG = { + 'string': 'string', + 'int': 'integer', + 'int-enum': 'integer', + 'string-enum': 'string', + 'string-enum-list': 'stringArray', + 'main': 'boolean', + 'list': 'stringArray', + 'dict': 'string', +} + +_POLICY_TYPE_TO_INPUT_TYPE = { + 'string': 'input', + 'int': 'input', + 'int-enum': 'select', + 'string-enum': 'select', + 'string-enum-list': 'multiselect', + 'main': 'checkbox', + 'list': 'list', + 'dict': 'input' +} + +_JSON_SCHEMA_TYPES = [ + "string", "number", "integer", "boolean", "null", "object", "array" +] + + +class Error(Exception): + pass + + +def _ParseSchemaTypeValueToString(value, type): + '''Parses the value of a given JSON schema type to a string. + ''' + if type not in _JSON_SCHEMA_TYPES: + raise Error('schema type "{}" not supported'.format(type)) + + if type == 'integer': + return '{0:d}'.format(value) + + # Use the default string parser. + return str(value) + + +def GetWriter(config): + '''Factory method for instanciating the IOSAppConfigWriter. Every Writer needs + a GetWriter method because the TemplateFormatter uses this method to + instantiate a Writer. + ''' + return IOSAppConfigWriter(['ios'], config) # platforms unused + + +class IOSAppConfigWriter(xml_formatted_writer.XMLFormattedWriter): + '''Simple writer that writes app_config.xml files. + ''' + + def _WritePolicyPresentation(self, policy, field_group): + element_type = _POLICY_TYPE_TO_INPUT_TYPE[policy['type']] + if element_type: + attributes = {'type': element_type, 'keyName': policy['name']} + field = self.AddElement(field_group, 'field', attributes) + self._AddLocalizedElement(field, 'label', policy['caption']) + self._AddLocalizedElement(field, 'description', policy['desc']) + + if 'enum' in policy['type']: + options = self.AddElement(field, 'options', {}) + for item in policy['items']: + self._AddLocalizedElement( + options, 'option', str(item['caption']), { + 'value': + _ParseSchemaTypeValueToString(item['value'], + policy['schema']['type']) + }) + + def _AddLocalizedElement(self, + parent, + element_type, + text, + attributes={}, + localization={'value': 'en-US'}): + item = self.AddElement(parent, element_type, attributes) + localized = self.AddElement(item, 'language', localization) + self.AddText(localized, text) + + def _WritePresentation(self, policy_list): + groups = [policy for policy in policy_list if policy['type'] == 'group'] + policies_without_group = [ + policy for policy in policy_list if policy['type'] != 'group' + ] + for policy in groups: + child_policies = self._GetPoliciesForWriter(policy) + if child_policies: + field_group = self.AddElement(self._presentation, 'fieldGroup', {}) + self._AddLocalizedElement(field_group, 'name', policy['caption']) + for child_policy in child_policies: + self._WritePolicyPresentation(child_policy, field_group) + for policy in self._GetPoliciesForWriter( + {'policies': policies_without_group}): + self._WritePolicyPresentation(policy, self._presentation) + + def _WritePolicyDefaultValue(self, parent, policy): + if 'default' in policy: + default_value = self.AddElement(parent, 'defaultValue', {}) + value = self.AddElement(default_value, 'value', {}) + if policy['type'] == 'main': + if policy['default'] == True: + self.AddText(value, 'true') + elif policy['default'] == False: + self.AddText(value, 'false') + elif policy['type'] in ['list', 'string-enum-list']: + for v in policy['default']: + if value == None: + value = self.AddElement(default_value, 'value', {}) + self.AddText(value, v) + value = None + else: + self.AddText(value, policy['default']) + + def _WritePolicyConstraint(self, parent, policy): + attrs = {'nullable': 'true'} + if 'schema' in policy: + if 'minimum' in policy['schema']: + attrs['min'] = _ParseSchemaTypeValueToString( + policy['schema']['minimum'], policy['schema']['type']) + if 'maximum' in policy['schema']: + attrs['max'] = _ParseSchemaTypeValueToString( + policy['schema']['maximum'], policy['schema']['type']) + + constraint = self.AddElement(parent, 'constraint', attrs) + if 'enum' in policy['type']: + values_element = self.AddElement(constraint, 'values', {}) + for v in policy['schema']['enum']: + value = self.AddElement(values_element, 'value', {}) + self.AddText(value, + _ParseSchemaTypeValueToString(v, policy['schema']['type'])) + + def IsFuturePolicySupported(self, policy): + # For now, include all future policies in appconfig.xml. + return True + + def CreateDocument(self): + dom_impl = minidom.getDOMImplementation('') + return dom_impl.createDocument('http://www.w3.org/2001/XMLSchema-instance', + 'managedAppConfiguration', None) + + def WriteTemplate(self, template): + self.messages = template['messages'] + self.Init() + template['policy_definitions'] = \ + self.PreprocessPolicies(template['policy_definitions']) + self.BeginTemplate() + self.WritePolicies(template['policy_definitions']) + self._WritePresentation(template['policy_definitions']) + self.EndTemplate() + + return self.GetTemplateText() + + def BeginTemplate(self): + self._app_config.attributes[ + 'xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance' + schema_location = 'https://storage.googleapis.com/appconfig-media/appconfigschema.xsd' + self._app_config.attributes[ + 'xsi:noNamespaceSchemaLocation'] = schema_location + + version = self.AddElement(self._app_config, 'version', {}) + milestone = self.config['version'].split(".", 1)[0] + self.AddText(version, milestone) + + bundle_id = self.AddElement(self._app_config, 'bundleId', {}) + self.AddText(bundle_id, self.config['bundle_id']) + self._policies = self.AddElement(self._app_config, 'dict', {}) + self._presentation = self.AddElement(self._app_config, 'presentation', + {'defaultLocale': 'en-US'}) + + def WritePolicy(self, policy): + element_type = _POLICY_TYPE_TO_XML_TAG[policy['type']] + if element_type: + attributes = {'keyName': policy['name']} + # Add a "<!--FUTURE POLICY-->" comment before future policies. + if 'future_on' in policy: + for config in policy['future_on']: + if config['platform'] == 'ios': + self.AddComment(self._policies, 'FUTURE POLICY') + policy_element = self.AddElement(self._policies, element_type, attributes) + self._WritePolicyDefaultValue(policy_element, policy) + self._WritePolicyConstraint(policy_element, policy) + + def Init(self): + self._doc = self.CreateDocument() + self._app_config = self._doc.documentElement + + def GetTemplateText(self): + return self.ToPrettyXml(self._doc) diff --git a/chromium/components/policy/tools/template_writers/writers/ios_app_config_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/ios_app_config_writer_unittest.py new file mode 100755 index 00000000000..8774f2e84a3 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/ios_app_config_writer_unittest.py @@ -0,0 +1,434 @@ +#!/usr/bin/env python3 +# Copyright 2020 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. + +import os +import sys +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +import json +import unittest + +from writers import writer_unittest_common + + +class IOSAppConfigWriterUnitTests(writer_unittest_common.WriterUnittestCommon): + '''Unit tests for IOSAppConfigWriter.''' + + def _GetTestPolicyTemplate(self, policy_definitions): + return ''' +{ + 'policy_definitions': %s, + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {}, +} +''' % (policy_definitions) + + def _GetExpectedOutput(self, version, policy_definition, policy_presentation): + if policy_definition: + definition = '<dict>\n %s\n </dict>' % policy_definition + else: + definition = '<dict/>' + if policy_presentation: + presentation = '<presentation defaultLocale="en-US">\n %s\n </presentation>' % policy_presentation + else: + presentation = '<presentation defaultLocale="en-US"/>' + + return '''<?xml version="1.0" ?> +<managedAppConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://storage.googleapis.com/appconfig-media/appconfigschema.xsd"> + <version>%s</version> + <bundleId>com.google.chrome.ios</bundleId> + %s + %s +</managedAppConfiguration>''' % (version, definition, presentation) + + def testStringPolicy(self): + policy_definition = json.dumps([{ + 'name': 'string policy', + 'type': 'string', + 'supported_on': ['ios:80-'], + 'caption': 'string caption', + 'desc': 'string description' + }]) + policy_json = self._GetTestPolicyTemplate(policy_definition) + expected_configuration = '''<string keyName="string policy"> + <constraint nullable="true"/> + </string>''' + expected_presentation = '''<field keyName="string policy" type="input"> + <label> + <language value="en-US">string caption</language> + </label> + <description> + <language value="en-US">string description</language> + </description> + </field>''' + expected = self._GetExpectedOutput('83', expected_configuration, + expected_presentation) + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0' + }, 'ios_app_config') + self.assertEquals(output.strip(), expected.strip()) + + def testIntPolicy(self): + policy_definition = json.dumps([{ + 'name': 'IntPolicy', + 'type': 'int', + 'supported_on': ['ios:80-'], + 'caption': 'int caption', + 'desc': 'int description' + }]) + policy_json = self._GetTestPolicyTemplate(policy_definition) + expected_configuration = '''<integer keyName="IntPolicy"> + <constraint nullable="true"/> + </integer>''' + expected_presentation = '''<field keyName="IntPolicy" type="input"> + <label> + <language value="en-US">int caption</language> + </label> + <description> + <language value="en-US">int description</language> + </description> + </field>''' + expected = self._GetExpectedOutput('83', expected_configuration, + expected_presentation) + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0' + }, 'ios_app_config') + self.assertEquals(output.strip(), expected.strip()) + + def testIntEnumPolicy(self): + policy_definition = json.dumps([{ + 'name': + 'IntEnumPolicy', + 'type': + 'int-enum', + 'supported_on': ['ios:80-'], + 'caption': + 'int-enum caption', + 'desc': + 'int-enum description', + 'schema': { + 'type': 'integer', + 'enum': [0, 1], + }, + 'items': [{ + 'name': 'item0', + 'value': 0, + 'caption': 'item 0', + }, { + 'name': 'item1', + 'value': 1, + 'caption': 'item 1', + }] + }]) + policy_json = self._GetTestPolicyTemplate(policy_definition) + expected_configuration = '''<integer keyName="IntEnumPolicy"> + <constraint nullable="true"> + <values> + <value>0</value> + <value>1</value> + </values> + </constraint> + </integer>''' + expected_presentation = '''<field keyName="IntEnumPolicy" type="select"> + <label> + <language value="en-US">int-enum caption</language> + </label> + <description> + <language value="en-US">int-enum description</language> + </description> + <options> + <option value="0"> + <language value="en-US">item 0</language> + </option> + <option value="1"> + <language value="en-US">item 1</language> + </option> + </options> + </field>''' + expected = self._GetExpectedOutput('83', expected_configuration, + expected_presentation) + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0' + }, 'ios_app_config') + self.assertEquals(output.strip(), expected.strip()) + + def testStringEnumPolicy(self): + policy_definition = json.dumps([{ + 'name': + 'StringEnumPolicy', + 'type': + 'string-enum', + 'supported_on': ['ios:80-'], + 'caption': + 'string-enum caption', + 'desc': + 'string-enum description', + 'schema': { + 'type': 'string', + 'enum': ['0', '1'], + }, + 'items': [{ + 'name': 'item0', + 'value': '0', + 'caption': 'item 0', + }, { + 'name': 'item1', + 'value': '1', + 'caption': 'item 1', + }] + }]) + policy_json = self._GetTestPolicyTemplate(policy_definition) + expected_configuration = '''<string keyName="StringEnumPolicy"> + <constraint nullable="true"> + <values> + <value>0</value> + <value>1</value> + </values> + </constraint> + </string>''' + expected_presentation = '''<field keyName="StringEnumPolicy" type="select"> + <label> + <language value="en-US">string-enum caption</language> + </label> + <description> + <language value="en-US">string-enum description</language> + </description> + <options> + <option value="0"> + <language value="en-US">item 0</language> + </option> + <option value="1"> + <language value="en-US">item 1</language> + </option> + </options> + </field>''' + expected = self._GetExpectedOutput('83', expected_configuration, + expected_presentation) + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0' + }, 'ios_app_config') + self.assertEquals(output.strip(), expected.strip()) + + def testStringEnumListPolicy(self): + policy_definition = json.dumps([{ + 'name': + 'StringEnumListPolicy', + 'type': + 'string-enum-list', + 'supported_on': ['ios:80-'], + 'caption': + 'string-enum-list caption', + 'desc': + 'string-enum-list description', + 'schema': { + 'type': 'string', + 'enum': ['0', '1'], + }, + 'items': [{ + 'name': 'item0', + 'value': '0', + 'caption': 'item 0', + }, { + 'name': 'item1', + 'value': '1', + 'caption': 'item 1', + }] + }]) + policy_json = self._GetTestPolicyTemplate(policy_definition) + expected_configuration = '''<stringArray keyName="StringEnumListPolicy"> + <constraint nullable="true"> + <values> + <value>0</value> + <value>1</value> + </values> + </constraint> + </stringArray>''' + expected_presentation = '''<field keyName="StringEnumListPolicy" type="multiselect"> + <label> + <language value="en-US">string-enum-list caption</language> + </label> + <description> + <language value="en-US">string-enum-list description</language> + </description> + <options> + <option value="0"> + <language value="en-US">item 0</language> + </option> + <option value="1"> + <language value="en-US">item 1</language> + </option> + </options> + </field>''' + expected = self._GetExpectedOutput('83', expected_configuration, + expected_presentation) + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0' + }, 'ios_app_config') + self.assertEquals(output.strip(), expected.strip()) + + def testBooleanPolicy(self): + policy_definition = json.dumps([{ + 'name': 'BooleanPolicy', + 'type': 'main', + 'supported_on': ['ios:80-'], + 'caption': 'boolean caption', + 'desc': 'boolean description' + }]) + policy_json = self._GetTestPolicyTemplate(policy_definition) + expected_configuration = '''<boolean keyName="BooleanPolicy"> + <constraint nullable="true"/> + </boolean>''' + expected_presentation = '''<field keyName="BooleanPolicy" type="checkbox"> + <label> + <language value="en-US">boolean caption</language> + </label> + <description> + <language value="en-US">boolean description</language> + </description> + </field>''' + expected = self._GetExpectedOutput('83', expected_configuration, + expected_presentation) + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0' + }, 'ios_app_config') + self.assertEquals(output.strip(), expected.strip()) + + def testListPolicy(self): + policy_definition = json.dumps([{ + 'name': 'ListPolicy', + 'type': 'list', + 'supported_on': ['ios:80-'], + 'caption': 'list caption', + 'desc': 'list description' + }]) + policy_json = self._GetTestPolicyTemplate(policy_definition) + expected_configuration = '''<stringArray keyName="ListPolicy"> + <constraint nullable="true"/> + </stringArray>''' + expected_presentation = '''<field keyName="ListPolicy" type="list"> + <label> + <language value="en-US">list caption</language> + </label> + <description> + <language value="en-US">list description</language> + </description> + </field>''' + expected = self._GetExpectedOutput('83', expected_configuration, + expected_presentation) + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0' + }, 'ios_app_config') + self.assertEquals(output.strip(), expected.strip()) + + def testDictPolicy(self): + policy_definition = json.dumps([{ + 'name': 'DictPolicy', + 'type': 'dict', + 'supported_on': ['ios:80-'], + 'caption': 'dict caption', + 'desc': 'dict description' + }]) + policy_json = self._GetTestPolicyTemplate(policy_definition) + # Dict policies are not supported by the appconfig.xml format, therefore + # they are treated as JSON strings. + expected_configuration = '''<string keyName="DictPolicy"> + <constraint nullable="true"/> + </string>''' + expected_presentation = '''<field keyName="DictPolicy" type="input"> + <label> + <language value="en-US">dict caption</language> + </label> + <description> + <language value="en-US">dict description</language> + </description> + </field>''' + expected = self._GetExpectedOutput('83', expected_configuration, + expected_presentation) + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0' + }, 'ios_app_config') + self.assertEquals(output.strip(), expected.strip()) + + def testFuturePolicy(self): + policy_definition = json.dumps([{ + 'name': 'FuturePolicy', + 'type': 'string', + 'future_on': ['ios'], + 'caption': 'string caption', + 'desc': 'string description' + }]) + policy_json = self._GetTestPolicyTemplate(policy_definition) + expected_configuration = '''<!--FUTURE POLICY--> + <string keyName="FuturePolicy"> + <constraint nullable="true"/> + </string>''' + expected_presentation = '''<field keyName="FuturePolicy" type="input"> + <label> + <language value="en-US">string caption</language> + </label> + <description> + <language value="en-US">string description</language> + </description> + </field>''' + expected = self._GetExpectedOutput('83', expected_configuration, + expected_presentation) + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0' + }, 'ios_app_config') + self.assertEquals(output.strip(), expected.strip()) + + def testPolicyWithGroup(self): + policy_definition = json.dumps([{ + 'name': 'PolicyInGroup', + 'type': 'string', + 'supported_on': ['ios:80-'], + 'caption': 'string caption', + 'desc': 'string description' + }, { + 'name': 'DummyGroup', + 'type': 'group', + 'caption': 'Dummy Group', + 'desc': 'Dummy group for testing', + 'policies': ['PolicyInGroup'] + }]) + policy_json = self._GetTestPolicyTemplate(policy_definition) + expected_configuration = '''<string keyName="PolicyInGroup"> + <constraint nullable="true"/> + </string>''' + expected_presentation = '''<fieldGroup> + <name> + <language value="en-US">Dummy Group</language> + </name> + <field keyName="PolicyInGroup" type="input"> + <label> + <language value="en-US">string caption</language> + </label> + <description> + <language value="en-US">string description</language> + </description> + </field> + </fieldGroup>''' + expected = self._GetExpectedOutput('83', expected_configuration, + expected_presentation) + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0' + }, 'ios_app_config') + self.assertEquals(output.strip(), expected.strip()) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/jamf_writer.py b/chromium/components/policy/tools/template_writers/writers/jamf_writer.py new file mode 100755 index 00000000000..f8379c0bca9 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/jamf_writer.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +# Copyright 2020 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. + +import copy +import json + +from writers import template_writer + + +def GetWriter(config): + '''Factory method for creating JamfWriter objects. + See the constructor of TemplateWriter for description of + arguments. + ''' + return JamfWriter(['mac', 'ios'], config) + + +class JamfWriter(template_writer.TemplateWriter): + '''Simple writer that writes a jamf.json file. + ''' + MAX_RECURSIVE_FIELDS_DEPTH = 5 + TYPE_TO_INPUT = { + 'string': 'string', + 'int': 'integer', + 'int-enum': 'integer', + 'string-enum': 'string', + 'string-enum-list': 'array', + 'main': 'boolean', + 'list': 'array', + 'dict': 'object', + 'external': 'object', + } + + # Some policies are forced to a certain schema, so they bypass TYPE_TO_INPUT + POLICY_ID_TO_INPUT = { + 227: 'string', # ManagedBookmarks + 278: 'string', # ExtensionSettings + } + + + def WriteTemplate(self, template): + '''Writes the given template definition. + + Args: + template: Template definition to write. + + Returns: + Generated output for the passed template definition. + ''' + self.messages = template['messages'] + # Keep track of all items that can be referred to by an id. + # This is used for '$ref' fields in the policy templates. + ref_ids_schemas = {} + policies = [] + for policy_def in template['policy_definitions']: + # Iterate over all policies, even the policies contained inside a policy + # group. + if policy_def['type'] == 'group': + policies += policy_def['policies'] + else: + policies += [policy_def] + + for policy in policies: + if policy['type'] == 'int-enum' or policy['type'] == 'string-enum': + self.RecordEnumIds(policy, ref_ids_schemas) + elif 'schema' in policy: + if 'id' in policy['schema']: + self.RecordKnownPropertyIds(policy['schema'], ref_ids_schemas) + if 'items' in policy['schema']: + self.RecordKnownPropertyIds(policy['schema']['items'], + ref_ids_schemas) + if 'properties' in policy['schema']: + self.RecordKnownPropertyIds(policy['schema']['properties'], + ref_ids_schemas) + if 'patternProperties' in policy['schema']: + self.RecordKnownPropertyIds(policy['schema']['patternProperties'], + ref_ids_schemas) + + policies = [policy for policy in policies if self.IsPolicySupported(policy)] + output = { + 'title': self.config['bundle_id'], + 'version': self.config['version'].split(".", 1)[0], + 'description': self.config['app_name'], + 'options': { + 'remove_empty_properties': True + }, + 'properties': {} + } + + for policy in policies: + output['properties'][policy['name']] = { + 'title': + policy['name'], + 'description': + policy['caption'], + 'type': + self.TYPE_TO_INPUT[policy['type']], + 'links': [{ + 'rel': self.messages['doc_policy_documentation']['text'], + 'href': self.config['doc_url'] + '#' + policy['name'] + }] + } + + policy_output = output['properties'][policy['name']] + if policy['id'] in self.POLICY_ID_TO_INPUT: + policy_output['type'] = self.POLICY_ID_TO_INPUT[policy['id']] + + policy_type = policy_output['type'] + if policy['type'] == 'int-enum' or policy['type'] == 'string-enum': + policy_output['options'] = { + 'enum_titles': [item['name'] for item in policy['items']] + } + policy_output['enum'] = [item['value'] for item in policy['items']] + elif policy['type'] == 'int' and 'schema' in policy: + if 'minimum' in policy['schema']: + policy_output['minimum'] = policy['schema']['minimum'] + if 'maximum' in policy['schema']: + policy_output['maximum'] = policy['schema']['maximum'] + elif policy['type'] == 'list': + policy_output['items'] = policy['schema']['items'] + elif policy['type'] == 'string-enum-list' or policy[ + 'type'] == 'int-enum-list': + policy_output['items'] = { + 'type': policy['schema']['items']['type'], + 'options': { + 'enum_titles': [item['name'] for item in policy['items']] + }, + 'enum': [item['value'] for item in policy['items']] + } + elif policy_output['type'] == 'object' and policy['type'] != 'external': + policy_output['type'] = policy['schema']['type'] + if policy_output['type'] == 'array': + policy_output['items'] = policy['schema']['items'] + self.WriteRefItems(policy_output['items'], policy_output['items'], [], + ref_ids_schemas, set()) + elif policy_output['type'] == 'object': + policy_output['properties'] = policy['schema']['properties'] + self.WriteRefItems(policy_output['properties'], + policy_output['properties'], [], ref_ids_schemas, + set()) + + return json.dumps(output, indent=2, sort_keys=True, separators=(',', ': ')) + + def RecordEnumIds(self, policy, known_ids): + '''Writes the a dictionary mapping ids of enums that can be referred to by + '$ref' to their schema. + + Args: + policy: The policy to scan for refs. + known_ids: The dictionary and output of all the known ids. + ''' + if 'id' in policy['schema']: + known_ids[policy['schema']['id']] = { + 'type': policy['schema']['type'], + 'options': { + 'enum_titles': [item['name'] for item in policy['items']] + }, + 'enum': [item['value'] for item in policy['items']] + } + + def RecordKnownPropertyIds(self, obj, known_ids): + '''Writes the a dictionary mapping ids of schemas properties that can be + referred to by '$ref' to their schema. + + Args: + obj: The object to scan for refs. + known_ids: The dictionary and output of all the known ids. + ''' + if type(obj) is not dict: + return + if 'id' in obj: + known_ids[obj['id']] = obj + for value in obj.values(): + self.RecordKnownPropertyIds(value, known_ids) + + def WriteRefItems(self, root, obj, path_to_obj_parent, known_ids, + ids_in_ancestry): + '''Replaces all the '$ref' items by their actual value. Nested properties + are limited to a depth of MAX_RECURSIVE_FIELDS_DEPTH, after which the + recursive field is removed. + + Args: + root: The root of the object tree to scan for refs. + obj: The current object being checked for ids. + path_to_obj_parent: A array of all the keys leading to the parent of |obj| + starting at |root|. + known_ids: The dictionary of all the known ids. + ids_in_ancestry: A list of ids found in the tree starting at root. Use to + keep nested fields in check. + ''' + if type(obj) is not dict: + return + if 'id' in obj: + ids_in_ancestry.add(obj['id']) + # Make a copy of items since we are going to change |obj|. + for key, value in list(obj.items()): + if type(value) is not dict: + continue + if '$ref' in value: + # If the id is an ancestor, we have a nested field. + if value['$ref'] in ids_in_ancestry: + id = value['$ref'] + + last_obj = None + parent = root + grandparent = root + + # Find the parent and grandparent of obj to create the |last_obj| + # which is the field where the nesting stops. + for i in range(0, len(path_to_obj_parent)): + if i + 1 < len(path_to_obj_parent): + grandparent = grandparent[path_to_obj_parent[i]] + else: + parent = grandparent[path_to_obj_parent[i]] + # Remove the link between grand parent and parent so we can have a + # copy of the object without nesting. + grandparent[path_to_obj_parent[i]] = None + del grandparent[path_to_obj_parent[i]] + # last_obj is a copy of the reference object without nesting. + last_obj = copy.deepcopy(known_ids[id]) + # Re-establish the link between grand parent and parent. + grandparent[path_to_obj_parent[i]] = parent + + del obj[key] + obj[key] = last_obj + # Create nested '$ref' objects with |last_obj| as the last object. + for count in range(1, self.MAX_RECURSIVE_FIELDS_DEPTH): + obj[key] = copy.deepcopy(known_ids[id]) + obj_grandparent_ref = path_to_obj_parent[len(path_to_obj_parent) - 1] + else: + # If no nested field, simply assign the '$ref'. + obj[key] = dict(known_ids[value['$ref']]) + self.WriteRefItems(root, obj[key], path_to_obj_parent + [key], + known_ids, ids_in_ancestry) + else: + self.WriteRefItems(root, value, path_to_obj_parent + [key], known_ids, + ids_in_ancestry) diff --git a/chromium/components/policy/tools/template_writers/writers/jamf_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/jamf_writer_unittest.py new file mode 100755 index 00000000000..1dc8fc5d114 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/jamf_writer_unittest.py @@ -0,0 +1,383 @@ +#!/usr/bin/env python3 +# Copyright 2020 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. + +import os +import sys +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +import unittest + +import copy +import json +from writers import writer_unittest_common + + +def _JsonFormat(input): + return json.dumps(input, indent=2, sort_keys=True, separators=(',', ': ')) + + +class JamfWriterUnitTests(writer_unittest_common.WriterUnittestCommon): + '''Unit tests for JamfWriter.''' + + doc_url = 'https://chromeenterprise.google/policies/' + + def _GetTestPolicyTemplate(self, policy_name, policy_type, schema_type, + policy_caption): + template = { + 'policy_definitions': [{ + 'name': + policy_name, + 'id': + 1, + 'type': + policy_type, + 'supported_on': ['chrome.mac:*-'], + 'caption': + policy_caption, + 'desc': + '', + 'items': [{ + 'name': 'title1', + 'value': 1, + 'caption': '', + 'type': 'integer' + }], + 'schema': { + 'type': schema_type, + 'id': 'enumid', + 'properties' if schema_type == 'object' else 'items': { + 'title': + 'title_obj' if schema_type == 'object' else 'title_array', + 'type': 'integer' + }, + } + }], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': { + 'doc_policy_documentation': { + 'text': 'Documentation for policy' + } + }, + } + return _JsonFormat(template) + + def _GetExpectedOutput(self, policy_name, policy_type, policy_caption, + initial_type, version): + output = { + 'description': 'Google Chrome', + 'options': { + 'remove_empty_properties': True + }, + 'properties': { + policy_name: { + 'description': + policy_caption, + 'title': + policy_name, + 'type': + policy_type, + 'links': [{ + 'rel': 'Documentation for policy', + 'href': self.doc_url + '#' + policy_name + }] + } + }, + 'title': 'com.google.chrome.ios', + 'version': version + } + if initial_type == 'int-enum' or initial_type == 'string-enum': + output['properties'][policy_name]['enum'] = [1] + output['properties'][policy_name]['options'] = {'enum_titles': ['title1']} + if initial_type == 'string-enum-list' or initial_type == 'int-enum-list': + output['properties'][policy_name]['items'] = { + 'type': 'integer', + 'enum': [1], + 'options': { + 'enum_titles': ['title1'] + } + } + elif policy_type == 'array': + output['properties'][policy_name]['items'] = { + 'type': 'integer', + 'title': 'title_array' + } + elif initial_type == 'dict': + output['properties'][policy_name]['properties'] = { + 'type': 'integer', + 'title': 'title_obj' + } + + return _JsonFormat(output) + + def testStringPolicy(self): + policy_json = self._GetTestPolicyTemplate('stringPolicy', 'string', '', + 'A string policy') + expected = self._GetExpectedOutput('stringPolicy', 'string', + 'A string policy', 'string', '83') + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0', + 'doc_url': self.doc_url + }, 'jamf') + self.assertEquals(output.strip(), expected.strip()) + + def testIntPolicy(self): + policy_json = self._GetTestPolicyTemplate('intPolicy', 'int', '', + 'An int policy') + expected = self._GetExpectedOutput('intPolicy', 'integer', 'An int policy', + 'int', '83') + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0', + 'doc_url': self.doc_url + }, 'jamf') + self.assertEquals(output.strip(), expected.strip()) + + def testIntPolicyWithMinAndMax(self): + template = { + 'policy_definitions': [{ + 'name': 'intPolicyWithMinAndMax', + 'id': 1, + 'type': 'int', + 'supported_on': ['chrome.mac:*-'], + 'caption': 'An int policy with min and max', + 'desc': '', + 'schema': { + 'type': 'int', + 'minimum': 0, + 'maximum': 10 + } + }], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': { + 'doc_policy_documentation': { + 'text': 'Documentation for policy' + } + } + } + policy_json = _JsonFormat(template) + + expected = { + 'description': 'Google Chrome', + 'options': { + 'remove_empty_properties': True + }, + 'properties': { + 'intPolicyWithMinAndMax': { + 'description': + 'An int policy with min and max', + 'maximum': + 10, + 'minimum': + 0, + 'title': + 'intPolicyWithMinAndMax', + 'type': + 'integer', + 'links': [{ + 'rel': 'Documentation for policy', + 'href': self.doc_url + '#' + 'intPolicyWithMinAndMax' + }] + } + }, + 'title': 'com.google.chrome.ios', + 'version': '83' + } + expected_json = _JsonFormat(expected) + + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0', + 'doc_url': self.doc_url + }, 'jamf') + self.assertEquals(output.strip(), expected_json.strip()) + + def testIntEnumPolicy(self): + policy_json = self._GetTestPolicyTemplate('intPolicy', 'int-enum', '', + 'An int-enum policy') + expected = self._GetExpectedOutput('intPolicy', 'integer', + 'An int-enum policy', 'int-enum', '83') + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0', + 'doc_url': self.doc_url + }, 'jamf') + self.assertEquals(output.strip(), expected.strip()) + + def testStringEnumPolicy(self): + policy_json = self._GetTestPolicyTemplate('stringPolicy', 'string-enum', '', + 'A string-enum policy') + expected = self._GetExpectedOutput('stringPolicy', 'string', + 'A string-enum policy', 'string-enum', + '83') + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0', + 'doc_url': self.doc_url + }, 'jamf') + self.assertEquals(output.strip(), expected.strip()) + + def testStringEnumListPolicy(self): + policy_json = self._GetTestPolicyTemplate('stringPolicy', + 'string-enum-list', '', + 'A string-enum-list policy') + expected = self._GetExpectedOutput('stringPolicy', 'array', + 'A string-enum-list policy', + 'string-enum-list', '83') + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0', + 'doc_url': self.doc_url + }, 'jamf') + self.assertEquals(output.strip(), expected.strip()) + + def testBooleanPolicy(self): + policy_json = self._GetTestPolicyTemplate('booleanPolicy', 'main', '', + 'A boolean policy') + expected = self._GetExpectedOutput('booleanPolicy', 'boolean', + 'A boolean policy', 'main', '83') + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0', + 'doc_url': self.doc_url + }, 'jamf') + self.assertEquals(output.strip(), expected.strip()) + + def testListPolicy(self): + policy_json = self._GetTestPolicyTemplate('listPolicy', 'list', '', + 'A list policy') + expected = self._GetExpectedOutput('listPolicy', 'array', 'A list policy', + 'list', '83') + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0', + 'doc_url': self.doc_url + }, 'jamf') + self.assertEquals(output.strip(), expected.strip()) + + def testDictPolicy(self): + policy_json = self._GetTestPolicyTemplate('dictPolicy', 'dict', 'object', + 'A dict policy') + expected = self._GetExpectedOutput('dictPolicy', 'object', 'A dict policy', + 'dict', '83') + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0', + 'doc_url': self.doc_url + }, 'jamf') + self.assertEquals(output.strip(), expected.strip()) + + def testArrayDictPolicy(self): + policy_json = self._GetTestPolicyTemplate('dictPolicy', 'dict', 'array', + 'A dict policy') + expected = self._GetExpectedOutput('dictPolicy', 'array', 'A dict policy', + 'dict', '83') + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0', + 'doc_url': self.doc_url + }, 'jamf') + self.assertEquals(output.strip(), expected.strip()) + + def testNestedArrayDict(self): + template = { + 'policy_definitions': [{ + 'name': 'name', + 'id': 1, + 'type': 'dict', + 'supported_on': ['chrome.mac:*-'], + 'caption': 'caption', + 'desc': '', + 'schema': { + 'type': 'array', + 'items': { + 'title': 'title2', + 'id': 'id', + 'type': 'object', + 'properties': { + 'name': 'name', + 'children': { + 'type': 'array', + 'items': { + '$ref': 'id' + } + } + } + } + } + }], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': { + 'doc_policy_documentation': { + 'text': 'Documentation for policy' + } + } + } + policy_json = _JsonFormat(template) + + expected = { + 'description': 'Google Chrome', + 'options': { + 'remove_empty_properties': True + }, + 'properties': { + 'name': { + 'description': + 'caption', + 'title': + 'name', + 'type': + 'array', + 'items': { + 'title': 'title2', + 'id': 'id', + 'type': 'object', + 'properties': { + 'name': 'name' + } + }, + 'links': [{ + 'rel': 'Documentation for policy', + 'href': self.doc_url + '#' + 'name' + }] + } + }, + 'title': 'com.google.chrome.ios', + 'version': '83' + } + + for i in range(0, 5): + expected['properties']['name']['items']['properties']['children'] = { + 'type': 'array', + 'items': copy.deepcopy(expected['properties']['name']['items']) + } + + output_expected = _JsonFormat(expected) + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0', + 'doc_url': self.doc_url + }, 'jamf') + self.assertEquals(output.strip(), output_expected.strip()) + + def testExternalPolicy(self): + policy_json = self._GetTestPolicyTemplate('externalPolicy', 'external', '', + 'A external policy') + expected = self._GetExpectedOutput('externalPolicy', 'object', + 'A external policy', 'external', '83') + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'version': '83.0.4089.0', + 'doc_url': self.doc_url + }, 'jamf') + self.assertEquals(output.strip(), expected.strip()) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/json_writer.py b/chromium/components/policy/tools/template_writers/writers/json_writer.py new file mode 100755 index 00000000000..7d5d166c7c9 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/json_writer.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# 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. + +import json + +from textwrap import TextWrapper +from writers import template_writer + +TEMPLATE_HEADER = """\ +// Policy template for Linux. +// Uncomment the policies you wish to activate and change their values to +// something useful for your case. The provided values are for reference only +// and do not provide meaningful defaults! +{""" + +HEADER_DELIMETER = """\ + //-------------------------------------------------------------------------""" + + +def GetWriter(config): + '''Factory method for creating JsonWriter objects. + See the constructor of TemplateWriter for description of + arguments. + ''' + return JsonWriter(['linux'], config) + + +class JsonWriter(template_writer.TemplateWriter): + '''Class for generating policy files in JSON format (for Linux). The + generated files will define all the supported policies with example values + set for them. This class is used by PolicyTemplateGenerator to write .json + files. + ''' + + def PreprocessPolicies(self, policy_list): + return self.FlattenGroupsAndSortPolicies(policy_list) + + def WriteComment(self, comment): + self._out.append('// ' + comment) + + def WritePolicy(self, policy): + example_value_str = json.dumps(policy['example_value'], sort_keys=True) + + # Add comma to the end of the previous line. + if not self._first_written: + self._out[-2] += ',' + + if not self.CanBeMandatory(policy) and self.CanBeRecommended(policy): + line = ' // Note: this policy is supported only in recommended mode.' + self._out.append(line) + line = ' // The JSON file should be placed in %srecommended.' % \ + self.config['linux_policy_path'] + self._out.append(line) + + line = ' // %s' % policy['caption'] + self._out.append(line) + self._out.append(HEADER_DELIMETER) + description = policy['desc'] + if self.HasExpandedPolicyDescription(policy): + description += ' ' + self.GetExpandedPolicyDescription(policy) + '\n' + description = self._text_wrapper.wrap(description) + self._out += description + line = ' //"%s": %s' % (policy['name'], example_value_str) + self._out.append('') + self._out.append(line) + self._out.append('') + + self._first_written = False + + def BeginTemplate(self): + if self._GetChromiumVersionString() is not None: + self.WriteComment(self.config['build'] + ''' version: ''' + \ + self._GetChromiumVersionString()) + self._out.append(TEMPLATE_HEADER) + + def EndTemplate(self): + self._out.append('}') + + def Init(self): + self._out = [] + # The following boolean member is true until the first policy is written. + self._first_written = True + # Create the TextWrapper object once. + self._text_wrapper = TextWrapper( + initial_indent=' // ', + subsequent_indent=' // ', + break_long_words=False, + width=80) + + def GetTemplateText(self): + return '\n'.join(self._out) diff --git a/chromium/components/policy/tools/template_writers/writers/json_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/json_writer_unittest.py new file mode 100755 index 00000000000..5f7c3ad9660 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/json_writer_unittest.py @@ -0,0 +1,460 @@ +#!/usr/bin/env python3 +# 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. +'''Unit tests for writers.json_writer''' + +import os +import sys +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +import unittest + +from writers import writer_unittest_common + +TEMPLATE_HEADER = """\ +// Policy template for Linux. +// Uncomment the policies you wish to activate and change their values to +// something useful for your case. The provided values are for reference only +// and do not provide meaningful defaults! +{ +""" + +TEMPLATE_HEADER_WITH_VERSION = """\ +// chromium version: 39.0.0.0 +// Policy template for Linux. +// Uncomment the policies you wish to activate and change their values to +// something useful for your case. The provided values are for reference only +// and do not provide meaningful defaults! +{ +""" + +HEADER_DELIMETER = """\ + //------------------------------------------------------------------------- +""" + +MESSAGES = ''' + { + 'doc_schema_description_link': { + 'text': 'See $6' + }, + }''' + + +class JsonWriterUnittest(writer_unittest_common.WriterUnittestCommon): + '''Unit tests for JsonWriter.''' + + def CompareOutputs(self, output, expected_output): + '''Compares the output of the json_writer with its expected output. + + Args: + output: The output of the json writer. + expected_output: The expected output. + + Raises: + AssertionError: if the two strings are not equivalent. + ''' + self.assertEquals(output.strip(), expected_output.strip()) + + def testEmpty(self): + # Test the handling of an empty policy list. + policy_json = ''' + { + "policy_definitions": [], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json') + expected_output = TEMPLATE_HEADER + '}' + self.CompareOutputs(output, expected_output) + + def testEmptyWithVersion(self): + # Test the handling of an empty policy list. + policy_json = ''' + { + "policy_definitions": [], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'version': '39.0.0.0' + }, 'json') + expected_output = TEMPLATE_HEADER_WITH_VERSION + '}' + self.CompareOutputs(output, expected_output) + + def testMainPolicy(self): + # Tests a policy group with a single policy of type 'main'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "MainPolicy", + "type": "main", + "caption": "Example Main Policy", + "desc": "Example Main Policy", + "supported_on": ["chrome.linux:8-"], + "example_value": True + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'json') + expected_output = ( + TEMPLATE_HEADER + ' // Example Main Policy\n' + HEADER_DELIMETER + + ' // Example Main Policy\n\n' + ' //"MainPolicy": true\n\n' + '}') + self.CompareOutputs(output, expected_output) + + def testRecommendedOnlyPolicy(self): + # Tests a policy group with a single policy of type 'main'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "MainPolicy", + "type": "main", + "caption": "Example Main Policy", + "desc": "Example Main Policy", + "features": { + "can_be_recommended": True, + "can_be_mandatory": False + }, + "supported_on": ["chrome.linux:8-"], + "example_value": True + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'json') + expected_output = ( + TEMPLATE_HEADER + + ' // Note: this policy is supported only in recommended mode.\n' + + ' // The JSON file should be placed in' + + ' /etc/opt/chrome/policies/recommended.\n' + + ' // Example Main Policy\n' + HEADER_DELIMETER + + ' // Example Main Policy\n\n' + ' //"MainPolicy": true\n\n' + '}') + self.CompareOutputs(output, expected_output) + + def testStringPolicy(self): + # Tests a policy group with a single policy of type 'string'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "StringPolicy", + "type": "string", + "caption": "Example String Policy", + "desc": "Example String Policy", + "supported_on": ["chrome.linux:8-"], + "example_value": "hello, world!" + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json') + expected_output = ( + TEMPLATE_HEADER + ' // Example String Policy\n' + HEADER_DELIMETER + + ' // Example String Policy\n\n' + ' //"StringPolicy": "hello, world!"\n\n' + '}') + self.CompareOutputs(output, expected_output) + + def testIntPolicy(self): + # Tests a policy group with a single policy of type 'string'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "IntPolicy", + "type": "int", + "caption": "Example Int Policy", + "desc": "Example Int Policy", + "supported_on": ["chrome.linux:8-"], + "example_value": 15 + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json') + expected_output = ( + TEMPLATE_HEADER + ' // Example Int Policy\n' + HEADER_DELIMETER + + ' // Example Int Policy\n\n' + ' //"IntPolicy": 15\n\n' + '}') + self.CompareOutputs(output, expected_output) + + def testIntEnumPolicy(self): + # Tests a policy group with a single policy of type 'int-enum'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "EnumPolicy", + "type": "int-enum", + "caption": "Example Int Enum", + "desc": "Example Int Enum", + "items": [ + {"name": "ProxyServerDisabled", "value": 0, "caption": ""}, + {"name": "ProxyServerAutoDetect", "value": 1, "caption": ""}, + ], + "supported_on": ["chrome.linux:8-"], + "example_value": 1 + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'json') + expected_output = ( + TEMPLATE_HEADER + ' // Example Int Enum\n' + HEADER_DELIMETER + + ' // Example Int Enum\n\n' + ' //"EnumPolicy": 1\n\n' + '}') + self.CompareOutputs(output, expected_output) + + def testStringEnumPolicy(self): + # Tests a policy group with a single policy of type 'string-enum'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "EnumPolicy", + "type": "string-enum", + "caption": "Example String Enum", + "desc": "Example String Enum", + "items": [ + {"name": "ProxyServerDisabled", "value": "one", + "caption": ""}, + {"name": "ProxyServerAutoDetect", "value": "two", + "caption": ""}, + ], + "supported_on": ["chrome.linux:8-"], + "example_value": "one" + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'json') + expected_output = ( + TEMPLATE_HEADER + ' // Example String Enum\n' + HEADER_DELIMETER + + ' // Example String Enum\n\n' + ' //"EnumPolicy": "one"\n\n' + '}') + self.CompareOutputs(output, expected_output) + + def testListPolicy(self): + # Tests a policy group with a single policy of type 'list'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "ListPolicy", + "type": "list", + "caption": "Example List", + "desc": "Example List", + "supported_on": ["chrome.linux:8-"], + "example_value": ["foo", "bar"] + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json') + expected_output = ( + TEMPLATE_HEADER + ' // Example List\n' + HEADER_DELIMETER + + ' // Example List\n\n' + ' //"ListPolicy": ["foo", "bar"]\n\n' + '}') + self.CompareOutputs(output, expected_output) + + def testStringEnumListPolicy(self): + # Tests a policy group with a single policy of type 'string-enum-list'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "ListPolicy", + "type": "string-enum-list", + "caption": "Example List", + "desc": "Example List", + "items": [ + {"name": "ProxyServerDisabled", "value": "one", + "caption": ""}, + {"name": "ProxyServerAutoDetect", "value": "two", + "caption": ""}, + ], + "supported_on": ["chrome.linux:8-"], + "example_value": ["one", "two"] + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json') + expected_output = ( + TEMPLATE_HEADER + ' // Example List\n' + HEADER_DELIMETER + + ' // Example List\n\n' + ' //"ListPolicy": ["one", "two"]\n\n' + '}') + self.CompareOutputs(output, expected_output) + + def testDictionaryPolicy(self): + # Tests a policy group with a single policy of type 'dict'. + example = { + 'bool': True, + 'dict': { + 'a': 1, + 'b': 2, + }, + 'int': 10, + 'list': [1, 2, 3], + 'string': 'abc', + } + policy_json = ''' + { + "policy_definitions": [ + { + "name": "DictionaryPolicy", + "type": "dict", + "caption": "Example Dictionary Policy", + "desc": "Example Dictionary Policy", + "supported_on": ["chrome.linux:8-"], + "example_value": ''' + str(example) + ''' + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": %s, + }''' % MESSAGES + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json') + expected_output = ( + TEMPLATE_HEADER + ' // Example Dictionary Policy\n' + HEADER_DELIMETER + + ' // Example Dictionary Policy See ' + 'https://cloud.google.com/docs/chrome-\n' + ' // enterprise/policies/?policy=DictionaryPolicy\n\n' + ' //"DictionaryPolicy": {"bool": true, "dict": {"a": 1, ' + '"b": 2}, "int": 10, "list": [1, 2, 3], "string": "abc"}\n\n' + '}') + self.CompareOutputs(output, expected_output) + + def testExternalPolicy(self): + # Tests a policy group with a single policy of type 'external'. + example = { + "url": "https://example.com/avatar.jpg", + "hash": "deadbeef", + } + policy_json = ''' + { + "policy_definitions": [ + { + "name": "ExternalPolicy", + "type": "external", + "caption": "Example External Policy", + "desc": "Example External Policy", + "supported_on": ["chrome.linux:8-"], + "example_value": %s + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": %s, + }''' % (str(example), MESSAGES) + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json') + expected_output = ( + TEMPLATE_HEADER + ' // Example External Policy\n' + HEADER_DELIMETER + + ' // Example External Policy See ' + 'https://cloud.google.com/docs/chrome-\n' + ' // enterprise/policies/?policy=ExternalPolicy\n\n' + ' //"ExternalPolicy": {"hash": "deadbeef", "url": "https://example.com/avatar.jpg"}\n\n' + '}') + self.CompareOutputs(output, expected_output) + + def testNonSupportedPolicy(self): + # Tests a policy that is not supported on Linux, so it shouldn't + # be included in the JSON file. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "NonLinuxPolicy", + "type": "list", + "caption": "", + "desc": "", + "supported_on": ["chrome.mac:8-"], + "example_value": ["a"] + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json') + expected_output = TEMPLATE_HEADER + '}' + self.CompareOutputs(output, expected_output) + + def testPolicyGroup(self): + # Tests a policy group that has more than one policies. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "Group1", + "type": "group", + "caption": "", + "desc": "", + "policies": ["Policy1", "Policy2"], + }, + { + "name": "Policy1", + "type": "list", + "caption": "Policy One", + "desc": "Policy One", + "supported_on": ["chrome.linux:8-"], + "example_value": ["a", "b"] + }, + { + "name": "Policy2", + "type": "string", + "caption": "Policy Two", + "desc": "Policy Two", + "supported_on": ["chrome.linux:8-"], + "example_value": "c" + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json') + expected_output = ( + TEMPLATE_HEADER + ' // Policy One\n' + HEADER_DELIMETER + + ' // Policy One\n\n' + ' //"Policy1": ["a", "b"],\n\n' + ' // Policy Two\n' + HEADER_DELIMETER + ' // Policy Two\n\n' + ' //"Policy2": "c"\n\n' + '}') + self.CompareOutputs(output, expected_output) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/mock_writer.py b/chromium/components/policy/tools/template_writers/writers/mock_writer.py new file mode 100755 index 00000000000..19e1d748908 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/mock_writer.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# 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. + +from .template_writer import TemplateWriter + + +class MockWriter(TemplateWriter): + '''Helper class for unit tests in policy_template_generator_unittest.py + ''' + + def __init__(self, platforms=[], config={}): + super(MockWriter, self).__init__(platforms, config) + + def WritePolicy(self, policy): + pass + + def BeginTemplate(self): + pass + + def GetTemplateText(self): + pass + + def IsPolicySupported(self, policy): + return True + + def Test(self): + pass diff --git a/chromium/components/policy/tools/template_writers/writers/plist_helper.py b/chromium/components/policy/tools/template_writers/writers/plist_helper.py new file mode 100755 index 00000000000..e69f00dcb68 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/plist_helper.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# 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. +'''Common functions for plist_writer and plist_strings_writer. +''' + + +def GetPlistFriendlyName(name): + '''Transforms a string so that it will be suitable for use as + a pfm_name in the plist manifest file. + ''' + return name.replace(' ', '_') diff --git a/chromium/components/policy/tools/template_writers/writers/plist_strings_writer.py b/chromium/components/policy/tools/template_writers/writers/plist_strings_writer.py new file mode 100755 index 00000000000..adfbc6d279e --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/plist_strings_writer.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# 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. + +from writers import plist_helper +from writers import template_writer + + +def GetWriter(config): + '''Factory method for creating PListStringsWriter objects. + See the constructor of TemplateWriter for description of + arguments. + ''' + return PListStringsWriter(['mac'], config) + + +class PListStringsWriter(template_writer.TemplateWriter): + '''Outputs localized string table files for the Mac policy file. + These files are named Localizable.strings and they are in the + [lang].lproj subdirectories of the manifest bundle. + ''' + + def WriteComment(self, comment): + self._out.append('/* ' + comment + ' */') + + def _AddToStringTable(self, item_name, caption, desc): + '''Add a title and a description of an item to the string table. + + Args: + item_name: The name of the item that will get the title and the + description. + title: The text of the title to add. + desc: The text of the description to add. + ''' + caption = caption.replace('"', '\\"') + caption = caption.replace('\n', '\\n') + desc = desc.replace('"', '\\"') + desc = desc.replace('\n', '\\n') + self._out.append('%s.pfm_title = \"%s\";' % (item_name, caption)) + self._out.append('%s.pfm_description = \"%s\";' % (item_name, desc)) + + def PreprocessPolicies(self, policy_list): + return self.FlattenGroupsAndSortPolicies(policy_list) + + def WritePolicy(self, policy): + '''Add strings to the stringtable corresponding a given policy. + + Args: + policy: The policy for which the strings will be added to the + string table. + ''' + desc = policy['desc'] + if policy['type'] in ('int-enum', 'string-enum', 'string-enum-list'): + # Append the captions of enum items to the description string. + item_descs = [] + for item in policy['items']: + item_descs.append(str(item['value']) + ' - ' + item['caption']) + desc = '\n'.join(item_descs) + '\n' + desc + if self.HasExpandedPolicyDescription(policy): + desc += '\n' + self.GetExpandedPolicyDescription(policy) + + self._AddToStringTable(policy['name'], policy['label'], desc) + + def BeginTemplate(self): + app_name = plist_helper.GetPlistFriendlyName(self.config['app_name']) + if self._GetChromiumVersionString() is not None: + self.WriteComment(self.config['build'] + ''' version: ''' + \ + self._GetChromiumVersionString()) + self._AddToStringTable(app_name, self.config['app_name'], + self.messages['mac_chrome_preferences']['text']) + + def Init(self): + # A buffer for the lines of the string table being generated. + self._out = [] + + def GetTemplateText(self): + return '\n'.join(self._out) diff --git a/chromium/components/policy/tools/template_writers/writers/plist_strings_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/plist_strings_writer_unittest.py new file mode 100755 index 00000000000..91c39f431e3 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/plist_strings_writer_unittest.py @@ -0,0 +1,402 @@ +#!/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. +'''Unit tests for writers.plist_strings_writer''' + +import os +import sys +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +import unittest + +from writers import writer_unittest_common + + +class PListStringsWriterUnittest(writer_unittest_common.WriterUnittestCommon): + '''Unit tests for PListStringsWriter.''' + + def testEmpty(self): + # Test PListStringsWriter in case of empty polices. + policy_json = ''' + { + 'policy_definitions': [], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': { + 'mac_chrome_preferences': { + 'text': 'Chromium preferen"ces', + 'desc': 'blah' + } + } + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist_strings') + expected_output = ('Chromium.pfm_title = "Chromium";\n' + 'Chromium.pfm_description = "Chromium preferen\\"ces";') + self.assertEquals(output.strip(), expected_output.strip()) + + def testEmptyVersion(self): + # Test PListStringsWriter in case of empty polices. + policy_json = ''' + { + 'policy_definitions': [], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': { + 'mac_chrome_preferences': { + 'text': 'Chromium preferen"ces', + 'desc': 'blah' + } + } + }''' + output = self.GetOutput( + policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test', + 'version': '39.0.0.0' + }, 'plist_strings') + expected_output = ('/* chromium version: 39.0.0.0 */\n' + 'Chromium.pfm_title = "Chromium";\n' + 'Chromium.pfm_description = "Chromium preferen\\"ces";') + self.assertEquals(output.strip(), expected_output.strip()) + + def testMainPolicy(self): + # Tests a policy group with a single policy of type 'main'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'MainGroup', + 'type': 'group', + 'caption': 'Caption of main.', + 'desc': 'Description of main.', + 'policies': ['MainPolicy'], + }, + { + 'name': 'MainPolicy', + 'type': 'main', + 'supported_on': ['chrome.mac:8-'], + 'caption': 'Caption of main policy.', + 'desc': 'Description of main policy.', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': { + 'mac_chrome_preferences': { + 'text': 'Preferences of Google Chrome', + 'desc': 'blah' + } + } + }''' + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist_strings') + expected_output = ( + 'Google_Chrome.pfm_title = "Google Chrome";\n' + 'Google_Chrome.pfm_description = "Preferences of Google Chrome";\n' + 'MainPolicy.pfm_title = "Caption of main policy.";\n' + 'MainPolicy.pfm_description = "Description of main policy.";') + self.assertEquals(output.strip(), expected_output.strip()) + + def testStringPolicy(self): + # Tests a policy group with a single policy of type 'string'. Also test + # inheriting group description to policy description. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'StringGroup', + 'type': 'group', + 'caption': 'Caption of group.', + 'desc': """Description of group. +With a newline.""", + 'policies': ['StringPolicy'], + }, + { + 'name': 'StringPolicy', + 'type': 'string', + 'caption': 'Caption of policy.', + 'desc': """Description of policy. +With a newline.""", + 'supported_on': ['chrome.mac:8-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': { + 'mac_chrome_preferences': { + 'text': 'Preferences of Chromium', + 'desc': 'blah' + } + } + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist_strings') + expected_output = ('Chromium.pfm_title = "Chromium";\n' + 'Chromium.pfm_description = "Preferences of Chromium";\n' + 'StringPolicy.pfm_title = "Caption of policy.";\n' + 'StringPolicy.pfm_description = ' + '"Description of policy.\\nWith a newline.";') + self.assertEquals(output.strip(), expected_output.strip()) + + def testStringListPolicy(self): + # Tests a policy group with a single policy of type 'list'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'ListGroup', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['ListPolicy'], + }, + { + 'name': 'ListPolicy', + 'type': 'list', + 'caption': 'Caption of policy.', + 'desc': """Description of policy. +With a newline.""", + 'schema': { + 'type': 'array', + 'items': { 'type': 'string' }, + }, + 'supported_on': ['chrome.mac:8-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': { + 'mac_chrome_preferences': { + 'text': 'Preferences of Chromium', + 'desc': 'blah' + } + } + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist_strings') + expected_output = ('Chromium.pfm_title = "Chromium";\n' + 'Chromium.pfm_description = "Preferences of Chromium";\n' + 'ListPolicy.pfm_title = "Caption of policy.";\n' + 'ListPolicy.pfm_description = ' + '"Description of policy.\\nWith a newline.";') + self.assertEquals(output.strip(), expected_output.strip()) + + def testStringEnumListPolicy(self): + # Tests a policy group with a single policy of type 'string-enum-list'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'EnumGroup', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['EnumPolicy'], + }, + { + 'name': 'EnumPolicy', + 'type': 'string-enum-list', + 'caption': 'Caption of policy.', + 'desc': """Description of policy. +With a newline.""", + 'schema': { + 'type': 'array', + 'items': { 'type': 'string' }, + }, + 'items': [ + { + 'name': 'ProxyServerDisabled', + 'value': 'one', + 'caption': 'Option1' + }, + { + 'name': 'ProxyServerAutoDetect', + 'value': 'two', + 'caption': 'Option2' + }, + ], + 'supported_on': ['chrome.mac:8-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': { + 'mac_chrome_preferences': { + 'text': 'Preferences of Chromium', + 'desc': 'blah' + } + } + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist_strings') + expected_output = ('Chromium.pfm_title = "Chromium";\n' + 'Chromium.pfm_description = "Preferences of Chromium";\n' + 'EnumPolicy.pfm_title = "Caption of policy.";\n' + 'EnumPolicy.pfm_description = ' + '"one - Option1\\ntwo - Option2\\n' + 'Description of policy.\\nWith a newline.";') + self.assertEquals(output.strip(), expected_output.strip()) + + def testIntEnumPolicy(self): + # Tests a policy group with a single policy of type 'int-enum'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'EnumGroup', + 'type': 'group', + 'desc': '', + 'caption': '', + 'policies': ['EnumPolicy'], + }, + { + 'name': 'EnumPolicy', + 'type': 'int-enum', + 'desc': 'Description of policy.', + 'caption': 'Caption of policy.', + 'items': [ + { + 'name': 'ProxyServerDisabled', + 'value': 0, + 'caption': 'Option1' + }, + { + 'name': 'ProxyServerAutoDetect', + 'value': 1, + 'caption': 'Option2' + }, + ], + 'supported_on': ['chrome.mac:8-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': { + 'mac_chrome_preferences': { + 'text': 'Google Chrome preferences', + 'desc': 'blah' + } + } + }''' + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'mac_bundle_id': 'com.example.Test2' + }, 'plist_strings') + expected_output = ( + 'Google_Chrome.pfm_title = "Google Chrome";\n' + 'Google_Chrome.pfm_description = "Google Chrome preferences";\n' + 'EnumPolicy.pfm_title = "Caption of policy.";\n' + 'EnumPolicy.pfm_description = ' + '"0 - Option1\\n1 - Option2\\nDescription of policy.";\n') + + self.assertEquals(output.strip(), expected_output.strip()) + + def testStringEnumPolicy(self): + # Tests a policy group with a single policy of type 'string-enum'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'EnumGroup', + 'type': 'group', + 'desc': '', + 'caption': '', + 'policies': ['EnumPolicy'], + }, + { + 'name': 'EnumPolicy', + 'type': 'string-enum', + 'desc': 'Description of policy.', + 'caption': 'Caption of policy.', + 'items': [ + { + 'name': 'ProxyServerDisabled', + 'value': 'one', + 'caption': 'Option1' + }, + { + 'name': 'ProxyServerAutoDetect', + 'value': 'two', + 'caption': 'Option2' + }, + ], + 'supported_on': ['chrome.mac:8-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': { + 'mac_chrome_preferences': { + 'text': 'Google Chrome preferences', + 'desc': 'blah' + } + } + }''' + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'mac_bundle_id': 'com.example.Test2' + }, 'plist_strings') + expected_output = ( + 'Google_Chrome.pfm_title = "Google Chrome";\n' + 'Google_Chrome.pfm_description = "Google Chrome preferences";\n' + 'EnumPolicy.pfm_title = "Caption of policy.";\n' + 'EnumPolicy.pfm_description = ' + '"one - Option1\\ntwo - Option2\\nDescription of policy.";\n') + + self.assertEquals(output.strip(), expected_output.strip()) + + def testNonSupportedPolicy(self): + # Tests a policy that is not supported on Mac, so its strings shouldn't + # be included in the plist string table. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'NonMacGroup', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['NonMacPolicy'], + }, + { + 'name': 'NonMacPolicy', + 'type': 'string', + 'caption': '', + 'desc': '', + 'supported_on': ['chrome_os:8-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': { + 'mac_chrome_preferences': { + 'text': 'Google Chrome preferences', + 'desc': 'blah' + } + } + }''' + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'mac_bundle_id': 'com.example.Test2' + }, 'plist_strings') + expected_output = ( + 'Google_Chrome.pfm_title = "Google Chrome";\n' + 'Google_Chrome.pfm_description = "Google Chrome preferences";') + self.assertEquals(output.strip(), expected_output.strip()) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/plist_writer.py b/chromium/components/policy/tools/template_writers/writers/plist_writer.py new file mode 100755 index 00000000000..e8666486457 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/plist_writer.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# 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. + +from xml.dom import minidom +from writers import plist_helper +from writers import xml_formatted_writer + +# This writer outputs a Preferences Manifest file as documented at +# https://developer.apple.com/library/mac/documentation/MacOSXServer/Conceptual/Preference_Manifest_Files + + +def GetWriter(config): + '''Factory method for creating PListWriter objects. + See the constructor of TemplateWriter for description of + arguments. + ''' + return PListWriter(['mac'], config) + + +class PListWriter(xml_formatted_writer.XMLFormattedWriter): + '''Class for generating policy templates in Mac plist format. + It is used by PolicyTemplateGenerator to write plist files. + ''' + + STRING_TABLE = 'Localizable.strings' + TYPE_TO_INPUT = { + 'string': 'string', + 'int': 'integer', + 'int-enum': 'integer', + 'string-enum': 'string', + 'string-enum-list': 'array', + 'main': 'boolean', + 'list': 'array', + 'dict': 'dictionary', + 'external': 'dictionary', + } + + def _AddKeyValuePair(self, parent, key_string, value_tag): + '''Adds a plist key-value pair to a parent XML element. + + A key-value pair in plist consists of two XML elements next two each other: + <key>key_string</key> + <value_tag>...</value_tag> + + Args: + key_string: The content of the key tag. + value_tag: The name of the value element. + + Returns: + The XML element of the value tag. + ''' + self.AddElement(parent, 'key', {}, key_string) + return self.AddElement(parent, value_tag) + + def _AddStringKeyValuePair(self, parent, key_string, value_string): + '''Adds a plist key-value pair to a parent XML element, where the + value element contains a string. The name of the value element will be + <string>. + + Args: + key_string: The content of the key tag. + value_string: The content of the value tag. + ''' + self.AddElement(parent, 'key', {}, key_string) + self.AddElement(parent, 'string', {}, value_string) + + def _AddRealKeyValuePair(self, parent, key_string, value_string): + '''Adds a plist key-value pair to a parent XML element, where the + value element contains a real number. The name of the value element will be + <real>. + + Args: + key_string: The content of the key tag. + value_string: The content of the value tag. + ''' + self.AddElement(parent, 'key', {}, key_string) + self.AddElement(parent, 'real', {}, value_string) + + def _AddTargets(self, parent, policy): + '''Adds the following XML snippet to an XML element: + <key>pfm_targets</key> + <array> + <string>user-managed</string> + </array> + + Args: + parent: The parent XML element where the snippet will be added. + ''' + array = self._AddKeyValuePair(parent, 'pfm_targets', 'array') + if self.CanBeRecommended(policy): + self.AddElement(array, 'string', {}, 'user') + if self.CanBeMandatory(policy): + self.AddElement(array, 'string', {}, 'user-managed') + + def PreprocessPolicies(self, policy_list): + return self.FlattenGroupsAndSortPolicies(policy_list) + + def WritePolicy(self, policy): + policy_name = policy['name'] + policy_type = policy['type'] + + dict = self.AddElement(self._array, 'dict') + self._AddStringKeyValuePair(dict, 'pfm_name', policy_name) + # Set empty strings for title and description. They will be taken by the + # OSX Workgroup Manager from the string table in a Localizable.strings file. + # Those files are generated by plist_strings_writer. + self._AddStringKeyValuePair(dict, 'pfm_description', '') + self._AddStringKeyValuePair(dict, 'pfm_title', '') + self._AddTargets(dict, policy) + self._AddStringKeyValuePair(dict, 'pfm_type', + self.TYPE_TO_INPUT[policy_type]) + if policy_type in ('int-enum', 'string-enum'): + range_list = self._AddKeyValuePair(dict, 'pfm_range_list', 'array') + for item in policy['items']: + if policy_type == 'int-enum': + element_type = 'integer' + else: + element_type = 'string' + self.AddElement(range_list, element_type, {}, str(item['value'])) + elif policy_type in ('list', 'string-enum-list'): + subkeys = self._AddKeyValuePair(dict, 'pfm_subkeys', 'array') + subkeys_dict = self.AddElement(subkeys, 'dict') + subkeys_type = self._AddKeyValuePair(subkeys_dict, 'pfm_type', 'string') + self.AddText(subkeys_type, 'string') + + def BeginTemplate(self): + self._plist.attributes['version'] = '1' + dict = self.AddElement(self._plist, 'dict') + if self._GetChromiumVersionString() is not None: + self.AddComment(self._plist, self.config['build'] + ' version: ' + \ + self._GetChromiumVersionString()) + app_name = plist_helper.GetPlistFriendlyName(self.config['app_name']) + self._AddStringKeyValuePair(dict, 'pfm_name', app_name) + self._AddStringKeyValuePair(dict, 'pfm_description', '') + self._AddStringKeyValuePair(dict, 'pfm_title', '') + self._AddRealKeyValuePair(dict, 'pfm_version', '1') + self._AddStringKeyValuePair(dict, 'pfm_domain', + self.config['mac_bundle_id']) + + self._array = self._AddKeyValuePair(dict, 'pfm_subkeys', 'array') + + def CreatePlistDocument(self): + dom_impl = minidom.getDOMImplementation('') + doctype = dom_impl.createDocumentType( + 'plist', '-//Apple//DTD PLIST 1.0//EN', + 'http://www.apple.com/DTDs/PropertyList-1.0.dtd') + return dom_impl.createDocument(None, 'plist', doctype) + + def Init(self): + self._doc = self.CreatePlistDocument() + self._plist = self._doc.documentElement + + def GetTemplateText(self): + return self.ToPrettyXml(self._doc) diff --git a/chromium/components/policy/tools/template_writers/writers/plist_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/plist_writer_unittest.py new file mode 100755 index 00000000000..92d75ee7f8f --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/plist_writer_unittest.py @@ -0,0 +1,732 @@ +#!/usr/bin/env python3 +# 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. +'''Unit tests for writers.plist_writer''' + +import os +import sys +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +import unittest + +from writers import writer_unittest_common + + +class PListWriterUnittest(writer_unittest_common.WriterUnittestCommon): + '''Unit tests for PListWriter.''' + + def _GetExpectedOutputs(self, product_name, bundle_id, policies): + '''Substitutes the variable parts into a plist template. The result + of this function can be used as an expected result to test the output + of PListWriter. + + Args: + product_name: The name of the product, normally Chromium or Google Chrome. + bundle_id: The mac bundle id of the product. + policies: The list of policies. + + Returns: + The text of a plist template with the variable parts substituted. + ''' + return ''' +<?xml version="1.0" ?> +<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'> +<plist version="1"> + <dict> + <key>pfm_name</key> + <string>%s</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_version</key> + <real>1</real> + <key>pfm_domain</key> + <string>%s</string> + <key>pfm_subkeys</key> + %s + </dict> +</plist>''' % (product_name, bundle_id, policies) + + def _GetExpectedOutputsWithVersion(self, product_name, bundle_id, policies, + version): + '''Substitutes the variable parts into a plist template. The result + of this function can be used as an expected result to test the output + of PListWriter. + + Args: + product_name: The name of the product, normally Chromium or Google Chrome. + bundle_id: The mac bundle id of the product. + policies: The list of policies. + + Returns: + The text of a plist template with the variable parts substituted. + ''' + return ''' +<?xml version="1.0" ?> +<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'> +<plist version="1"> + <dict> + <key>pfm_name</key> + <string>%s</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_version</key> + <real>1</real> + <key>pfm_domain</key> + <string>%s</string> + <key>pfm_subkeys</key> + %s + </dict> + <!--%s--> +</plist>''' % (product_name, bundle_id, policies, version) + + def testEmpty(self): + # Test PListWriter in case of empty polices. + policy_json = ''' + { + 'policy_definitions': [], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {}, + }''' + + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist') + expected_output = self._GetExpectedOutputs('Chromium', 'com.example.Test', + '<array/>') + self.assertEquals(output.strip(), expected_output.strip()) + + def testEmptyVersion(self): + # Test PListWriter in case of empty polices. + policy_json = ''' + { + 'policy_definitions': [], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {}, + }''' + + output = self.GetOutput( + policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test', + 'version': '39.0.0.0' + }, 'plist') + expected_output = self._GetExpectedOutputsWithVersion( + 'Chromium', 'com.example.Test', '<array/>', + 'chromium version: 39.0.0.0') + self.assertEquals(output.strip(), expected_output.strip()) + + def testMainPolicy(self): + # Tests a policy group with a single policy of type 'main'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'MainGroup', + 'type': 'group', + 'policies': ['MainPolicy'], + 'desc': '', + 'caption': '', + }, + { + 'name': 'MainPolicy', + 'type': 'main', + 'desc': '', + 'caption': '', + 'supported_on': ['chrome.mac:8-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {} + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist') + expected_output = self._GetExpectedOutputs( + 'Chromium', 'com.example.Test', '''<array> + <dict> + <key>pfm_name</key> + <string>MainPolicy</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_targets</key> + <array> + <string>user-managed</string> + </array> + <key>pfm_type</key> + <string>boolean</string> + </dict> + </array>''') + self.assertEquals(output.strip(), expected_output.strip()) + + def testRecommendedPolicy(self): + # Tests a policy group with a single policy of type 'main'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'MainGroup', + 'type': 'group', + 'policies': ['MainPolicy'], + 'desc': '', + 'caption': '', + }, + { + 'name': 'MainPolicy', + 'type': 'main', + 'desc': '', + 'caption': '', + 'features': { + 'can_be_recommended' : True + }, + 'supported_on': ['chrome.mac:8-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {} + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist') + expected_output = self._GetExpectedOutputs( + 'Chromium', 'com.example.Test', '''<array> + <dict> + <key>pfm_name</key> + <string>MainPolicy</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_targets</key> + <array> + <string>user</string> + <string>user-managed</string> + </array> + <key>pfm_type</key> + <string>boolean</string> + </dict> + </array>''') + self.assertEquals(output.strip(), expected_output.strip()) + + def testRecommendedOnlyPolicy(self): + # Tests a policy group with a single policy of type 'main'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'MainGroup', + 'type': 'group', + 'policies': ['MainPolicy'], + 'desc': '', + 'caption': '', + }, + { + 'name': 'MainPolicy', + 'type': 'main', + 'desc': '', + 'caption': '', + 'features': { + 'can_be_recommended' : True, + 'can_be_mandatory' : False + }, + 'supported_on': ['chrome.mac:8-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {} + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist') + expected_output = self._GetExpectedOutputs( + 'Chromium', 'com.example.Test', '''<array> + <dict> + <key>pfm_name</key> + <string>MainPolicy</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_targets</key> + <array> + <string>user</string> + </array> + <key>pfm_type</key> + <string>boolean</string> + </dict> + </array>''') + self.assertEquals(output.strip(), expected_output.strip()) + + def testStringPolicy(self): + # Tests a policy group with a single policy of type 'string'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'StringGroup', + 'type': 'group', + 'desc': '', + 'caption': '', + 'policies': ['StringPolicy'], + }, + { + 'name': 'StringPolicy', + 'type': 'string', + 'supported_on': ['chrome.mac:8-'], + 'desc': '', + 'caption': '', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {}, + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist') + expected_output = self._GetExpectedOutputs( + 'Chromium', 'com.example.Test', '''<array> + <dict> + <key>pfm_name</key> + <string>StringPolicy</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_targets</key> + <array> + <string>user-managed</string> + </array> + <key>pfm_type</key> + <string>string</string> + </dict> + </array>''') + self.assertEquals(output.strip(), expected_output.strip()) + + def testListPolicy(self): + # Tests a policy group with a single policy of type 'list'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'ListGroup', + 'type': 'group', + 'desc': '', + 'caption': '', + 'policies': ['ListPolicy'], + }, + { + 'name': 'ListPolicy', + 'type': 'list', + 'schema': { + 'type': 'array', + 'items': { 'type': 'string' }, + }, + 'supported_on': ['chrome.mac:8-'], + 'desc': '', + 'caption': '', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {}, + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist') + expected_output = self._GetExpectedOutputs( + 'Chromium', 'com.example.Test', '''<array> + <dict> + <key>pfm_name</key> + <string>ListPolicy</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_targets</key> + <array> + <string>user-managed</string> + </array> + <key>pfm_type</key> + <string>array</string> + <key>pfm_subkeys</key> + <array> + <dict> + <key>pfm_type</key> + <string>string</string> + </dict> + </array> + </dict> + </array>''') + self.assertEquals(output.strip(), expected_output.strip()) + + def testStringEnumListPolicy(self): + # Tests a policy group with a single policy of type 'string-enum-list'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'ListGroup', + 'type': 'group', + 'desc': '', + 'caption': '', + 'policies': ['ListPolicy'], + }, + { + 'name': 'ListPolicy', + 'type': 'string-enum-list', + 'schema': { + 'type': 'array', + 'items': { 'type': 'string' }, + }, + 'items': [ + {'name': 'ProxyServerDisabled', 'value': 'one', 'caption': ''}, + {'name': 'ProxyServerAutoDetect', 'value': 'two', 'caption': ''}, + ], + 'supported_on': ['chrome.mac:8-'], + 'supported_on': ['chrome.mac:8-'], + 'desc': '', + 'caption': '', + } + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {}, + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist') + expected_output = self._GetExpectedOutputs( + 'Chromium', 'com.example.Test', '''<array> + <dict> + <key>pfm_name</key> + <string>ListPolicy</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_targets</key> + <array> + <string>user-managed</string> + </array> + <key>pfm_type</key> + <string>array</string> + <key>pfm_subkeys</key> + <array> + <dict> + <key>pfm_type</key> + <string>string</string> + </dict> + </array> + </dict> + </array>''') + self.assertEquals(output.strip(), expected_output.strip()) + + def testIntPolicy(self): + # Tests a policy group with a single policy of type 'int'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'IntGroup', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['IntPolicy'], + }, + { + 'name': 'IntPolicy', + 'type': 'int', + 'caption': '', + 'desc': '', + 'supported_on': ['chrome.mac:8-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {}, + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist') + expected_output = self._GetExpectedOutputs( + 'Chromium', 'com.example.Test', '''<array> + <dict> + <key>pfm_name</key> + <string>IntPolicy</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_targets</key> + <array> + <string>user-managed</string> + </array> + <key>pfm_type</key> + <string>integer</string> + </dict> + </array>''') + self.assertEquals(output.strip(), expected_output.strip()) + + def testIntEnumPolicy(self): + # Tests a policy group with a single policy of type 'int-enum'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'EnumGroup', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['EnumPolicy'], + }, + { + 'name': 'EnumPolicy', + 'type': 'int-enum', + 'desc': '', + 'caption': '', + 'items': [ + {'name': 'ProxyServerDisabled', 'value': 0, 'caption': ''}, + {'name': 'ProxyServerAutoDetect', 'value': 1, 'caption': ''}, + ], + 'supported_on': ['chrome.mac:8-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {}, + }''' + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'mac_bundle_id': 'com.example.Test2' + }, 'plist') + expected_output = self._GetExpectedOutputs( + 'Google_Chrome', 'com.example.Test2', '''<array> + <dict> + <key>pfm_name</key> + <string>EnumPolicy</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_targets</key> + <array> + <string>user-managed</string> + </array> + <key>pfm_type</key> + <string>integer</string> + <key>pfm_range_list</key> + <array> + <integer>0</integer> + <integer>1</integer> + </array> + </dict> + </array>''') + self.assertEquals(output.strip(), expected_output.strip()) + + def testStringEnumPolicy(self): + # Tests a policy group with a single policy of type 'string-enum'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'EnumGroup', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['EnumPolicy'], + }, + { + 'name': 'EnumPolicy', + 'type': 'string-enum', + 'desc': '', + 'caption': '', + 'items': [ + {'name': 'ProxyServerDisabled', 'value': 'one', 'caption': ''}, + {'name': 'ProxyServerAutoDetect', 'value': 'two', 'caption': ''}, + ], + 'supported_on': ['chrome.mac:8-'], + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {}, + }''' + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'mac_bundle_id': 'com.example.Test2' + }, 'plist') + expected_output = self._GetExpectedOutputs( + 'Google_Chrome', 'com.example.Test2', '''<array> + <dict> + <key>pfm_name</key> + <string>EnumPolicy</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_targets</key> + <array> + <string>user-managed</string> + </array> + <key>pfm_type</key> + <string>string</string> + <key>pfm_range_list</key> + <array> + <string>one</string> + <string>two</string> + </array> + </dict> + </array>''') + self.assertEquals(output.strip(), expected_output.strip()) + + def testDictionaryPolicy(self): + # Tests a policy group with a single policy of type 'dict'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'DictionaryGroup', + 'type': 'group', + 'desc': '', + 'caption': '', + 'policies': ['DictionaryPolicy'], + }, + { + 'name': 'DictionaryPolicy', + 'type': 'dict', + 'supported_on': ['chrome.mac:8-'], + 'desc': '', + 'caption': '', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {}, + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist') + expected_output = self._GetExpectedOutputs( + 'Chromium', 'com.example.Test', '''<array> + <dict> + <key>pfm_name</key> + <string>DictionaryPolicy</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_targets</key> + <array> + <string>user-managed</string> + </array> + <key>pfm_type</key> + <string>dictionary</string> + </dict> + </array>''') + self.assertEquals(output.strip(), expected_output.strip()) + + def testExternalPolicy(self): + # Tests a policy group with a single policy of type 'dict'. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'ExternalGroup', + 'type': 'group', + 'desc': '', + 'caption': '', + 'policies': ['ExternalPolicy'], + }, + { + 'name': 'ExternalPolicy', + 'type': 'external', + 'supported_on': ['chrome.mac:8-'], + 'desc': '', + 'caption': '', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {}, + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'mac_bundle_id': 'com.example.Test' + }, 'plist') + expected_output = self._GetExpectedOutputs( + 'Chromium', 'com.example.Test', '''<array> + <dict> + <key>pfm_name</key> + <string>ExternalPolicy</string> + <key>pfm_description</key> + <string/> + <key>pfm_title</key> + <string/> + <key>pfm_targets</key> + <array> + <string>user-managed</string> + </array> + <key>pfm_type</key> + <string>dictionary</string> + </dict> + </array>''') + self.assertEquals(output.strip(), expected_output.strip()) + + def testNonSupportedPolicy(self): + # Tests a policy that is not supported on Mac, so it shouldn't + # be included in the plist file. + policy_json = ''' + { + 'policy_definitions': [ + { + 'name': 'NonMacGroup', + 'type': 'group', + 'caption': '', + 'desc': '', + 'policies': ['NonMacPolicy'], + }, + { + 'name': 'NonMacPolicy', + 'type': 'string', + 'supported_on': ['chrome.linux:8-', 'chrome.win:7-'], + 'caption': '', + 'desc': '', + }, + ], + 'policy_atomic_group_definitions': [], + 'placeholders': [], + 'messages': {}, + }''' + output = self.GetOutput(policy_json, { + '_google_chrome': '1', + 'mac_bundle_id': 'com.example.Test2' + }, 'plist') + expected_output = self._GetExpectedOutputs( + 'Google_Chrome', 'com.example.Test2', '''<array/>''') + self.assertEquals(output.strip(), expected_output.strip()) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/reg_writer.py b/chromium/components/policy/tools/template_writers/writers/reg_writer.py new file mode 100755 index 00000000000..3fbd0a6b358 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/reg_writer.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# 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. + +import json + +from writers import template_writer + + +def GetWriter(config): + '''Factory method for creating RegWriter objects. + See the constructor of TemplateWriter for description of + arguments. + ''' + return RegWriter(['win', 'win7'], config) + + +class RegWriter(template_writer.TemplateWriter): + '''Class for generating policy example files in .reg format (for Windows). + The generated files will define all the supported policies with example + values set for them. This class is used by PolicyTemplateGenerator to + write .reg files. + ''' + + NEWLINE = '\r\n' + + def _QuoteAndEscapeString(self, string): + assert isinstance(string, str) + return json.dumps(string) + + def _StartBlock(self, key, suffix, list): + key = 'HKEY_LOCAL_MACHINE\\' + key + if suffix: + key = key + '\\' + suffix + if key != self._last_key.get(id(list), None): + list.append('') + list.append('[%s]' % key) + self._last_key[id(list)] = key + + def PreprocessPolicies(self, policy_list): + return self.FlattenGroupsAndSortPolicies(policy_list, + self.GetPolicySortingKey) + + def GetPolicySortingKey(self, policy): + '''Extracts a sorting key from a policy. These keys can be used for + list.sort() methods to sort policies. + See TemplateWriter.SortPoliciesGroupsFirst for usage. + ''' + is_list = policy['type'] in ('list', 'string-enum-list') + # Lists come after regular policies. + return (is_list, policy['name']) + + def _WritePolicy(self, policy, key, list): + example_value = policy['example_value'] + + if policy['type'] in ('list', 'string-enum-list'): + self._StartBlock(key, policy['name'], list) + i = 1 + for item in example_value: + list.append('"%d"=%s' % (i, self._QuoteAndEscapeString(item))) + i = i + 1 + else: + self._StartBlock(key, None, list) + if policy['type'] in ('string', 'string-enum'): + example_value_str = self._QuoteAndEscapeString(example_value) + elif policy['type'] in ('dict', 'external'): + example_value_str = self._QuoteAndEscapeString( + json.dumps(example_value, sort_keys=True)) + elif policy['type'] in ('main', 'int', 'int-enum'): + example_value_str = 'dword:%08x' % int(example_value) + else: + raise Exception('unknown policy type %s:' % policy['type']) + + list.append('"%s"=%s' % (policy['name'], example_value_str)) + + def WriteComment(self, comment): + self._prefix.append('; ' + comment) + + def WritePolicy(self, policy): + if self.CanBeMandatory(policy): + self._WritePolicy(policy, self._winconfig['reg_mandatory_key_name'], + self._mandatory) + + def WriteRecommendedPolicy(self, policy): + self._WritePolicy(policy, self._winconfig['reg_recommended_key_name'], + self._recommended) + + def BeginTemplate(self): + pass + + def EndTemplate(self): + pass + + def Init(self): + self._mandatory = [] + self._recommended = [] + self._last_key = {} + self._prefix = [] + self._winconfig = self.config['win_config']['win'] + + def GetTemplateText(self): + self._prefix.append('Windows Registry Editor Version 5.00') + if self._GetChromiumVersionString() is not None: + self.WriteComment(self.config['build'] + ' version: ' + \ + self._GetChromiumVersionString()) + all = self._prefix + self._mandatory + self._recommended + return self.NEWLINE.join(all) diff --git a/chromium/components/policy/tools/template_writers/writers/reg_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/reg_writer_unittest.py new file mode 100755 index 00000000000..7278f76b007 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/reg_writer_unittest.py @@ -0,0 +1,428 @@ +#!/usr/bin/env python3 +# 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. +'''Unit tests for writers.reg_writer''' + +import os +import sys +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +import unittest + +from writers import writer_unittest_common + + +class RegWriterUnittest(writer_unittest_common.WriterUnittestCommon): + '''Unit tests for RegWriter.''' + + NEWLINE = '\r\n' + + def CompareOutputs(self, output, expected_output): + '''Compares the output of the reg_writer with its expected output. + + Args: + output: The output of the reg writer. + expected_output: The expected output. + + Raises: + AssertionError: if the two strings are not equivalent. + ''' + self.assertEquals(output.strip(), expected_output.strip()) + + def testEmpty(self): + # Test the handling of an empty policy list. + policy_json = ''' + { + "policy_definitions": [], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {} + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + }, 'reg') + expected_output = 'Windows Registry Editor Version 5.00' + self.CompareOutputs(output, expected_output) + + def testEmptyVersion(self): + # Test the handling of an empty policy list. + policy_json = ''' + { + "policy_definitions": [], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {} + }''' + output = self.GetOutput(policy_json, { + '_chromium': '1', + 'version': '39.0.0.0' + }, 'reg') + expected_output = ('Windows Registry Editor Version 5.00\r\n' + '; chromium version: 39.0.0.0\r\n') + self.CompareOutputs(output, expected_output) + + def testMainPolicy(self): + # Tests a policy group with a single policy of type 'main'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "MainPolicy", + "type": "main", + "features": { "can_be_recommended": True }, + "caption": "", + "desc": "", + "supported_on": ["chrome.win:8-"], + "example_value": True + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'reg') + expected_output = self.NEWLINE.join([ + 'Windows Registry Editor Version 5.00', '', + '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome]', + '"MainPolicy"=dword:00000001', '', + '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome\\Recommended]', + '"MainPolicy"=dword:00000001' + ]) + self.CompareOutputs(output, expected_output) + + def testRecommendedMainPolicy(self): + # Tests a policy group with a single policy of type 'main'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "MainPolicy", + "type": "main", + "features": { + "can_be_recommended": True, + "can_be_mandatory": False + }, + "caption": "", + "desc": "", + "supported_on": ["chrome.win:8-"], + "example_value": True + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'reg') + expected_output = self.NEWLINE.join([ + 'Windows Registry Editor Version 5.00', '', + '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome\\Recommended]', + '"MainPolicy"=dword:00000001' + ]) + self.CompareOutputs(output, expected_output) + + def testStringPolicy(self): + # Tests a policy group with a single policy of type 'string'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "StringPolicy", + "type": "string", + "caption": "", + "desc": "", + "supported_on": ["chrome.win:8-"], + "example_value": "hello, world! \\\" \\\\" + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg') + expected_output = self.NEWLINE.join([ + 'Windows Registry Editor Version 5.00', '', + '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium]', + '"StringPolicy"="hello, world! \\\" \\\\"' + ]) + self.CompareOutputs(output, expected_output) + + def testIntPolicy(self): + # Tests a policy group with a single policy of type 'int'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "IntPolicy", + "type": "int", + "caption": "", + "desc": "", + "supported_on": ["chrome.win:8-"], + "example_value": 26 + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg') + expected_output = self.NEWLINE.join([ + 'Windows Registry Editor Version 5.00', '', + '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium]', + '"IntPolicy"=dword:0000001a' + ]) + self.CompareOutputs(output, expected_output) + + def testIntEnumPolicy(self): + # Tests a policy group with a single policy of type 'int-enum'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "EnumPolicy", + "type": "int-enum", + "caption": "", + "desc": "", + "items": [ + {"name": "ProxyServerDisabled", "value": 0, "caption": ""}, + {"name": "ProxyServerAutoDetect", "value": 1, "caption": ""}, + ], + "supported_on": ["chrome.win:8-"], + "example_value": 1 + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'reg') + expected_output = self.NEWLINE.join([ + 'Windows Registry Editor Version 5.00', '', + '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome]', + '"EnumPolicy"=dword:00000001' + ]) + self.CompareOutputs(output, expected_output) + + def testStringEnumPolicy(self): + # Tests a policy group with a single policy of type 'string-enum'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "EnumPolicy", + "type": "string-enum", + "caption": "", + "desc": "", + "items": [ + {"name": "ProxyServerDisabled", "value": "one", "caption": ""}, + {"name": "ProxyServerAutoDetect", "value": "two","caption": ""}, + ], + "supported_on": ["chrome.win:8-"], + "example_value": "two" + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'reg') + expected_output = self.NEWLINE.join([ + 'Windows Registry Editor Version 5.00', '', + '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome]', + '"EnumPolicy"="two"' + ]) + self.CompareOutputs(output, expected_output) + + def testListPolicy(self): + # Tests a policy group with a single policy of type 'list'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "ListPolicy", + "type": "list", + "caption": "", + "desc": "", + "supported_on": ["chrome.linux:8-"], + "example_value": ["foo", "bar"] + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg') + expected_output = self.NEWLINE.join([ + 'Windows Registry Editor Version 5.00', '', + '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium\\ListPolicy]', + '"1"="foo"', '"2"="bar"' + ]) + + def testStringEnumListPolicy(self): + # Tests a policy group with a single policy of type 'string-enum-list'. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "ListPolicy", + "type": "string-enum-list", + "caption": "", + "desc": "", + "items": [ + {"name": "ProxyServerDisabled", "value": "foo", "caption": ""}, + {"name": "ProxyServerAutoDetect", "value": "bar","caption": ""}, + ], + "supported_on": ["chrome.linux:8-"], + "example_value": ["foo", "bar"] + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg') + expected_output = self.NEWLINE.join([ + 'Windows Registry Editor Version 5.00', '', + '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium\\ListPolicy]', + '"1"="foo"', '"2"="bar"' + ]) + + def testDictionaryPolicy(self): + # Tests a policy group with a single policy of type 'dict'. + example = { + 'bool': True, + 'dict': { + 'a': 1, + 'b': 2, + }, + 'int': 10, + 'list': [1, 2, 3], + 'string': 'abc', + } + policy_json = ''' + { + "policy_definitions": [ + { + "name": "DictionaryPolicy", + "type": "dict", + "caption": "", + "desc": "", + "supported_on": ["chrome.win:8-"], + "example_value": ''' + str(example) + ''' + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg') + expected_output = self.NEWLINE.join([ + 'Windows Registry Editor Version 5.00', '', + '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium]', + '"DictionaryPolicy"="{\\"bool\\": true, ' + '\\"dict\\": {\\"a\\": 1, \\"b\\": 2}, \\"int\\": 10, ' + '\\"list\\": [1, 2, 3], \\"string\\": \\"abc\\"}"' + ]) + self.CompareOutputs(output, expected_output) + + def testExternalPolicy(self): + # Tests a policy group with a single policy of type 'external'. + example = { + 'url': "https://example.com/avatar.jpg", + 'hash': "deadbeef", + } + policy_json = ''' + { + "policy_definitions": [ + { + "name": "ExternalPolicy", + "type": "external", + "caption": "", + "desc": "", + "supported_on": ["chrome.win:8-"], + "example_value": %s + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' % str(example) + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg') + expected_output = self.NEWLINE.join([ + 'Windows Registry Editor Version 5.00', '', + '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium]', + '"ExternalPolicy"="{\\"hash\\": \\"deadbeef\\", ' + '\\"url\\": \\"https://example.com/avatar.jpg\\"}"' + ]) + self.CompareOutputs(output, expected_output) + + def testNonSupportedPolicy(self): + # Tests a policy that is not supported on Windows, so it shouldn't + # be included in the .REG file. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "NonWindowsPolicy", + "type": "list", + "caption": "", + "desc": "", + "supported_on": ["chrome.mac:8-"], + "example_value": ["a"] + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg') + expected_output = self.NEWLINE.join( + ['Windows Registry Editor Version 5.00']) + self.CompareOutputs(output, expected_output) + + def testPolicyGroup(self): + # Tests a policy group that has more than one policies. + policy_json = ''' + { + "policy_definitions": [ + { + "name": "Group1", + "type": "group", + "caption": "", + "desc": "", + "policies": ["Policy1", "Policy2"], + }, + { + "name": "Policy1", + "type": "list", + "caption": "", + "desc": "", + "supported_on": ["chrome.win:8-"], + "example_value": ["a", "b"] + }, + { + "name": "Policy2", + "type": "string", + "caption": "", + "desc": "", + "supported_on": ["chrome.win:8-"], + "example_value": "c" + }, + ], + "policy_atomic_group_definitions": [], + "placeholders": [], + "messages": {}, + }''' + output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg') + expected_output = self.NEWLINE.join([ + 'Windows Registry Editor Version 5.00', '', + '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium]', '"Policy2"="c"', + '', '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium\\Policy1]', + '"1"="a"', '"2"="b"' + ]) + self.CompareOutputs(output, expected_output) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/template_writer.py b/chromium/components/policy/tools/template_writers/writers/template_writer.py new file mode 100755 index 00000000000..075a381aa2d --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/template_writer.py @@ -0,0 +1,468 @@ +#!/usr/bin/env python3 +# 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. + + +class TemplateWriter(object): + '''Abstract base class for writing policy templates in various formats. + The methods of this class will be called by PolicyTemplateGenerator. + ''' + def __init__(self, platforms, config): + '''Initializes a TemplateWriter object. + + Args: + platforms: List of platforms for which this writer can write policies. + config: A dictionary of information required to generate the template. + It contains some key-value pairs, including the following examples: + 'build': 'chrome' or 'chromium' + 'branding': 'Google Chrome' or 'Chromium' + 'mac_bundle_id': The Mac bundle id of Chrome. (Only set when building + for Mac.) + ''' + self.platforms = platforms + self.config = config + + def IsDeprecatedPolicySupported(self, policy): + '''Checks if the given deprecated policy is supported by the writer. + + Args: + policy: The dictionary of the policy. + + Returns: + True if the writer chooses to include the deprecated 'policy' in its + output. + ''' + return False + + def IsFuturePolicySupported(self, policy): + '''Checks if the given future policy is supported by the writer. + + Args: + policy: The dictionary of the policy. + + Returns: + True if the writer chooses to include the unreleased 'policy' in its + output. + ''' + return False + + def IsCloudOnlyPolicySupported(self, policy): + '''Checks if the given cloud only policy is supported by the writer. + + Args: + policy: The dictionary of the policy. + + Returns: + True if the writer chooses to include the cloud only 'policy' in its + output. + ''' + return False + + def IsInternalOnlyPolicySupported(self, policy): + '''Checks if the given internal policy is supported by the writer. + + Args: + policy: The dictionary of the policy. + + Returns: + True if the writer chooses to include the internal only 'policy' in its + output. + ''' + return False + + def IsPolicySupported(self, policy): + '''Checks if the given policy is supported by the writer. + In other words, the set of platforms supported by the writer + has a common subset with the set of platforms that support + the policy. + + Args: + policy: The dictionary of the policy. + + Returns: + True if the writer chooses to include 'policy' in its output. + ''' + if ('deprecated' in policy and policy['deprecated'] is True + and not self.IsDeprecatedPolicySupported(policy)): + return False + + if (self.IsCloudOnlyPolicy(policy) + and not self.IsCloudOnlyPolicySupported(policy)): + return False + + if (self.IsInternalOnlyPolicy(policy) + and not self.IsInternalOnlyPolicySupported(policy)): + return False + + for supported_on in policy['supported_on']: + if not self.IsVersionSupported(policy, supported_on): + continue + if '*' in self.platforms or supported_on['platform'] in self.platforms: + return True + + if self.IsFuturePolicySupported(policy): + if '*' in self.platforms and policy['future_on']: + return True + for future in policy['future_on']: + if future['platform'] in self.platforms: + return True + return False + + def GetPolicyFeature(self, policy, feature_name, value=None): + '''Returns policy feature with |feature_name| if exsits. Otherwise, returns + |value|.''' + return policy.get('features', {}).get(feature_name, value) + + def CanBeRecommended(self, policy): + '''Checks if the given policy can be recommended.''' + return self.GetPolicyFeature(policy, 'can_be_recommended', False) + + def CanBeMandatory(self, policy): + '''Checks if the given policy can be mandatory.''' + return self.GetPolicyFeature(policy, 'can_be_mandatory', True) + + def IsCloudOnlyPolicy(self, policy): + '''Checks if the given policy is cloud only''' + return self.GetPolicyFeature(policy, 'cloud_only', False) + + def IsInternalOnlyPolicy(self, policy): + '''Checks if the given policy is internal only''' + return self.GetPolicyFeature(policy, 'internal_only', False) + + def IsPolicyOrItemSupportedOnPlatform(self, + item, + platform, + product=None, + management=None): + '''Checks if |item| is supported on |product| for |platform|. If + |product| is not specified, only the platform support is checked. + If |management| is specified, also checks for support for Chrome OS + management type. + + Args: + item: The dictionary of the policy or item. + platform: The platform to check; one of + 'win', 'mac', 'linux', 'chrome_os', 'android'. + product: Optional product to check; one of + 'chrome', 'chrome_frame', 'chrome_os', 'webview'. + management: Optional Chrome OS management type to check; one of + 'active_directory', 'google_cloud'. + ''' + if management and not self.IsCrOSManagementSupported(item, management): + return False + + for supported_on in item['supported_on']: + if (platform == supported_on['platform'] + and (not product or product in supported_on['product']) + and self.IsVersionSupported(item, supported_on)): + return True + if self.IsFuturePolicySupported(item): + if (product and { + 'platform': platform, + 'product': product + } in item.get('future_on', [])): + return True + if (not product and filter(lambda f: f['platform'] == platform, + item.get('future_on', []))): + return True + return False + + def IsPolicySupportedOnWindows(self, policy, product=None): + ''' Checks if |policy| is supported on any Windows platform. + + Args: + policy: The dictionary of the policy. + product: Optional product to check; one of + 'chrome', 'chrome_frame', 'chrome_os', 'webview' + ''' + return (self.IsPolicyOrItemSupportedOnPlatform(policy, 'win', product) + or self.IsPolicyOrItemSupportedOnPlatform(policy, 'win7', product)) + + def IsCrOSManagementSupported(self, policy, management): + '''Checks whether |policy| supports the Chrome OS |management| type. + + Args: + policy: The dictionary of the policy. + management: Chrome OS management type to check; one of + 'active_directory', 'google_cloud'. + ''' + # By default, i.e. if supported_chrome_os_management is not set, all + # management types are supported. + return management in policy.get('supported_chrome_os_management', + ['active_directory', 'google_cloud']) + + def IsVersionSupported(self, policy, supported_on): + '''Checks whether the policy is supported on current version''' + major_version = self._GetChromiumMajorVersion() + if not major_version: + return True + + since_version = supported_on.get('since_version', None) + until_version = supported_on.get('until_version', None) + + return ((not since_version or int(since_version) <= major_version) + and (not until_version or int(until_version) >= major_version)) + + def _GetChromiumVersionString(self): + '''Returns the Chromium version string stored in the environment variable + version (if it is set). + + Returns: The Chromium version string or None if it has not been set.''' + + return self.config.get('version', None) + + def _GetChromiumMajorVersion(self): + ''' Returns the major version of Chromium if it exists + in config. + ''' + return self.config.get('major_version', None) + + def _GetPoliciesForWriter(self, group): + '''Filters the list of policies in the passed group that are supported by + the writer. + + Args: + group: The dictionary of the policy group. + + Returns: The list of policies of the policy group that are compatible + with the writer. + ''' + if not 'policies' in group: + return [] + result = [] + for policy in group['policies']: + if self.IsPolicySupported(policy): + result.append(policy) + return result + + def Init(self): + '''Initializes the writer. If the WriteTemplate method is overridden, then + this method must be called as first step of each template generation + process. + ''' + pass + + def WriteTemplate(self, template): + '''Writes the given template definition. + + Args: + template: Template definition to write. + + Returns: + Generated output for the passed template definition. + ''' + self.messages = template['messages'] + self.Init() + template['policy_definitions'] = \ + self.PreprocessPolicies(template['policy_definitions']) + self.BeginTemplate() + self.WritePolicies(template['policy_definitions']) + self.EndTemplate() + + return self.GetTemplateText() + + def PreprocessPolicies(self, policy_list): + '''Preprocesses a list of policies according to a given writer's needs. + Preprocessing steps include sorting policies and stripping unneeded + information such as groups (for writers that ignore them). + Subclasses are encouraged to override this method, overriding + implementations may call one of the provided specialized implementations. + The default behaviour is to use SortPoliciesGroupsFirst(). + + Args: + policy_list: A list containing the policies to sort. + + Returns: + The sorted policy list. + ''' + return self.SortPoliciesGroupsFirst(policy_list) + + def WritePolicies(self, policy_list): + '''Appends the template text corresponding to all the policies into the + internal buffer. + + Args: + policy_list: A list containing the policies to write. + ''' + for policy in policy_list: + if policy['type'] == 'group': + child_policies = list(self._GetPoliciesForWriter(policy)) + child_recommended_policies = list( + filter(self.CanBeRecommended, child_policies)) + if child_policies: + # Only write nonempty groups. + self.BeginPolicyGroup(policy) + for child_policy in child_policies: + # Nesting of groups is currently not supported. + self.WritePolicy(child_policy) + self.EndPolicyGroup() + if child_recommended_policies: + self.BeginRecommendedPolicyGroup(policy) + for child_policy in child_recommended_policies: + self.WriteRecommendedPolicy(child_policy) + self.EndRecommendedPolicyGroup() + elif self.IsPolicySupported(policy): + self.WritePolicy(policy) + if self.CanBeRecommended(policy): + self.WriteRecommendedPolicy(policy) + + def WritePolicy(self, policy): + '''Appends the template text corresponding to a policy into the + internal buffer. + + Args: + policy: The policy as it is found in the JSON file. + ''' + raise NotImplementedError() + + def WriteComment(self, comment): + '''Appends the comment to the internal buffer. + + comment: The comment to be added. + ''' + raise NotImplementedError() + + def WriteRecommendedPolicy(self, policy): + '''Appends the template text corresponding to a recommended policy into the + internal buffer. + + Args: + policy: The recommended policy as it is found in the JSON file. + ''' + # TODO + #raise NotImplementedError() + pass + + def BeginPolicyGroup(self, group): + '''Appends the template text corresponding to the beginning of a + policy group into the internal buffer. + + Args: + group: The policy group as it is found in the JSON file. + ''' + pass + + def EndPolicyGroup(self): + '''Appends the template text corresponding to the end of a + policy group into the internal buffer. + ''' + pass + + def BeginRecommendedPolicyGroup(self, group): + '''Appends the template text corresponding to the beginning of a recommended + policy group into the internal buffer. + + Args: + group: The recommended policy group as it is found in the JSON file. + ''' + pass + + def EndRecommendedPolicyGroup(self): + '''Appends the template text corresponding to the end of a recommended + policy group into the internal buffer. + ''' + pass + + def BeginTemplate(self): + '''Appends the text corresponding to the beginning of the whole + template into the internal buffer. + ''' + raise NotImplementedError() + + def EndTemplate(self): + '''Appends the text corresponding to the end of the whole + template into the internal buffer. + ''' + pass + + def GetTemplateText(self): + '''Gets the content of the internal template buffer. + + Returns: + The generated template from the the internal buffer as a string. + ''' + raise NotImplementedError() + + def SortPoliciesGroupsFirst(self, policy_list): + '''Sorts a list of policies alphabetically. The order is the + following: first groups alphabetically by caption, then other policies + alphabetically by name. The order of policies inside groups is unchanged. + + Args: + policy_list: The list of policies to sort. Sub-lists in groups will not + be sorted. + ''' + policy_list.sort(key=self.GetPolicySortingKeyGroupsFirst) + return policy_list + + def FlattenGroupsAndSortPolicies(self, policy_list, sorting_key=None): + '''Sorts a list of policies according to |sorting_key|, defaulting + to alphabetical sorting if no key is given. If |policy_list| contains + policies with type="group", it is flattened first, i.e. any groups' contents + are inserted into the list as first-class elements and the groups are then + removed. + ''' + new_list = [] + for policy in policy_list: + if policy['type'] == 'group': + for grouped_policy in policy['policies']: + new_list.append(grouped_policy) + else: + new_list.append(policy) + if sorting_key == None: + sorting_key = self.GetPolicySortingKeyName + new_list.sort(key=sorting_key) + return new_list + + def GetPolicySortingKeyName(self, policy): + return policy['name'] + + def GetPolicySortingKeyGroupsFirst(self, policy): + '''Extracts a sorting key from a policy. These keys can be used for + list.sort() methods to sort policies. + See TemplateWriter.SortPolicies for usage. + ''' + is_group = policy['type'] == 'group' + if is_group: + # Groups are sorted by caption. + str_key = policy['caption'] + else: + # Regular policies are sorted by name. + str_key = policy['name'] + # Groups come before regular policies. + return (not is_group, str_key) + + def GetLocalizedMessage(self, msg_id): + '''Returns a localized message for this writer. + + Args: + msg_id: The identifier of the message. + + Returns: + The localized message. + ''' + return self.messages['doc_' + msg_id]['text'] + + def HasExpandedPolicyDescription(self, policy): + '''Returns whether the policy has expanded documentation containing the link + to the documentation with schema and formatting. + ''' + return (policy['type'] in ('dict', 'external') or 'url_schema' in policy + or 'validation_schema' in policy or 'description_schema' in policy) + + def GetExpandedPolicyDescription(self, policy): + '''Returns the expanded description of the policy containing the link to the + documentation with schema and formatting. + ''' + schema_description_link_text = self.GetLocalizedMessage( + 'schema_description_link') + url = None + if 'url_schema' in policy: + url = policy['url_schema'] + if (policy['type'] in ('dict', 'external') or 'validation_schema' in policy + or 'description_schema' in policy): + url = ( + 'https://cloud.google.com/docs/chrome-enterprise/policies/?policy=' + + policy['name']) + return schema_description_link_text.replace('$6', url) if url else '' diff --git a/chromium/components/policy/tools/template_writers/writers/template_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/template_writer_unittest.py new file mode 100755 index 00000000000..62c2e5e3fbc --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/template_writer_unittest.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +# 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. +'''Unit tests for writers.template_writer''' + +import os +import sys +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..')) + +import unittest + +from writers import template_writer + +POLICY_DEFS = [ + { + 'name': 'zp', + 'type': 'string', + 'caption': 'a1', + 'supported_on': [] + }, + { + 'type': + 'group', + 'caption': + 'z_group1_caption', + 'name': + 'group1', + 'policies': [{ + 'name': 'z0', + 'type': 'string', + 'supported_on': [] + }, { + 'name': 'a0', + 'type': 'string', + 'supported_on': [] + }] + }, + { + 'type': 'group', + 'caption': 'b_group2_caption', + 'name': 'group2', + 'policies': [{ + 'name': 'q', + 'type': 'string', + 'supported_on': [] + }], + }, { + 'name': 'ap', + 'type': 'string', + 'caption': 'a2', + 'supported_on': [] + } +] + +GROUP_FIRST_SORTED_POLICY_DEFS = [ + { + 'type': 'group', + 'caption': 'b_group2_caption', + 'name': 'group2', + 'policies': [{ + 'name': 'q', + 'type': 'string', + 'supported_on': [] + }], + }, + { + 'type': + 'group', + 'caption': + 'z_group1_caption', + 'name': + 'group1', + 'policies': [{ + 'name': 'z0', + 'type': 'string', + 'supported_on': [] + }, { + 'name': 'a0', + 'type': 'string', + 'supported_on': [] + }] + }, + { + 'name': 'ap', + 'type': 'string', + 'caption': 'a2', + 'supported_on': [] + }, + { + 'name': 'zp', + 'type': 'string', + 'caption': 'a1', + 'supported_on': [] + }, +] + +IGNORE_GROUPS_SORTED_POLICY_DEFS = [ + { + 'name': 'a0', + 'type': 'string', + 'supported_on': [] + }, + { + 'name': 'ap', + 'type': 'string', + 'caption': 'a2', + 'supported_on': [] + }, + { + 'name': 'q', + 'type': 'string', + 'supported_on': [] + }, + { + 'name': 'z0', + 'type': 'string', + 'supported_on': [] + }, + { + 'name': 'zp', + 'type': 'string', + 'caption': 'a1', + 'supported_on': [] + }, +] + + +class TemplateWriterUnittests(unittest.TestCase): + '''Unit tests for templater_writer.py.''' + + def _IsPolicySupported(self, + platform, + version, + policy, + writer=template_writer.TemplateWriter): + tw = writer([platform], {'major_version': version}) + if platform != '*': + self.assertEqual( + tw.IsPolicySupported(policy), + tw.IsPolicyOrItemSupportedOnPlatform(policy, platform)) + return tw.IsPolicySupported(policy) + + def testSortingGroupsFirst(self): + tw = template_writer.TemplateWriter(None, None) + sorted_list = tw.SortPoliciesGroupsFirst(POLICY_DEFS) + self.assertEqual(sorted_list, GROUP_FIRST_SORTED_POLICY_DEFS) + + def testSortingIgnoreGroups(self): + tw = template_writer.TemplateWriter(None, None) + sorted_list = tw.FlattenGroupsAndSortPolicies(POLICY_DEFS) + self.assertEqual(sorted_list, IGNORE_GROUPS_SORTED_POLICY_DEFS) + + def testPoliciesIsNotSupported(self): + tw = template_writer.TemplateWriter(None, None) + self.assertFalse(tw.IsPolicySupported({'deprecated': True})) + self.assertFalse(tw.IsPolicySupported({'features': {'cloud_only': True}})) + self.assertFalse(tw.IsPolicySupported({'features': { + 'internal_only': True + }})) + + def testFuturePoliciesSupport(self): + class FutureWriter(template_writer.TemplateWriter): + def IsFuturePolicySupported(self, policy): + return True + + expected_request_for_all_platforms = [[False, True, True], + [True, True, True]] + expected_request_for_all_win = [[False, False, True], [True, True, True]] + for i, writer in enumerate([template_writer.TemplateWriter, FutureWriter]): + for j, policy in enumerate([{ + 'supported_on': [], + 'future_on': [{ + 'product': 'chrome', + 'platform': 'win' + }, { + 'product': 'chrome', + 'platform': 'mac' + }] + }, { + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'mac' + }], + 'future_on': [{ + 'product': 'chrome', + 'platform': 'win' + }] + }, { + 'supported_on': [{ + 'product': 'chrome', + 'platform': 'win' + }, { + 'product': 'chrome', + 'platform': 'mac' + }], + 'future_on': [] + }]): + self.assertEqual(expected_request_for_all_platforms[i][j], + self._IsPolicySupported('*', None, policy, writer)) + self.assertEqual( + expected_request_for_all_win[i][j], + self._IsPolicySupported('win', None, policy, writer), + ) + + def testPoliciesIsSupportedOnCertainVersion(self): + platform = 'win' + policy = { + 'supported_on': [{ + 'platform': 'win', + 'since_version': '11', + 'until_version': '12' + }] + } + self.assertFalse(self._IsPolicySupported(platform, 10, policy)) + self.assertTrue(self._IsPolicySupported(platform, 11, policy)) + self.assertTrue(self._IsPolicySupported(platform, 12, policy)) + self.assertFalse(self._IsPolicySupported(platform, 13, policy)) + + policy = { + 'supported_on': [{ + 'platform': 'win', + 'since_version': '11', + 'until_version': '' + }] + } + self.assertFalse(self._IsPolicySupported(platform, 10, policy)) + self.assertTrue(self._IsPolicySupported(platform, 11, policy)) + self.assertTrue(self._IsPolicySupported(platform, 12, policy)) + self.assertTrue(self._IsPolicySupported(platform, 13, policy)) + + def testPoliciesIsSupportedOnMulitplePlatform(self): + policy = { + 'supported_on': [{ + 'platform': 'win', + 'since_version': '12', + 'until_version': '' + }, { + 'platform': 'mac', + 'since_version': '11', + 'until_version': '' + }] + } + self.assertFalse(self._IsPolicySupported('win', 11, policy)) + self.assertTrue(self._IsPolicySupported('mac', 11, policy)) + self.assertTrue(self._IsPolicySupported('*', 11, policy)) + self.assertFalse(self._IsPolicySupported('*', 10, policy)) + + def testHasExpandedPolicyDescriptionForUrlSchema(self): + policy = {'url_schema': 'https://example.com/details', 'type': 'list'} + tw = template_writer.TemplateWriter(None, None) + self.assertTrue(tw.HasExpandedPolicyDescription(policy)) + + def testHasExpandedPolicyDescriptionForJSONPolicies(self): + policy = {'name': 'PolicyName', 'type': 'dict'} + tw = template_writer.TemplateWriter(None, None) + self.assertTrue(tw.HasExpandedPolicyDescription(policy)) + + def testGetExpandedPolicyDescriptionForUrlSchema(self): + policy = {'type': 'integer', 'url_schema': 'https://example.com/details'} + tw = template_writer.TemplateWriter(None, None) + tw.messages = { + 'doc_schema_description_link': { + 'text': '''See $6''' + }, + } + expanded_description = tw.GetExpandedPolicyDescription(policy) + self.assertEqual(expanded_description, 'See https://example.com/details') + + def testGetExpandedPolicyDescriptionForJSONPolicies(self): + policy = {'name': 'PolicyName', 'type': 'dict'} + tw = template_writer.TemplateWriter(None, None) + tw.messages = { + 'doc_schema_description_link': { + 'text': '''See $6''' + }, + } + expanded_description = tw.GetExpandedPolicyDescription(policy) + self.assertEqual( + expanded_description, + 'See https://cloud.google.com/docs/chrome-enterprise/policies/?policy=PolicyName' + ) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/policy/tools/template_writers/writers/writer_unittest_common.py b/chromium/components/policy/tools/template_writers/writers/writer_unittest_common.py new file mode 100755 index 00000000000..d96bb7986d2 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/writer_unittest_common.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# Copyright 2017 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. +'''Common tools for unit-testing writers.''' + +import unittest +import policy_template_generator +import template_formatter +import textwrap +import writer_configuration + + +class WriterUnittestCommon(unittest.TestCase): + '''Common class for unittesting writers.''' + + def GetOutput(self, policy_json, definitions, writer_type): + '''Generates an output of a writer. + + Args: + policy_json: Raw policy JSON string. + definitions: Definitions to create writer configurations. + writer_type: Writer type (e.g. 'admx'), see template_formatter.py. + + Returns: + The string of the template created by the writer. + ''' + + # Evaluate policy_json. For convenience, fix indentation in statements like + # policy_json = ''' + # { + # ... + # }''') + start_idx = 1 if policy_json[0] == '\n' else 0 + policy_data = eval(textwrap.dedent(policy_json[start_idx:])) + + config = writer_configuration.GetConfigurationForBuild(definitions) + policy_generator = \ + policy_template_generator.PolicyTemplateGenerator(config, policy_data) + writer = template_formatter.GetWriter(writer_type, config) + return policy_generator.GetTemplateText(writer) diff --git a/chromium/components/policy/tools/template_writers/writers/xml_formatted_writer.py b/chromium/components/policy/tools/template_writers/writers/xml_formatted_writer.py new file mode 100755 index 00000000000..a8815370472 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/xml_formatted_writer.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# 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. + +from writers import template_writer + + +class XMLFormattedWriter(template_writer.TemplateWriter): + '''Helper class for generating XML-based templates. + ''' + + def AddElement(self, parent, name, attrs=None, text=None): + ''' + Adds a new XML Element as a child to an existing element or the Document. + + Args: + parent: An XML element or the document, where the new element will be + added. + name: The name of the new element. + attrs: A dictionary of the attributes' names and values for the new + element. + text: Text content for the new element. + + Returns: + The created new element. + ''' + if attrs == None: + attrs = {} + + doc = parent.ownerDocument + element = doc.createElement(name) + for key, value in sorted(attrs.items()): + element.setAttribute(key, value) + if text: + element.appendChild(doc.createTextNode(text)) + parent.appendChild(element) + return element + + def AddText(self, parent, text): + '''Adds text to a parent node. + ''' + doc = parent.ownerDocument + parent.appendChild(doc.createTextNode(text)) + + def AddAttribute(self, parent, name, value): + '''Adds a new attribute to the parent Element. If an attribute with the + given name already exists then it will be replaced. + ''' + doc = parent.ownerDocument + attribute = doc.createAttribute(name) + attribute.value = value + parent.setAttributeNode(attribute) + + def AddComment(self, parent, comment): + '''Adds a comment node.''' + parent.appendChild(parent.ownerDocument.createComment(comment)) + + def ToPrettyXml(self, doc, **kwargs): + # return doc.toprettyxml(indent=' ') + # The above pretty-printer does not print the doctype and adds spaces + # around texts, e.g.: + # <string> + # value of the string + # </string> + # This is problematic both for the OSX Workgroup Manager (plist files) and + # the Windows Group Policy Editor (admx files). What they need instead: + # <string>value of string</string> + # So we use a hacky pretty printer here. It assumes that there are no + # mixed-content nodes. + # Get all the XML content in a one-line string. + xml = doc.toxml(**kwargs) + # Determine where the line breaks will be. (They will only be between tags.) + lines = xml[1:len(xml) - 1].split('><') + indent = '' + res = '' + # Determine indent for each line. + for i, line in enumerate(lines): + if line[0] == '/': + # If the current line starts with a closing tag, decrease indent before + # printing. + indent = indent[2:] + lines[i] = indent + '<' + line + '>' + if (line[0] not in ['/', '?', '!'] and '</' not in line and + line[len(line) - 1] != '/'): + # If the current line starts with an opening tag and does not conatin a + # closing tag, increase indent after the line is printed. + indent += ' ' + # Reconstruct XML text from the lines. + return '\n'.join(lines) diff --git a/chromium/components/policy/tools/template_writers/writers/xml_writer_base_unittest.py b/chromium/components/policy/tools/template_writers/writers/xml_writer_base_unittest.py new file mode 100755 index 00000000000..40c2ff52bb5 --- /dev/null +++ b/chromium/components/policy/tools/template_writers/writers/xml_writer_base_unittest.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# 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. +"""Unittests for writers.admx_writer.""" + +import re +import unittest + + +class XmlWriterBaseTest(unittest.TestCase): + '''Base class for XML writer unit-tests. + ''' + + def GetXMLOfChildren(self, parent): + '''Returns the XML of all child nodes of the given parent node. + Args: + parent: The XML of the children of this node will be returned. + + Return: XML of the chrildren of the parent node. + ''' + raw_pretty_xml = ''.join( + child.toprettyxml(indent=' ') for child in parent.childNodes) + # Python 2.6.5 which is present in Lucid has bug in its pretty print + # function which produces new lines around string literals. This has been + # fixed in Precise which has Python 2.7.3 but we have to keep compatibility + # with both for now. + text_re = re.compile('>\n\s+([^<>\s].*?)\n\s*</', re.DOTALL) + return text_re.sub('>\g<1></', raw_pretty_xml) + + def AssertXMLEquals(self, output, expected_output): + '''Asserts if the passed XML arguements are equal. + Args: + output: Actual XML text. + expected_output: Expected XML text. + ''' + self.assertEquals(output.strip(), expected_output.strip()) |