diff options
Diffstat (limited to 'chromium/components/policy/tools/syntax_check_policy_template_json.py')
-rwxr-xr-x | chromium/components/policy/tools/syntax_check_policy_template_json.py | 501 |
1 files changed, 0 insertions, 501 deletions
diff --git a/chromium/components/policy/tools/syntax_check_policy_template_json.py b/chromium/components/policy/tools/syntax_check_policy_template_json.py deleted file mode 100755 index f51e3e3dc5a..00000000000 --- a/chromium/components/policy/tools/syntax_check_policy_template_json.py +++ /dev/null @@ -1,501 +0,0 @@ -#!/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. - -''' -Checks a policy_templates.json file for conformity to its syntax specification. -''' - -import json -import optparse -import os -import re -import sys - - -LEADING_WHITESPACE = re.compile('^([ \t]*)') -TRAILING_WHITESPACE = re.compile('.*?([ \t]+)$') -# Matches all non-empty strings that contain no whitespaces. -NO_WHITESPACE = re.compile('[^\s]+$') - -# Convert a 'type' to its corresponding schema type. -TYPE_TO_SCHEMA = { - 'int': 'integer', - 'list': 'array', - 'dict': 'object', - 'main': 'boolean', - 'string': 'string', - 'int-enum': 'integer', - 'string-enum': 'string', - 'external': 'object', -} - -# List of boolean policies that have been introduced with negative polarity in -# the past and should not trigger the negative polarity check. -LEGACY_INVERTED_POLARITY_WHITELIST = [ - 'DeveloperToolsDisabled', - 'DeviceAutoUpdateDisabled', - 'Disable3DAPIs', - 'DisableAuthNegotiateCnameLookup', - 'DisablePluginFinder', - 'DisablePrintPreview', - 'DisableSafeBrowsingProceedAnyway', - 'DisableScreenshots', - 'DisableSpdy', - 'DisableSSLRecordSplitting', - 'DriveDisabled', - 'DriveDisabledOverCellular', - 'ExternalStorageDisabled', - 'SavingBrowserHistoryDisabled', - 'SyncDisabled', -] - -class PolicyTemplateChecker(object): - - def __init__(self): - self.error_count = 0 - self.warning_count = 0 - self.num_policies = 0 - self.num_groups = 0 - self.num_policies_in_groups = 0 - self.options = None - self.features = [] - - def _Error(self, message, parent_element=None, identifier=None, - offending_snippet=None): - self.error_count += 1 - error = '' - if identifier is not None and parent_element is not None: - error += 'In %s %s: ' % (parent_element, identifier) - print error + 'Error: ' + message - if offending_snippet is not None: - print ' Offending:', json.dumps(offending_snippet, indent=2) - - def _CheckContains(self, container, key, value_type, - optional=False, - parent_element='policy', - container_name=None, - identifier=None, - offending='__CONTAINER__', - regexp_check=None): - ''' - Checks |container| for presence of |key| with value of type |value_type|. - If |value_type| is string and |regexp_check| is specified, then an error is - reported when the value does not match the regular expression object. - - The other parameters are needed to generate, if applicable, an appropriate - human-readable error message of the following form: - - In |parent_element| |identifier|: - (if the key is not present): - Error: |container_name| must have a |value_type| named |key|. - Offending snippet: |offending| (if specified; defaults to |container|) - (if the value does not have the required type): - Error: Value of |key| must be a |value_type|. - Offending snippet: |container[key]| - - Returns: |container[key]| if the key is present, None otherwise. - ''' - if identifier is None: - identifier = container.get('name') - if container_name is None: - container_name = parent_element - if offending == '__CONTAINER__': - offending = container - if key not in container: - if optional: - return - else: - self._Error('%s must have a %s "%s".' % - (container_name.title(), value_type.__name__, key), - container_name, identifier, offending) - return None - value = container[key] - if not isinstance(value, value_type): - self._Error('Value of "%s" must be a %s.' % - (key, value_type.__name__), - container_name, identifier, value) - if value_type == str and regexp_check and not regexp_check.match(value): - self._Error('Value of "%s" must match "%s".' % - (key, regexp_check.pattern), - container_name, identifier, value) - return value - - def _AddPolicyID(self, id, policy_ids, policy): - ''' - Adds |id| to |policy_ids|. Generates an error message if the - |id| exists already; |policy| is needed for this message. - ''' - if id in policy_ids: - self._Error('Duplicate id', 'policy', policy.get('name'), - id) - else: - policy_ids.add(id) - - def _CheckPolicyIDs(self, policy_ids): - ''' - Checks a set of policy_ids to make sure it contains a continuous range - of entries (i.e. no holes). - Holes would not be a technical problem, but we want to ensure that nobody - accidentally omits IDs. - ''' - for i in range(len(policy_ids)): - if (i + 1) not in policy_ids: - self._Error('No policy with id: %s' % (i + 1)) - - def _CheckPolicySchema(self, policy, policy_type): - '''Checks that the 'schema' field matches the 'type' field.''' - self._CheckContains(policy, 'schema', dict) - if isinstance(policy.get('schema'), dict): - self._CheckContains(policy['schema'], 'type', str) - schema_type = policy['schema'].get('type') - if schema_type != TYPE_TO_SCHEMA[policy_type]: - self._Error('Schema type must match the existing type for policy %s' % - policy.get('name')) - - # Checks that boolean policies are not negated (which makes them harder to - # reason about). - if (schema_type == 'boolean' and - 'disable' in policy.get('name').lower() and - policy.get('name') not in LEGACY_INVERTED_POLARITY_WHITELIST): - self._Error(('Boolean policy %s uses negative polarity, please make ' + - 'new boolean policies follow the XYZEnabled pattern. ' + - 'See also http://crbug.com/85687') % policy.get('name')) - - - def _CheckPolicy(self, policy, is_in_group, policy_ids): - if not isinstance(policy, dict): - self._Error('Each policy must be a dictionary.', 'policy', None, policy) - return - - # There should not be any unknown keys in |policy|. - for key in policy: - if key not in ('name', 'type', 'caption', 'desc', 'device_only', - 'supported_on', 'label', 'policies', 'items', - 'example_value', 'features', 'deprecated', 'future', - 'id', 'schema', 'max_size'): - self.warning_count += 1 - print ('In policy %s: Warning: Unknown key: %s' % - (policy.get('name'), key)) - - # Each policy must have a name. - self._CheckContains(policy, 'name', str, regexp_check=NO_WHITESPACE) - - # Each policy must have a type. - policy_types = ('group', 'main', 'string', 'int', 'list', 'int-enum', - 'string-enum', 'dict', 'external') - policy_type = self._CheckContains(policy, 'type', str) - if policy_type not in policy_types: - self._Error('Policy type must be one of: ' + ', '.join(policy_types), - 'policy', policy.get('name'), policy_type) - return # Can't continue for unsupported type. - - # Each policy must have a caption message. - self._CheckContains(policy, 'caption', str) - - # Each policy must have a description message. - self._CheckContains(policy, 'desc', str) - - # If 'label' is present, it must be a string. - self._CheckContains(policy, 'label', str, True) - - # If 'deprecated' is present, it must be a bool. - self._CheckContains(policy, 'deprecated', bool, True) - - # If 'future' is present, it must be a bool. - self._CheckContains(policy, 'future', bool, True) - - if policy_type == 'group': - # Groups must not be nested. - if is_in_group: - self._Error('Policy groups must not be nested.', 'policy', policy) - - # Each policy group must have a list of policies. - policies = self._CheckContains(policy, 'policies', list) - - # Check sub-policies. - if policies is not None: - for nested_policy in policies: - self._CheckPolicy(nested_policy, True, policy_ids) - - # Groups must not have an |id|. - if 'id' in policy: - self._Error('Policies of type "group" must not have an "id" field.', - 'policy', policy) - - # Statistics. - self.num_groups += 1 - - else: # policy_type != group - # Each policy must have a protobuf ID. - id = self._CheckContains(policy, 'id', int) - self._AddPolicyID(id, policy_ids, policy) - - # 'schema' is the new 'type'. - # TODO(joaodasilva): remove the 'type' checks once 'schema' is used - # everywhere. - self._CheckPolicySchema(policy, policy_type) - - # Each policy must have a supported_on list. - supported_on = self._CheckContains(policy, 'supported_on', list) - if supported_on is not None: - for s in supported_on: - if not isinstance(s, str): - self._Error('Entries in "supported_on" must be strings.', 'policy', - policy, supported_on) - - # Each policy must have a 'features' dict. - features = self._CheckContains(policy, 'features', dict) - - # All the features must have a documenting message. - if features: - for feature in features: - if not feature in self.features: - self._Error('Unknown feature "%s". Known features must have a ' - 'documentation string in the messages dictionary.' % - feature, 'policy', policy.get('name', policy)) - - # All user policies must have a per_profile feature flag. - if (not policy.get('device_only', False) and - not policy.get('deprecated', False) and - not filter(re.compile('^chrome_frame:.*').match, supported_on)): - self._CheckContains(features, 'per_profile', bool, - container_name='features', - identifier=policy.get('name')) - - # All policies must declare whether they allow changes at runtime. - self._CheckContains(features, 'dynamic_refresh', bool, - container_name='features', - identifier=policy.get('name')) - - # Each policy must have an 'example_value' of appropriate type. - if policy_type == 'main': - value_type = bool - elif policy_type in ('string', 'string-enum'): - value_type = str - elif policy_type in ('int', 'int-enum'): - value_type = int - elif policy_type == 'list': - value_type = list - elif policy_type in ('dict', 'external'): - value_type = dict - else: - raise NotImplementedError('Unimplemented policy type: %s' % policy_type) - self._CheckContains(policy, 'example_value', value_type) - - # Statistics. - self.num_policies += 1 - if is_in_group: - self.num_policies_in_groups += 1 - - if policy_type in ('int-enum', 'string-enum'): - # Enums must contain a list of items. - items = self._CheckContains(policy, 'items', list) - if items is not None: - if len(items) < 1: - self._Error('"items" must not be empty.', 'policy', policy, items) - for item in items: - # Each item must have a name. - # Note: |policy.get('name')| is used instead of |policy['name']| - # because it returns None rather than failing when no key called - # 'name' exists. - self._CheckContains(item, 'name', str, container_name='item', - identifier=policy.get('name'), - regexp_check=NO_WHITESPACE) - - # Each item must have a value of the correct type. - self._CheckContains(item, 'value', value_type, container_name='item', - identifier=policy.get('name')) - - # Each item must have a caption. - self._CheckContains(item, 'caption', str, container_name='item', - identifier=policy.get('name')) - - if policy_type == 'external': - # Each policy referencing external data must specify a maximum data size. - self._CheckContains(policy, 'max_size', int) - - def _CheckMessage(self, key, value): - # |key| must be a string, |value| a dict. - if not isinstance(key, str): - self._Error('Each message key must be a string.', 'message', key, key) - return - - if not isinstance(value, dict): - self._Error('Each message must be a dictionary.', 'message', key, value) - return - - # Each message must have a desc. - self._CheckContains(value, 'desc', str, parent_element='message', - identifier=key) - - # Each message must have a text. - self._CheckContains(value, 'text', str, parent_element='message', - identifier=key) - - # There should not be any unknown keys in |value|. - for vkey in value: - if vkey not in ('desc', 'text'): - self.warning_count += 1 - print 'In message %s: Warning: Unknown key: %s' % (key, vkey) - - def _LeadingWhitespace(self, line): - match = LEADING_WHITESPACE.match(line) - if match: - return match.group(1) - return '' - - def _TrailingWhitespace(self, line): - match = TRAILING_WHITESPACE.match(line) - if match: - return match.group(1) - return '' - - def _LineError(self, message, line_number): - self.error_count += 1 - print 'In line %d: Error: %s' % (line_number, message) - - def _LineWarning(self, message, line_number): - self.warning_count += 1 - print ('In line %d: Warning: Automatically fixing formatting: %s' - % (line_number, message)) - - def _CheckFormat(self, filename): - if self.options.fix: - fixed_lines = [] - with open(filename) as f: - indent = 0 - line_number = 0 - for line in f: - line_number += 1 - line = line.rstrip('\n') - # Check for trailing whitespace. - trailing_whitespace = self._TrailingWhitespace(line) - if len(trailing_whitespace) > 0: - if self.options.fix: - line = line.rstrip() - self._LineWarning('Trailing whitespace.', line_number) - else: - self._LineError('Trailing whitespace.', line_number) - if self.options.fix: - if len(line) == 0: - fixed_lines += ['\n'] - continue - else: - if line == trailing_whitespace: - # This also catches the case of an empty line. - continue - # Check for correct amount of leading whitespace. - leading_whitespace = self._LeadingWhitespace(line) - if leading_whitespace.count('\t') > 0: - if self.options.fix: - leading_whitespace = leading_whitespace.replace('\t', ' ') - line = leading_whitespace + line.lstrip() - self._LineWarning('Tab character found.', line_number) - else: - self._LineError('Tab character found.', line_number) - if line[len(leading_whitespace)] in (']', '}'): - indent -= 2 - if line[0] != '#': # Ignore 0-indented comments. - if len(leading_whitespace) != indent: - if self.options.fix: - line = ' ' * indent + line.lstrip() - self._LineWarning('Indentation should be ' + str(indent) + - ' spaces.', line_number) - else: - self._LineError('Bad indentation. Should be ' + str(indent) + - ' spaces.', line_number) - if line[-1] in ('[', '{'): - indent += 2 - if self.options.fix: - fixed_lines.append(line + '\n') - - # If --fix is specified: backup the file (deleting any existing backup), - # then write the fixed version with the old filename. - if self.options.fix: - if self.options.backup: - backupfilename = filename + '.bak' - if os.path.exists(backupfilename): - os.remove(backupfilename) - os.rename(filename, backupfilename) - with open(filename, 'w') as f: - f.writelines(fixed_lines) - - def Main(self, filename, options): - try: - with open(filename) as f: - data = eval(f.read()) - except: - import traceback - traceback.print_exc(file=sys.stdout) - self._Error('Invalid JSON syntax.') - return - if data == None: - self._Error('Invalid JSON syntax.') - return - self.options = options - - # First part: check JSON structure. - - # Check (non-policy-specific) message definitions. - messages = self._CheckContains(data, 'messages', dict, - parent_element=None, - container_name='The root element', - offending=None) - if messages is not None: - for message in messages: - self._CheckMessage(message, messages[message]) - if message.startswith('doc_feature_'): - self.features.append(message[12:]) - - # Check policy definitions. - policy_definitions = self._CheckContains(data, 'policy_definitions', list, - parent_element=None, - container_name='The root element', - offending=None) - if policy_definitions is not None: - policy_ids = set() - for policy in policy_definitions: - self._CheckPolicy(policy, False, policy_ids) - self._CheckPolicyIDs(policy_ids) - - # Second part: check formatting. - self._CheckFormat(filename) - - # Third part: summary and exit. - print ('Finished checking %s. %d errors, %d warnings.' % - (filename, self.error_count, self.warning_count)) - if self.options.stats: - if self.num_groups > 0: - print ('%d policies, %d of those in %d groups (containing on ' - 'average %.1f policies).' % - (self.num_policies, self.num_policies_in_groups, self.num_groups, - (1.0 * self.num_policies_in_groups / self.num_groups))) - else: - print self.num_policies, 'policies, 0 policy groups.' - if self.error_count > 0: - return 1 - return 0 - - def Run(self, argv, filename=None): - parser = optparse.OptionParser( - usage='usage: %prog [options] filename', - description='Syntax check a policy_templates.json file.') - parser.add_option('--fix', action='store_true', - help='Automatically fix formatting.') - parser.add_option('--backup', action='store_true', - help='Create backup of original file (before fixing).') - parser.add_option('--stats', action='store_true', - help='Generate statistics.') - (options, args) = parser.parse_args(argv) - if filename is None: - if len(args) != 2: - parser.print_help() - sys.exit(1) - filename = args[1] - return self.Main(filename, options) - - -if __name__ == '__main__': - sys.exit(PolicyTemplateChecker().Run(sys.argv)) |