generate_policy_source_test.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. #!/usr/bin/env python3
  2. # Copyright 2016 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. import codecs
  6. import unittest
  7. from unittest.mock import patch, mock_open, call
  8. from typing import NamedTuple
  9. import generate_policy_source
  10. import generate_policy_source_test_data as test_data
  11. from generate_policy_source import PolicyDetails
  12. class PolicyData(NamedTuple):
  13. policy_id: int
  14. chunk_number: int
  15. field_number: int
  16. class PolicyGenerationTest(unittest.TestCase):
  17. TEMPLATES_JSON = {
  18. "risk_tag_definitions": [{
  19. "name": "full-admin-access",
  20. "description": "full-admin-access-desc",
  21. "user-description": "full-admin-access-user-desc"
  22. }],
  23. "policy_definitions": [{
  24. "name": "ExampleStringPolicy",
  25. "type": "string",
  26. "schema": {
  27. "type": "string"
  28. },
  29. "supported_on": ["chrome_os:1-", "chrome.*:1-"],
  30. "id": 1,
  31. "tags": [],
  32. "caption": "ExampleStringPolicy caption",
  33. "desc": "ExampleStringPolicy desc"
  34. }, {
  35. "name": "ExampleBoolPolicy",
  36. "type": "main",
  37. "schema": {
  38. "type": "boolean"
  39. },
  40. "supported_on": ["chrome_os:1-", "chrome.*:1-"],
  41. "id": 2,
  42. "tags": [],
  43. "caption": "ExampleBoolPolicy caption",
  44. "desc": "ExampleBoolPolicy desc",
  45. }, {
  46. "name":
  47. "ExampleBoolMergeMetapolicy",
  48. "type":
  49. "main",
  50. "schema": {
  51. "type": "boolean"
  52. },
  53. "supported_on":
  54. ["chrome_os:1-", "chrome.*:1-", "android:1-", "ios:1-", "fuchsia:1-"],
  55. "features": {
  56. "metapolicy_type": "merge",
  57. },
  58. "id":
  59. 3,
  60. "tags": [],
  61. "caption":
  62. "ExampleBoolMergeMetapolicy caption",
  63. "desc":
  64. "ExampleBoolMergeMetapolicy desc",
  65. }, {
  66. "name":
  67. "ExampleBoolPrecedenceMetapolicy",
  68. "type":
  69. "main",
  70. "schema": {
  71. "type": "boolean"
  72. },
  73. "supported_on":
  74. ["chrome_os:1-", "chrome.*:1-", "android:1-", "ios:1-"],
  75. "features": {
  76. "metapolicy_type": "precedence",
  77. },
  78. "id":
  79. 4,
  80. "tags": [],
  81. "caption":
  82. "ExampleBoolPrecedenceMetapolicy caption",
  83. "desc":
  84. "ExampleBoolPrecedenceMetapolicy desc",
  85. }, {
  86. "name":
  87. "CloudOnlyPolicy",
  88. "type":
  89. "main",
  90. "schema": {
  91. "type": "boolean"
  92. },
  93. "features": {
  94. "cloud_only": True,
  95. },
  96. "supported_on": ["chrome_os:1-", "android:1-", "chrome.*:1-"],
  97. "id":
  98. 5,
  99. "tags": [],
  100. "caption":
  101. "CloudOnlyPolicy caption",
  102. "desc":
  103. "CloudOnlyPolicy desc",
  104. }, {
  105. "name":
  106. "CloudManagementEnrollmentToken",
  107. "type":
  108. "string",
  109. "schema": {
  110. "type": "string"
  111. },
  112. "supported_on": ["chrome_os:1-", "android:1-", "chrome.*:1-"],
  113. "id":
  114. 6,
  115. "tags": [],
  116. "caption":
  117. "CloudManagementEnrollmentToken caption",
  118. "desc":
  119. "CloudManagementEnrollmentToken desc"
  120. }, {
  121. "name":
  122. "DeprecatedButGenerated",
  123. "type":
  124. "string",
  125. "schema": {
  126. "type": "string"
  127. },
  128. "supported_on": ["chrome_os:1-93", "android:1-93", "chrome.*:1-93"],
  129. "id":
  130. 7,
  131. "tags": [],
  132. "caption":
  133. "DeprecatedButGenerated caption",
  134. "desc":
  135. "DeprecatedButGenerated desc"
  136. }, {
  137. "name": "DeprecatedNotGenerated",
  138. "type": "string",
  139. "schema": {
  140. "type": "string"
  141. },
  142. "supported_on": ["chrome_os:1-92"],
  143. "id": 8,
  144. "tags": [],
  145. "caption": "DeprecatedNotGenerated caption",
  146. "desc": "DeprecatedNotGenerated desc"
  147. }, {
  148. "name": "UnsupportedPolicy",
  149. "type": "string",
  150. "schema": {
  151. "type": "string"
  152. },
  153. "supported_on": [],
  154. "id": 9,
  155. "tags": [],
  156. "caption": "UnsupportedPolicy caption",
  157. "desc": "UnsupportedPolicy desc"
  158. }, {
  159. "name": "ChunkZeroLastFieldBooleanPolicy",
  160. "type": "main",
  161. "schema": {
  162. "type": "boolean"
  163. },
  164. "supported_on": ["chrome_os:1-", "chrome.*:1-"],
  165. "id": 1015,
  166. "tags": [],
  167. "caption": "ChunkZeroLastFieldBooleanPolicy caption",
  168. "desc": "ChunkZeroLastFieldBooleanPolicy desc.",
  169. }, {
  170. "name": "ChunkOneFirstFieldBooleanPolicy",
  171. "type": "main",
  172. "schema": {
  173. "type": "boolean"
  174. },
  175. "supported_on": ["chrome_os:1-", "chrome.*:1-"],
  176. "id": 1016,
  177. "tags": [],
  178. "caption": "ChunkOneFirstFieldBooleanPolicy caption",
  179. "desc": "ChunkOneFirstFieldBooleanPolicy desc.",
  180. }, {
  181. "name": "ChunkOneLastFieldBooleanPolicy",
  182. "type": "main",
  183. "schema": {
  184. "type": "boolean"
  185. },
  186. "supported_on": ["chrome_os:1-", "chrome.*:1-"],
  187. "id": 1815,
  188. "tags": [],
  189. "caption": "ChunkOneLastFieldBooleanPolicy caption",
  190. "desc": "ChunkOneLastFieldBooleanPolicy desc.",
  191. }, {
  192. "name": "ChunkTwoFirstFieldStringPolicy",
  193. "type": "string",
  194. "schema": {
  195. "type": "string"
  196. },
  197. "supported_on": ["chrome_os:1-", "chrome.*:1-"],
  198. "id": 1816,
  199. "tags": [],
  200. "caption": "ChunkTwoFirstFieldStringPolicy caption",
  201. "desc": "ChunkTwoFirstFieldStringPolicy desc"
  202. }, {
  203. "name": "ChunkTwoLastFieldStringPolicy",
  204. "type": "string",
  205. "schema": {
  206. "type": "string"
  207. },
  208. "supported_on": ["chrome_os:1-", "chrome.*:1-"],
  209. "id": 2615,
  210. "tags": [],
  211. "caption": "ChunkTwoLastFieldStringPolicy caption",
  212. "desc": "ChunkTwoLastFieldStringPolicy desc"
  213. }],
  214. "policy_atomic_group_definitions": []
  215. }
  216. def setUp(self):
  217. self.chrome_major_version = 94
  218. self.target_platform = 'chrome_os'
  219. self.deprecation_milestone_buffer = 1
  220. self.all_target_platforms = ['win', 'mac', 'linux', 'chromeos', 'fuchsia']
  221. self.risk_tags = generate_policy_source.RiskTags(self.TEMPLATES_JSON)
  222. self.policies = [
  223. generate_policy_source.PolicyDetails(policy, self.chrome_major_version,
  224. self.deprecation_milestone_buffer,
  225. self.target_platform,
  226. self.risk_tags.GetValidTags())
  227. for policy in self.TEMPLATES_JSON['policy_definitions']
  228. ]
  229. self.risk_tags.ComputeMaxTags(self.policies)
  230. policy_details_set = list(map((lambda x: x.name), self.policies))
  231. policies_already_in_group = set()
  232. self.policy_atomic_groups = [
  233. generate_policy_source.PolicyAtomicGroup(group, policy_details_set,
  234. policies_already_in_group)
  235. for group in self.TEMPLATES_JSON['policy_atomic_group_definitions']
  236. ]
  237. def testDefaultValueGeneration(self):
  238. """Tests generation of default policy values."""
  239. # Bools
  240. stmts, expr = generate_policy_source._GenerateDefaultValue(True)
  241. self.assertListEqual([], stmts)
  242. self.assertEqual('base::Value(true)', expr)
  243. stmts, expr = generate_policy_source._GenerateDefaultValue(False)
  244. self.assertListEqual([], stmts)
  245. self.assertEqual('base::Value(false)', expr)
  246. # Ints
  247. stmts, expr = generate_policy_source._GenerateDefaultValue(33)
  248. self.assertListEqual([], stmts)
  249. self.assertEqual('base::Value(33)', expr)
  250. # Strings
  251. stmts, expr = generate_policy_source._GenerateDefaultValue('foo')
  252. self.assertListEqual([], stmts)
  253. self.assertEqual('base::Value("foo")', expr)
  254. # Empty list
  255. stmts, expr = generate_policy_source._GenerateDefaultValue([])
  256. self.assertListEqual(
  257. ['base::Value default_value(base::Value::Type::LIST);'], stmts)
  258. self.assertEqual('std::move(default_value)', expr)
  259. # List with values
  260. stmts, expr = generate_policy_source._GenerateDefaultValue([1, '2'])
  261. self.assertListEqual([
  262. 'base::Value default_value(base::Value::Type::LIST);',
  263. 'default_value.Append(base::Value(1));',
  264. 'default_value.Append(base::Value("2"));'
  265. ], stmts)
  266. self.assertEqual('std::move(default_value)', expr)
  267. # Recursive lists are not supported.
  268. stmts, expr = generate_policy_source._GenerateDefaultValue([1, []])
  269. self.assertListEqual([], stmts)
  270. self.assertIsNone(expr)
  271. # Arbitary types are not supported.
  272. stmts, expr = generate_policy_source._GenerateDefaultValue(object())
  273. self.assertListEqual([], stmts)
  274. self.assertIsNone(expr)
  275. def _assertCallsEqual(self, expected_output, call_args_list):
  276. # Convert mocked write calls into actual content that would be written
  277. # to the file. Elements of call_args_list are call objects, which are
  278. # two-tuples of (positional args, keyword args). With call[0] we first
  279. # fetch the positional args, which are an n-tuple, and with call[0][0]
  280. # we get the first positional argument, which is the string that is
  281. # written into the file.
  282. actual_output = ''.join(call[0][0] for call in call_args_list)
  283. # Strip whitespace from the beginning and end of expected and actual
  284. # output and verify that they are equal.
  285. self.assertEqual(expected_output.strip(), actual_output.strip())
  286. def testWriteCloudPolicyProtobuf(self):
  287. is_full_runtime_values = [False, True]
  288. output_path = 'mock_cloud_policy_proto'
  289. for is_full_runtime in is_full_runtime_values:
  290. with patch('codecs.open', mock_open()) as mocked_file:
  291. with codecs.open(output_path, 'w', encoding='utf-8') as f:
  292. generate_policy_source._WriteCloudPolicyProtobuf(
  293. self.policies,
  294. self.policy_atomic_groups,
  295. self.target_platform,
  296. f,
  297. self.risk_tags,
  298. is_full_runtime=is_full_runtime)
  299. full_runtime_comment = '//' if is_full_runtime else ''
  300. full_runtime_suffix = '_full_runtime' if is_full_runtime else ''
  301. with self.subTest(is_full_runtime=is_full_runtime):
  302. mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
  303. expected_formatted = test_data.EXPECTED_CLOUD_POLICY_PROTOBUF % {
  304. "full_runtime_comment": full_runtime_comment,
  305. "full_runtime_suffix": full_runtime_suffix,
  306. }
  307. self._assertCallsEqual(expected_formatted,
  308. mocked_file().write.call_args_list)
  309. def testWriteChromeSettingsProtobuf(self):
  310. is_full_runtime_values = [False, True]
  311. output_path = 'mock_chrome_settings_proto'
  312. for is_full_runtime in is_full_runtime_values:
  313. with patch('codecs.open', mock_open()) as mocked_file:
  314. with codecs.open(output_path, 'w', encoding='utf-8') as f:
  315. generate_policy_source._WriteChromeSettingsProtobuf(
  316. self.policies,
  317. self.policy_atomic_groups,
  318. self.target_platform,
  319. f,
  320. self.risk_tags,
  321. is_full_runtime=is_full_runtime)
  322. full_runtime_comment = '//' if is_full_runtime else ''
  323. full_runtime_suffix = '_full_runtime' if is_full_runtime else ''
  324. with self.subTest(is_full_runtime=is_full_runtime):
  325. mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
  326. expected_formatted = test_data.EXPECTED_CHROME_SETTINGS_PROTOBUF % {
  327. "full_runtime_comment": full_runtime_comment,
  328. "full_runtime_suffix": full_runtime_suffix,
  329. }
  330. self._assertCallsEqual(expected_formatted,
  331. mocked_file().write.call_args_list)
  332. def testWritePolicyProto(self):
  333. output_path = 'mock_write_policy_proto'
  334. with patch('codecs.open', mock_open()) as mocked_file:
  335. with codecs.open(output_path, 'w', encoding='utf-8') as f:
  336. generate_policy_source._WritePolicyProto(f, self.policies[0])
  337. mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
  338. self._assertCallsEqual(test_data.EXPECTED_POLICY_PROTO,
  339. mocked_file().write.call_args_list)
  340. def testGetMetapoliciesOfType(self):
  341. merge_metapolicies = generate_policy_source._GetMetapoliciesOfType(
  342. self.policies, "merge")
  343. self.assertEqual(1, len(merge_metapolicies))
  344. self.assertEqual("ExampleBoolMergeMetapolicy", merge_metapolicies[0].name)
  345. precedence_metapolicies = generate_policy_source._GetMetapoliciesOfType(
  346. self.policies, "precedence")
  347. self.assertEqual(1, len(precedence_metapolicies))
  348. self.assertEqual("ExampleBoolPrecedenceMetapolicy",
  349. precedence_metapolicies[0].name)
  350. invalid_metapolicies = generate_policy_source._GetMetapoliciesOfType(
  351. self.policies, "invalid")
  352. self.assertEqual(0, len(invalid_metapolicies))
  353. def testWritePolicyConstantHeader(self):
  354. output_path = 'mock_policy_constants_h'
  355. for target_platform in self.all_target_platforms:
  356. with patch('codecs.open', mock_open()) as mocked_file:
  357. with codecs.open(output_path, 'w', encoding='utf-8') as f:
  358. generate_policy_source._WritePolicyConstantHeader(
  359. self.policies,
  360. self.policy_atomic_groups,
  361. target_platform,
  362. f,
  363. self.risk_tags,
  364. )
  365. with self.subTest(target_platform=target_platform):
  366. mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
  367. if target_platform == 'win':
  368. windows_only_part = test_data.POLICY_CONSTANTS_HEADER_WIN_ONLY_PART
  369. else:
  370. windows_only_part = ''
  371. expected_formatted = test_data.EXPECTED_POLICY_CONSTANTS_HEADER % {
  372. "windows_only_part": windows_only_part,
  373. }
  374. self._assertCallsEqual(expected_formatted,
  375. mocked_file().write.call_args_list)
  376. def testWritePolicyConstantSource(self):
  377. self.maxDiff = None
  378. output_path = 'mock_policy_constants_cc'
  379. for target_platform in self.all_target_platforms:
  380. with patch('codecs.open', mock_open()) as mocked_file:
  381. with codecs.open(output_path, 'w', encoding='utf-8') as f:
  382. generate_policy_source._WritePolicyConstantSource(
  383. self.policies,
  384. self.policy_atomic_groups,
  385. target_platform,
  386. f,
  387. self.risk_tags,
  388. )
  389. with self.subTest(target_platform=target_platform):
  390. mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
  391. if target_platform == 'win':
  392. windows_only_part = test_data.POLICY_CONSTANTS_SOURCE_WIN_ONLY_PART
  393. else:
  394. windows_only_part = ''
  395. expected_formatted = test_data.EXPECTED_POLICY_CONSTANTS_SOURCE % {
  396. "windows_only_part": windows_only_part,
  397. }
  398. self._assertCallsEqual(expected_formatted,
  399. mocked_file().write.call_args_list)
  400. def testWriteChromeOSPolicyConstantsHeader(self):
  401. output_path = 'mock_policy_constants_h'
  402. with patch('codecs.open', mock_open()) as mocked_file:
  403. with codecs.open(output_path, 'w', encoding='utf-8') as f:
  404. generate_policy_source._WriteChromeOSPolicyConstantsHeader(
  405. self.policies,
  406. self.policy_atomic_groups,
  407. self.target_platform,
  408. f,
  409. self.risk_tags,
  410. )
  411. mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
  412. self._assertCallsEqual(test_data.EXPECTED_CROS_POLICY_CONSTANTS_HEADER,
  413. mocked_file().write.call_args_list)
  414. def testWriteChromeOSPolicyConstantsSource(self):
  415. output_path = 'mock_policy_constants_cc'
  416. with patch('codecs.open', mock_open()) as mocked_file:
  417. with codecs.open(output_path, 'w', encoding='utf-8') as f:
  418. generate_policy_source._WriteChromeOSPolicyConstantsSource(
  419. self.policies,
  420. self.policy_atomic_groups,
  421. self.target_platform,
  422. f,
  423. self.risk_tags,
  424. )
  425. mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
  426. self._assertCallsEqual(test_data.EXPECTED_CROS_POLICY_CONSTANTS_SOURCE,
  427. mocked_file().write.call_args_list)
  428. def testWriteAppRestrictions(self):
  429. output_path = 'app_restrictions_xml'
  430. with patch('codecs.open', mock_open()) as mocked_file:
  431. with codecs.open(output_path, 'w', encoding='utf-8') as f:
  432. generate_policy_source._WriteAppRestrictions(
  433. self.policies,
  434. self.policy_atomic_groups,
  435. self.target_platform,
  436. f,
  437. self.risk_tags,
  438. )
  439. mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
  440. self._assertCallsEqual(test_data.EXPECTED_APP_RESTRICTIONS_XML,
  441. mocked_file().write.call_args_list)
  442. def testChunkNumberAndFieldNumber(self):
  443. test_data = [
  444. # Last top-level policy
  445. PolicyData(policy_id=1015, chunk_number=0, field_number=1017),
  446. # First policy in chunk 1
  447. PolicyData(policy_id=1016, chunk_number=1, field_number=1),
  448. # Last policy in chunk 1
  449. PolicyData(policy_id=1815, chunk_number=1, field_number=800),
  450. # First policy in chunk 2
  451. PolicyData(policy_id=1816, chunk_number=2, field_number=1),
  452. # Last policy in chunk 2
  453. PolicyData(policy_id=2615, chunk_number=2, field_number=800),
  454. # First policy in chunk 3
  455. PolicyData(policy_id=2616, chunk_number=3, field_number=1),
  456. # Last policy in chunk 3
  457. PolicyData(policy_id=3415, chunk_number=3, field_number=800),
  458. # First policy in chunk 501
  459. PolicyData(policy_id=401016, chunk_number=501, field_number=1),
  460. # Last policy in chunk 501
  461. PolicyData(policy_id=401815, chunk_number=501, field_number=800),
  462. # First policy in chunk 502
  463. PolicyData(policy_id=401816, chunk_number=502, field_number=1),
  464. # Last policy in chunk 502
  465. PolicyData(policy_id=402615, chunk_number=502, field_number=800),
  466. # First policy in chunk 503
  467. PolicyData(policy_id=402616, chunk_number=503, field_number=1),
  468. # Last policy in chunk 503
  469. PolicyData(policy_id=403415, chunk_number=503, field_number=800),
  470. ]
  471. for policy_data in test_data:
  472. self.assertEqual(
  473. generate_policy_source._ChunkNumber(policy_data.policy_id),
  474. policy_data.chunk_number)
  475. self.assertEqual(
  476. generate_policy_source._FieldNumber(policy_data.policy_id,
  477. policy_data.chunk_number),
  478. policy_data.field_number)
  479. if __name__ == '__main__':
  480. unittest.main()