Skip to content

Commit

Permalink
Add additional param to policy.RuleDefault
Browse files Browse the repository at this point in the history
This patch adds the needed subclass to define policy objects
that now require the description and operations parameters.

The operations parameter when defining a policy rule should follow
the below format.
    - operations : list of dicts containing the api url and verb.
        Ex : operations=[{path='/some/url/', method='GET'}]

This change make sure operators have all the information needed
in a centralized location in order to make decisions and understand
the purpose of a certain policy.

Change-Id: Ie9b335420394166bb39c43e3d26fcc9237ffd1a0
  • Loading branch information
Anthony Washington committed Mar 15, 2017
1 parent 0a5112c commit a95606c
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 0 deletions.
60 changes: 60 additions & 0 deletions oslo_policy/policy.py
Expand Up @@ -321,6 +321,13 @@ def __init__(self, names):
super(InvalidDefinitionError, self).__init__(msg)


class InvalidRuleDefault(Exception):
def __init__(self, error):
msg = (_('Invalid policy rule default: '
'%(error)s.') % {'error': error})
super(InvalidRuleDefault, self).__init__(msg)


def parse_file_contents(data):
"""Parse the raw contents of a policy file.
Expand Down Expand Up @@ -832,3 +839,56 @@ def __eq__(self, other):
isinstance(other, self.__class__))):
return True
return False


class DocumentedRuleDefault(RuleDefault):
"""A class for holding policy-in-code policy objects definitions
This class provides the same functionality as the RuleDefault class, but it
also requires additional data about the policy rule being registered. This
is necessary so that proper documentation can be rendered based on the
attributes of this class. Eventually, all usage of RuleDefault should be
converted to use DocumentedRuleDefault.
:param operations: List of dicts containing each api url and
corresponding http request method.
Example:
operations=[{'path': '/foo', 'method': 'GET'},
{'path': '/some', 'method': 'POST'}]
"""
def __init__(self, name, check_str, description, operations):
super(DocumentedRuleDefault, self).__init__(
name, check_str, description)
self.operations = operations

@property
def description(self):
return self._description

@description.setter
def description(self, value):
# Validates description isn't empty.
if not value:
raise InvalidRuleDefault('Description is required')
self._description = value

@property
def operations(self):
return self._operations

@operations.setter
def operations(self, ops):
if not isinstance(ops, list):
raise InvalidRuleDefault('Operations must be a list')
if not ops:
raise InvalidRuleDefault('Operations list must not be empty')

for op in ops:
if 'path' not in op:
raise InvalidRuleDefault('Operation must contain a path')
if 'method' not in op:
raise InvalidRuleDefault('Operation must contain a method')
if len(op.keys()) > 2:
raise InvalidRuleDefault('Operation contains > 2 keys')
self._operations = ops
83 changes: 83 additions & 0 deletions oslo_policy/tests/test_policy.py
Expand Up @@ -834,6 +834,89 @@ class RuleDefaultSub(policy.RuleDefault):
self.assertNotEqual(opt1, opt2)


class DocumentedRuleDefaultTestCase(base.PolicyBaseTestCase):

def test_contain_operations(self):
opt = policy.DocumentedRuleDefault(
name='foo', check_str='rule:foo', description='foo_api',
operations=[{'path': '/foo/', 'method': 'GET'}])

self.assertEqual(1, len(opt.operations))

def test_multiple_operations(self):
opt = policy.DocumentedRuleDefault(
name='foo', check_str='rule:foo', description='foo_api',
operations=[{'path': '/foo/', 'method': 'GET'},
{'path': '/foo/', 'method': 'POST'}])

self.assertEqual(2, len(opt.operations))

def test_description_not_empty(self):
invalid_desc = ''
self.assertRaises(policy.InvalidRuleDefault,
policy.DocumentedRuleDefault,
name='foo',
check_str='rule:foo',
description=invalid_desc,
operations=[{'path': '/foo/', 'method': 'GET'}])

def test_operation_not_empty_list(self):
invalid_op = []
self.assertRaises(policy.InvalidRuleDefault,
policy.DocumentedRuleDefault,
name='foo',
check_str='rule:foo',
description='foo_api',
operations=invalid_op)

def test_operation_must_be_list(self):
invalid_op = 'invalid_op'
self.assertRaises(policy.InvalidRuleDefault,
policy.DocumentedRuleDefault,
name='foo',
check_str='rule:foo',
description='foo_api',
operations=invalid_op)

def test_operation_must_be_list_of_dicts(self):
invalid_op = ['invalid_op']
self.assertRaises(policy.InvalidRuleDefault,
policy.DocumentedRuleDefault,
name='foo',
check_str='rule:foo',
description='foo_api',
operations=invalid_op)

def test_operation_must_have_path(self):
invalid_op = [{'method': 'POST'}]
self.assertRaises(policy.InvalidRuleDefault,
policy.DocumentedRuleDefault,
name='foo',
check_str='rule:foo',
description='foo_api',
operations=invalid_op)

def test_operation_must_have_method(self):
invalid_op = [{'path': '/foo/path/'}]
self.assertRaises(policy.InvalidRuleDefault,
policy.DocumentedRuleDefault,
name='foo',
check_str='rule:foo',
description='foo_api',
operations=invalid_op)

def test_operation_must_contain_method_and_path_only(self):
invalid_op = [{'path': '/some/path/',
'method': 'GET',
'break': 'me'}]
self.assertRaises(policy.InvalidRuleDefault,
policy.DocumentedRuleDefault,
name='foo',
check_str='rule:foo',
description='foo_api',
operations=invalid_op)


class EnforcerCheckRulesTest(base.PolicyBaseTestCase):
def setUp(self):
super(EnforcerCheckRulesTest, self).setUp()
Expand Down

0 comments on commit a95606c

Please sign in to comment.