blob: 7784a88dbb0658e208d534239503ed90a925600d [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import codecs
import unittest
from unittest.mock import patch, mock_open, call
from typing import NamedTuple
import generate_policy_source
import generate_policy_source_test_data as test_data
from generate_policy_source import PolicyDetails
class PolicyData(NamedTuple):
policy_id: int
chunk_number: int
field_number: int
class PolicyGenerationTest(unittest.TestCase):
TEMPLATES_JSON = {
"risk_tag_definitions": [{
"name": "full-admin-access",
"description": "full-admin-access-desc",
"user-description": "full-admin-access-user-desc"
}],
"policy_definitions": [{
"name": "ExampleStringPolicy",
"type": "string",
"schema": {
"type": "string"
},
"supported_on": ["chrome_os:1-", "chrome.*:1-", "android:1-"],
"id": 1,
"tags": [],
"caption": "ExampleStringPolicy caption",
"desc": "ExampleStringPolicy desc"
}, {
"name": "ExampleBoolPolicy",
"type": "main",
"schema": {
"type": "boolean"
},
"supported_on": ["chrome_os:1-", "chrome.*:1-", "android:1-"],
"id": 2,
"tags": [],
"caption": "ExampleBoolPolicy caption",
"desc": "ExampleBoolPolicy desc",
}, {
"name":
"ExampleBoolMergeMetapolicy",
"type":
"main",
"schema": {
"type": "boolean"
},
"supported_on":
["chrome_os:1-", "chrome.*:1-", "android:1-", "ios:1-", "fuchsia:1-"],
"features": {
"metapolicy_type": "merge",
},
"id":
3,
"tags": [],
"caption":
"ExampleBoolMergeMetapolicy caption",
"desc":
"ExampleBoolMergeMetapolicy desc",
}, {
"name":
"ExampleBoolPrecedenceMetapolicy",
"type":
"main",
"schema": {
"type": "boolean"
},
"supported_on":
["chrome_os:1-", "chrome.*:1-", "android:1-", "ios:1-"],
"features": {
"metapolicy_type": "precedence",
},
"id":
4,
"tags": [],
"caption":
"ExampleBoolPrecedenceMetapolicy caption",
"desc":
"ExampleBoolPrecedenceMetapolicy desc",
}, {
"name":
"CloudOnlyPolicy",
"type":
"main",
"schema": {
"type": "boolean"
},
"features": {
"cloud_only": True,
},
"supported_on": ["chrome_os:1-", "android:1-", "chrome.*:1-"],
"id":
5,
"tags": [],
"caption":
"CloudOnlyPolicy caption",
"desc":
"CloudOnlyPolicy desc",
}, {
"name":
"CloudManagementEnrollmentToken",
"type":
"string",
"schema": {
"type": "string"
},
"supported_on": ["chrome_os:1-", "android:1-", "chrome.*:1-"],
"id":
6,
"tags": [],
"caption":
"CloudManagementEnrollmentToken caption",
"desc":
"CloudManagementEnrollmentToken desc"
}, {
"name": "DeprecatedNotGenerated",
"type": "string",
"schema": {
"type": "string"
},
"supported_on": ["chrome_os:1-92"],
"id": 8,
"tags": [],
"caption": "DeprecatedNotGenerated caption",
"desc": "DeprecatedNotGenerated desc"
}, {
"name": "UnsupportedPolicy",
"type": "string",
"schema": {
"type": "string"
},
"supported_on": [],
"id": 9,
"tags": [],
"caption": "UnsupportedPolicy caption",
"desc": "UnsupportedPolicy desc"
}, {
"name": "ChunkZeroLastFieldBooleanPolicy",
"type": "main",
"schema": {
"type": "boolean"
},
"supported_on": ["chrome_os:1-", "chrome.*:1-", "android:1-"],
"id": 1040,
"tags": [],
"caption": "ChunkZeroLastFieldBooleanPolicy caption",
"desc": "ChunkZeroLastFieldBooleanPolicy desc.",
}, {
"name": "ChunkOneFirstFieldBooleanPolicy",
"type": "main",
"schema": {
"type": "boolean"
},
"supported_on": ["chrome_os:1-", "chrome.*:1-", "android:1-"],
"id": 1041,
"tags": [],
"caption": "ChunkOneFirstFieldBooleanPolicy caption",
"desc": "ChunkOneFirstFieldBooleanPolicy desc.",
}, {
"name": "ChunkOneLastFieldBooleanPolicy",
"type": "main",
"schema": {
"type": "boolean"
},
"supported_on": ["chrome_os:1-", "chrome.*:1-", "android:1-"],
"id": 1840,
"tags": [],
"caption": "ChunkOneLastFieldBooleanPolicy caption",
"desc": "ChunkOneLastFieldBooleanPolicy desc.",
}, {
"name": "ChunkTwoFirstFieldStringPolicy",
"type": "string",
"schema": {
"type": "string"
},
"supported_on": ["chrome_os:1-", "chrome.*:1-", "android:1-"],
"id": 1841,
"tags": [],
"caption": "ChunkTwoFirstFieldStringPolicy caption",
"desc": "ChunkTwoFirstFieldStringPolicy desc"
}, {
"name": "ChunkTwoLastFieldStringPolicy",
"type": "string",
"schema": {
"type": "string"
},
"supported_on": ["chrome_os:1-", "chrome.*:1-", "android:1-"],
"id": 2640,
"tags": [],
"caption": "ChunkTwoLastFieldStringPolicy caption",
"desc": "ChunkTwoLastFieldStringPolicy desc"
}, {
"name": "SensitivePolicyForMultiplePlatforms",
"type": "main",
"schema": { "type": "boolean" },
"sensitive": True,
"supported_on":
["chrome_os:1-", "chrome.*:1-", "android:1-"],
"id": 2643,
"tags": [],
"caption": "SensitivePolicyForMultiplePlatforms caption",
"desc": "SensitivePolicyForMultiplePlatforms desc"
}, {
"name": "SensitivePolicyForChromeOSOnly",
"type": "main",
"schema": { "type": "boolean" },
"sensitive": True,
"supported_on": ["chrome_os:1-"],
"id": 2644,
"tags": [],
"caption": "SensitivePolicyForChromeOSOnly caption",
"desc": "SensitivePolicyForChromeOSOnly desc"
}, {
"name": "SensitivePolicyForUnsupportedPlatform",
"type": "main",
"schema": { "type": "boolean" },
"supported_on": ["chrome.win:61-"],
"sensitive": True,
"id": 2645,
"tags": [],
"caption": "SensitivePolicyForUnsupportedPlatform caption",
"desc": "It should neither be generated nor listed as sensitive."
}, {
"name": "SensitivePolicyForChromeOSFuture",
"type": "main",
"schema": { "type": "boolean" },
"future_on": ["chrome_os"],
"sensitive": True,
"id": 2646,
"tags": [],
"caption": "SensitivePolicyForChromeOSFuture caption",
"desc": "SensitivePolicyForChromeOSFuture desc"
}, {
"name": "SensitivePolicyForChromeOSDeprecated",
"type": "main",
"schema": { "type": "boolean" },
"supported_on": ["chrome_os:1-"],
"deprecated": True,
"sensitive": True,
"id": 2647,
"tags": [],
"caption": "SensitivePolicyForChromeOSDeprecated caption",
"desc": "SensitivePolicyForChromeOSDeprecated desc"
}],
"policy_atomic_group_definitions": []
}
def setUp(self):
self.maxDiff = None # See the full diff in the test output.
self.chrome_major_version = 94
self.target_platform = 'chrome_os'
self.all_target_platforms = ['win', 'mac', 'linux', 'chromeos', 'fuchsia']
self.risk_tags = generate_policy_source.RiskTags(self.TEMPLATES_JSON)
self.policies = [
generate_policy_source.PolicyDetails(policy, self.chrome_major_version,
self.target_platform,
self.risk_tags.GetValidTags())
for policy in self.TEMPLATES_JSON['policy_definitions']
]
self.risk_tags.ComputeMaxTags(self.policies)
policy_details_set = list(map((lambda x: x.name), self.policies))
policies_already_in_group = set()
self.policy_atomic_groups = [
generate_policy_source.PolicyAtomicGroup(group, policy_details_set,
policies_already_in_group)
for group in self.TEMPLATES_JSON['policy_atomic_group_definitions']
]
def testDefaultValueGeneration(self):
"""Tests generation of default policy values."""
# Bools
stmts, expr = generate_policy_source._GenerateDefaultValue(True)
self.assertListEqual([], stmts)
self.assertEqual('base::Value(true)', expr)
stmts, expr = generate_policy_source._GenerateDefaultValue(False)
self.assertListEqual([], stmts)
self.assertEqual('base::Value(false)', expr)
# Ints
stmts, expr = generate_policy_source._GenerateDefaultValue(33)
self.assertListEqual([], stmts)
self.assertEqual('base::Value(33)', expr)
# Strings
stmts, expr = generate_policy_source._GenerateDefaultValue('foo')
self.assertListEqual([], stmts)
self.assertEqual('base::Value("foo")', expr)
# Empty list
stmts, expr = generate_policy_source._GenerateDefaultValue([])
self.assertListEqual(['base::Value::List default_value;'], stmts)
self.assertEqual('base::Value(std::move(default_value))', expr)
# List with values
stmts, expr = generate_policy_source._GenerateDefaultValue([1, '2'])
self.assertListEqual([
'base::Value::List default_value;',
'default_value.Append(base::Value(1));',
'default_value.Append(base::Value("2"));'
], stmts)
self.assertEqual('base::Value(std::move(default_value))', expr)
# Recursive lists are not supported.
stmts, expr = generate_policy_source._GenerateDefaultValue([1, []])
self.assertListEqual([], stmts)
self.assertIsNone(expr)
# Arbitary types are not supported.
stmts, expr = generate_policy_source._GenerateDefaultValue(object())
self.assertListEqual([], stmts)
self.assertIsNone(expr)
def _assertCallsEqual(self, expected_output, call_args_list):
# Convert mocked write calls into actual content that would be written
# to the file. Elements of call_args_list are call objects, which are
# two-tuples of (positional args, keyword args). With call[0] we first
# fetch the positional args, which are an n-tuple, and with call[0][0]
# we get the first positional argument, which is the string that is
# written into the file.
actual_output = ''.join(call[0][0] for call in call_args_list)
# Strip whitespace from the beginning and end of expected and actual
# output and verify that they are equal.
self.assertEqual(expected_output.strip(), actual_output.strip())
def testWriteCloudPolicyProtobuf(self):
output_path = 'mock_cloud_policy_proto'
with patch('codecs.open', mock_open()) as mocked_file:
with codecs.open(output_path, 'w', encoding='utf-8') as f:
generate_policy_source._WriteCloudPolicyProtobuf(
self.policies,
self.policy_atomic_groups,
self.target_platform,
f,
self.risk_tags,
chunking=True)
mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
self._assertCallsEqual(test_data.EXPECTED_CLOUD_POLICY_PROTOBUF,
mocked_file().write.call_args_list)
def testWriteCloudPolicyProtobufNoChunking(self):
output_path = 'mock_cloud_policy_proto'
with patch('codecs.open', mock_open()) as mocked_file:
with codecs.open(output_path, 'w', encoding='utf-8') as f:
generate_policy_source._WriteCloudPolicyProtobuf(
self.policies,
self.policy_atomic_groups,
self.target_platform,
f,
self.risk_tags,
chunking=False)
mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
self._assertCallsEqual(test_data.EXPECTED_CLOUD_POLICY_PROTOBUF_NO_CHUNKING,
mocked_file().write.call_args_list)
def testWriteChromeSettingsProtobuf(self):
output_path = 'mock_chrome_settings_proto'
with patch('codecs.open', mock_open()) as mocked_file:
with codecs.open(output_path, 'w', encoding='utf-8') as f:
generate_policy_source._WriteChromeSettingsProtobuf(
self.policies,
self.policy_atomic_groups,
self.target_platform,
f,
self.risk_tags,
chunking=True)
mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
self._assertCallsEqual(test_data.EXPECTED_CHROME_SETTINGS_PROTOBUF,
mocked_file().write.call_args_list)
def testWriteChromeSettingsProtobufNoChunking(self):
output_path = 'mock_chrome_settings_proto'
with patch('codecs.open', mock_open()) as mocked_file:
with codecs.open(output_path, 'w', encoding='utf-8') as f:
generate_policy_source._WriteChromeSettingsProtobuf(
self.policies,
self.policy_atomic_groups,
self.target_platform,
f,
self.risk_tags,
chunking=False)
mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
self._assertCallsEqual(
test_data.EXPECTED_CHROME_SETTINGS_PROTOBUF_NO_CHUNKING,
mocked_file().write.call_args_list)
def testWritePolicyProto(self):
output_path = 'mock_write_policy_proto'
with patch('codecs.open', mock_open()) as mocked_file:
with codecs.open(output_path, 'w', encoding='utf-8') as f:
generate_policy_source._WritePolicyProto(f, self.policies[0])
mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
self._assertCallsEqual(test_data.EXPECTED_POLICY_PROTO,
mocked_file().write.call_args_list)
def testGetMetapoliciesOfType(self):
merge_metapolicies = generate_policy_source._GetMetapoliciesOfType(
self.policies, "merge")
self.assertEqual(1, len(merge_metapolicies))
self.assertEqual("ExampleBoolMergeMetapolicy", merge_metapolicies[0].name)
precedence_metapolicies = generate_policy_source._GetMetapoliciesOfType(
self.policies, "precedence")
self.assertEqual(1, len(precedence_metapolicies))
self.assertEqual("ExampleBoolPrecedenceMetapolicy",
precedence_metapolicies[0].name)
invalid_metapolicies = generate_policy_source._GetMetapoliciesOfType(
self.policies, "invalid")
self.assertEqual(0, len(invalid_metapolicies))
def testGetSensitivePolicies(self):
sensitive_policies = sorted([
p.name for p in self.policies if p.is_sensitive and p.is_supported
])
self.assertListEqual([
"SensitivePolicyForChromeOSDeprecated",
"SensitivePolicyForChromeOSFuture",
"SensitivePolicyForChromeOSOnly",
"SensitivePolicyForMultiplePlatforms",
], sensitive_policies)
def testWritePolicyConstantHeader(self):
output_path = 'mock_policy_constants_h'
for target_platform in self.all_target_platforms:
with patch('codecs.open', mock_open()) as mocked_file:
with codecs.open(output_path, 'w', encoding='utf-8') as f:
generate_policy_source._WritePolicyConstantHeader(
self.policies,
self.policy_atomic_groups,
target_platform,
f,
self.risk_tags,
chunking=True,
)
with self.subTest(target_platform=target_platform):
mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
if target_platform == 'win':
windows_only_part = test_data.POLICY_CONSTANTS_HEADER_WIN_ONLY_PART
else:
windows_only_part = ''
expected_formatted = test_data.EXPECTED_POLICY_CONSTANTS_HEADER % {
"windows_only_part": windows_only_part,
}
self._assertCallsEqual(expected_formatted,
mocked_file().write.call_args_list)
def testWritePolicyConstantSource(self):
self.maxDiff = None
output_path = 'mock_policy_constants_cc'
for target_platform in self.all_target_platforms:
with patch('codecs.open', mock_open()) as mocked_file:
with codecs.open(output_path, 'w', encoding='utf-8') as f:
generate_policy_source._WritePolicyConstantSource(
self.policies,
self.policy_atomic_groups,
target_platform,
f,
self.risk_tags,
chunking=True,
)
with self.subTest(target_platform=target_platform):
mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
if target_platform == 'win':
windows_only_part = test_data.POLICY_CONSTANTS_SOURCE_WIN_ONLY_PART
else:
windows_only_part = ''
expected_formatted = test_data.EXPECTED_POLICY_CONSTANTS_SOURCE % {
"windows_only_part": windows_only_part,
}
self._assertCallsEqual(expected_formatted,
mocked_file().write.call_args_list)
def testWriteAppRestrictions(self):
# Create Android-specific policies for testing Android app restrictions.
# This ensures we only test with policies that actually support Android.
android_target_platform = 'android'
android_policies = [
generate_policy_source.PolicyDetails(policy, self.chrome_major_version,
android_target_platform,
self.risk_tags.GetValidTags())
for policy in self.TEMPLATES_JSON['policy_definitions']
]
output_path = 'app_restrictions_xml'
with patch('codecs.open', mock_open()) as mocked_file:
with codecs.open(output_path, 'w', encoding='utf-8') as f:
generate_policy_source._WriteAppRestrictions(
android_policies,
self.policy_atomic_groups,
android_target_platform,
f,
self.risk_tags,
chunking=True,
)
mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
self._assertCallsEqual(test_data.EXPECTED_APP_RESTRICTIONS_XML,
mocked_file().write.call_args_list)
def testChunkNumberAndFieldNumber(self):
test_data = [
# Last top-level policy
PolicyData(policy_id=1040, chunk_number=0, field_number=1042),
# First policy in chunk 1
PolicyData(policy_id=1041, chunk_number=1, field_number=1),
# Last policy in chunk 1
PolicyData(policy_id=1840, chunk_number=1, field_number=800),
# First policy in chunk 2
PolicyData(policy_id=1841, chunk_number=2, field_number=1),
# Last policy in chunk 2
PolicyData(policy_id=2640, chunk_number=2, field_number=800),
# First policy in chunk 3
PolicyData(policy_id=2641, chunk_number=3, field_number=1),
# Last policy in chunk 3
PolicyData(policy_id=3440, chunk_number=3, field_number=800),
# First policy in chunk 501
PolicyData(policy_id=401041, chunk_number=501, field_number=1),
# Last policy in chunk 501
PolicyData(policy_id=401840, chunk_number=501, field_number=800),
# First policy in chunk 502
PolicyData(policy_id=401841, chunk_number=502, field_number=1),
# Last policy in chunk 502
PolicyData(policy_id=402640, chunk_number=502, field_number=800),
# First policy in chunk 503
PolicyData(policy_id=402641, chunk_number=503, field_number=1),
# Last policy in chunk 503
PolicyData(policy_id=403440, chunk_number=503, field_number=800),
]
for policy_data in test_data:
# With chunking:
self.assertEqual(
generate_policy_source._ChunkNumber(policy_data.policy_id,
chunking=True),
policy_data.chunk_number)
self.assertEqual(
generate_policy_source._FieldNumber(policy_data.policy_id,
policy_data.chunk_number),
policy_data.field_number)
# Without chunking:
self.assertEqual(
generate_policy_source._ChunkNumber(policy_data.policy_id,
chunking=False), 0)
self.assertEqual(
generate_policy_source._FieldNumber(policy_data.policy_id, 0),
policy_data.policy_id + 2)
if __name__ == '__main__':
unittest.main()