1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
|
#!/usr/bin/env python3
# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import glob
import json
import copy
import os
import sys
# This script runs in Chromium and ChromiumOS.
try:
# Chromium.
_SRC_PATH = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', '..'))
sys.path.append(os.path.join(_SRC_PATH, 'third_party'))
import pyyaml
except ImportError:
# ChromiumOS.
# Some consumers (ChromiumOS ebuilds) of this script have pyyaml already in
# their environment and may not have access to the full Chromium repository
# nor the exact file structure. Import based on the Chromium third_party may
# not always be possible.
# Those consumers may install pyyaml in their environment and import it with
# the name of the module instead as per the library documentation.
# For compatibility reasons, please refer to
# https://source.chromium.org/chromium/chromium/src/+/main:third_party/pyyaml/README.chromium
# to know which version of pyyaml to use.
import yaml as pyyaml
def _SafeListDir(directory):
'''Wrapper around os.listdir() that ignores files created by Finder.app.'''
# On macOS, Finder.app creates .DS_Store files when a user visit a
# directory causing failure of the script laters on because there
# are no such group as .DS_Store. Skip the file to prevent the error.
return filter(lambda name:(name != '.DS_Store'),sorted(os.listdir(directory)))
TEMPLATES_PATH = os.path.join(
os.path.dirname(__file__), 'templates')
DEFAULT_TEMPLATES_GEN_PATH = os.path.join(
os.path.dirname(__file__), 'policy_templates.json')
POLICY_DEFINITIONS_KEY = 'policy_definitions'
def _SubstituteSchemaRefNames(node, child_key, common_schema, parent_refs,
refs_seen):
'''Converts recursively objects with the key '$ref' into their actual schema.
'''
if (not isinstance(node, dict) or not child_key in node
or not isinstance(node[child_key], dict)):
return
if '$ref' in node[child_key]:
ref_name = node[child_key]['$ref']
# If the parent has the same id as child, leave the child's ref to avoid
# infinite loop.
if ref_name not in parent_refs:
node[child_key] = copy.deepcopy(common_schema[ref_name])
parent_refs.add(ref_name)
# If the schema has been seen already, only keep a reference to the first to
# avoid having the same ref defined at multiple places.
if ref_name not in refs_seen:
node[child_key]['id'] = ref_name
refs_seen.add(ref_name)
for ck in sorted(node[child_key].keys()):
# Copy parents ref so that parents are unique for each child branch and do
# not mix with sibling nodes.
_SubstituteSchemaRefNames(node[child_key], ck, common_schema,
parent_refs.copy(), refs_seen)
def _SubstituteSchemaRefs(policies, common_schema):
'''Converts objects with the key '$ref' into their actual schema.
Args:
policies: List of policies.
common_schema: Dictionary of schemas by their ref names.'''
policy_list = [policy for policy in policies if 'schema' in policy]
refs_seen = set()
for policy in sorted(policy_list, key=lambda policy: policy['id']):
parent_refs = set()
_SubstituteSchemaRefNames(policy, 'schema', common_schema, parent_refs,
refs_seen)
parent_refs = set()
_SubstituteSchemaRefNames(policy, 'validation_schema', common_schema,
parent_refs, refs_seen)
def _BuildPolicyTemplate(data):
'''
Converts data into the format of the old policy_templates.json so that it
can be used in policy_templates.grd
Schema : {
"policy_definitions": {
"type": "list",
"items": {
"id": { "type": "number" },
"name": "string",
"policies": { "type": "list", "items": "string" } // for policy groups.
// See components/policy/resources/new_policy_templates/policy.yaml
// for the other variables/
}
},
"messages": {
"type": "Object",
"patternProperties": {
"[A-Za-z]": { "desc": "string", "text": "String" }
}
},
//components/policy/resources/templates/manual_device_policy_proto_map.yaml
// Includes policies where generate_device_proto is true.
"device_policy_proto_map": {
"type": "Object",
"patternProperties": {
"[A-Za-z]": { "type": "Object", "properties": "String" }
}
},
// Only lists of 2 items
"legacy_device_policy_proto_map": { "type": "list", "items": "String" },
//components/policy/resources/templates/messages.yaml
"messages": {
"type": "Object",
"patternProperties": {
"[A-Za-z]": {
"type": "Object",
"properties": { "text": string, "description": "string" }
}
},
"risk_tag_definitions": {
"type": "list",
"items": {
"name": "string",
"description": "string",
"user-description": "string"
}
},
"policy_atomic_group_definitions": {
"type": "list",
"items": {
"id": { "type": "number" },
"name": "string",
"caption": "string",
"policies": { "type": "list", "items": "string" } // for policy groups.
// See components/policy/resources/new_policy_templates/policy.yaml
// for the other variables/
}
},
"placeholders": { "type": "list" },
"deleted_policy_ids": { "type": "list", "items": { "type": "number" } },
"deleted_atomic_policy_group_ids": {
"type": "list",
"items": { "type": "number" }
},
"highest_id_currently_used": { "type": "number" },
"highest_atomic_group_id_currently_used": { "type": "number" },
}
'''
policy_name_id = { name: id for id, name
in data['policies']['policies'].items() if name }
atomic_group_name_id = { name: id
for id, name in data['policies']['atomic_groups'].items() if name }
policy_groups = [{
'name': group_name,
'type': 'group',
'caption': group['caption'],
'desc': group['desc'],
'policies': list(group['policies'])
} for group_name, group in data[POLICY_DEFINITIONS_KEY].items()]
policies = []
atomic_groups = []
for group in data[POLICY_DEFINITIONS_KEY].values():
for policy_name, policy in group['policies'].items():
policies.append({
'id': policy_name_id[policy_name],
'name': policy_name, **policy
})
for name, atomic_group in group['policy_atomic_groups'].items():
atomic_groups.append({
'id': atomic_group_name_id[name],
'name': name, **atomic_group
})
device_policy_proto_map = data['manual_device_policy_proto_map'].copy()
for policy in policies:
if not policy.get('device_only', False):
continue
if not policy.get('generate_device_proto', True):
continue
device_policy_proto_map[policy['name']] = policy['name'] + '.value'
result = {
POLICY_DEFINITIONS_KEY: policies + policy_groups,
'deleted_policy_ids':
[id for id, name in data['policies']['policies'].items() if not name],
'highest_id_currently_used': len(data['policies']['policies']),
'policy_atomic_group_definitions': atomic_groups,
'deleted_atomic_policy_group_ids': [
id for id, name in data['policies']['atomic_groups'].items()
if not name
],
'highest_atomic_group_id_currently_used':
len(data['policies']['atomic_groups']),
'placeholders': [],
'legacy_device_policy_proto_map': [],
'device_policy_proto_map': device_policy_proto_map,
'messages': data['messages'],
'risk_tag_definitions': [{'name': name, **value}
for name, value in data['risk_tag_definitions'].items()]
}
for key, values in data['legacy_device_policy_proto_map'].items():
for item in values:
result['legacy_device_policy_proto_map'].append([key, item])
_SubstituteSchemaRefs(result[POLICY_DEFINITIONS_KEY], data['common_schemas'])
return result
def _GetMetadata():
'''Returns an object containing the policy metadata in order to build the
policy definition template.'''
result = {}
for file in _SafeListDir(TEMPLATES_PATH):
filename = os.fsdecode(file)
file_basename, file_extension = os.path.splitext(filename)
if not file_extension == ".yaml":
continue
with open(os.path.join(TEMPLATES_PATH, filename), encoding='utf-8') as f:
result[file_basename] = pyyaml.safe_load(f)
return result
def _GetPoliciesAndGroups():
'''Returns an object containing the policy groups with their details, policies
and atomic policy groups in order to build the policy definition template.
'''
result = {}
policy_definitions_path = os.path.join(TEMPLATES_PATH, POLICY_DEFINITIONS_KEY)
for group_name in _SafeListDir(policy_definitions_path):
result[group_name] = {'policies': {}, 'policy_atomic_groups': {}}
group_path = os.path.join(policy_definitions_path, group_name)
if not os.path.isdir(group_path):
continue
for file in _SafeListDir(group_path):
filename = os.fsdecode(file)
file_basename, file_extension = os.path.splitext(filename)
file_path = os.path.join(group_path, filename)
if file_extension != '.yaml':
continue
with open(file_path, encoding='utf-8') as f:
data = pyyaml.safe_load(f)
if file_basename == '.group.details':
result[group_name].update(data)
elif file_basename == 'policy_atomic_groups':
result[group_name]['policy_atomic_groups'].update(data)
else:
result[group_name]['policies'][file_basename] = data
return result
def _LoadPolicies():
'''
Loads all the yaml files used to define policies and their metadata into a
single object. This is a direct representation of the data structure of the
directories and their files.
Schema : {
//components/policy/resources/templates/policies.yaml
"policies":{
// Map of policy atomic group ID to policy atomic group name.
"atomic_groups": {
"type": "Object",
"patternProperties": {
"[A-Za-z]": { "type": "Object", "properties": "String" }
}
}
// Map of policy ID to policy name.
"policies": {
"type": "Object",
"patternProperties": {
"[A-Za-z]": { "type": "Object", "properties": "String" }
}
}
}
"policy_definitions": {
"type": "Object",
"patternProperties": {
// Dictionary of policy groups
"[A-Za-z]": {
"type": "Object",
// c/policy/resources/new_policy_templates/.groups.details.yaml
"properties": {
"caption": {"type": "string"},
"description": {"type": "string"}
},
"patternProperties": {
// Dictionary of policies
"[A-Za-z]": {
"type": "Object",
"properties": {
// c/policy/resources/new_policy_templates/policy.yaml
}
}
}
}
}
}
},
// components/policy/resources/templates/manual_device_policy_proto_map.yaml
"manual_device_policy_proto_map": {
"type": "Object",
"patternProperties": {
"[A-Za-z]": { "type": "Object", "properties": "String" }
}
},
// components/policy/resources/templates/legacy_device_policy_proto_map.yaml
"legacy_device_policy_proto_map": {
"type": "Object",
"patternProperties": {
"[A-Za-z]": { "type": "list", "items": "String" }
}
},
// components/policy/resources/templates/messages.yaml
"messages": {
"type": "Object",
"patternProperties": {
"[A-Za-z]": {
"type": "Object",
"properties": { "text": string, "description": "string" }
}
}
},
// components/policy/resources/templates/risk_tag_definitions.yaml
"risk_tag_definitions": {
"type": "Object",
"patternProperties": {
"[A-Za-z]": {
"type": "Object",
"properties": { "description": string, "user-description": "string" }
}
}
}
}
'''
return {
POLICY_DEFINITIONS_KEY: _GetPoliciesAndGroups(),
**_GetMetadata()
}
def GetPolicyTemplates():
'''Returns an object containing the policy templates.
'''
template = _LoadPolicies()
return _BuildPolicyTemplate(template)
def _WriteDepFile(dep_file, target, source_files):
'''Writes a dep file for `target` at `dep_file` with `source_files` as the
dependencies.
Args:
dep_file: A path to the dependencies file for this script.
target: The build target.
source_files: A list of the dependencies for the build target.
'''
with open(dep_file, "w") as f:
f.write(target)
f.write(": ")
f.write(' '.join(source_files))
def main():
'''Generates the a JSON file at `dest` with all the policy definitions.
If `dest` is not specified, a file name 'policy_templates.json' will be
generated in the same directory as the script.
Args:
dest: A path to the policy templates generated definitions.
depfile: A path to the dependencies file for this script.
'''
parser = argparse.ArgumentParser()
parser.add_argument('--dest', dest='dest')
parser.add_argument('--depfile', dest='deps_file')
args = parser.parse_args()
if args.dest:
path = os.path.join(args.dest)
else:
path = DEFAULT_TEMPLATES_GEN_PATH
policy_templates = GetPolicyTemplates()
with open(path, 'w+', encoding='utf-8') as dest:
json.dump(policy_templates, dest, indent=2, sort_keys=True)
files = sorted([f.replace('\\', '/')
for f in glob.glob(TEMPLATES_PATH + '/**/*.yaml', recursive=True)])
if args.deps_file:
_WriteDepFile(args.deps_file, args.dest, files)
if '__main__' == __name__:
sys.exit(main())
|