Teach Enforcer.enforce to deal with context objects
The ``creds`` dictionary passed into oslo.policy's enforce() method assumes a lot of the same values already specified by oslo.context RequestContext objects. This commit teaches enforce() to handle being passed an instance of a RequestContext object, and populate credential values accordingly. Change-Id: Ia74bf6c40b1e05a1c958f4325e00f68be28d91b9 Closes-Bug: 1779172
This commit is contained in:
parent
13a4e789bc
commit
775641a5fc
|
@ -28,6 +28,7 @@ netifaces==0.10.4
|
||||||
openstackdocstheme==1.18.1
|
openstackdocstheme==1.18.1
|
||||||
os-client-config==1.28.0
|
os-client-config==1.28.0
|
||||||
oslo.config==5.2.0
|
oslo.config==5.2.0
|
||||||
|
oslo.context==2.21.0
|
||||||
oslo.i18n==3.15.3
|
oslo.i18n==3.15.3
|
||||||
oslo.serialization==2.18.0
|
oslo.serialization==2.18.0
|
||||||
oslo.utils==3.33.0
|
oslo.utils==3.33.0
|
||||||
|
|
|
@ -221,12 +221,14 @@ by setting the ``policy_default_rule`` configuration setting to the
|
||||||
desired rule name.
|
desired rule name.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import collections
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_context import context
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import six
|
import six
|
||||||
import yaml
|
import yaml
|
||||||
|
@ -342,6 +344,13 @@ class InvalidRuleDefault(Exception):
|
||||||
super(InvalidRuleDefault, self).__init__(msg)
|
super(InvalidRuleDefault, self).__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidContextObject(Exception):
|
||||||
|
def __init__(self, error):
|
||||||
|
msg = (_('Invalid context object: '
|
||||||
|
'%(error)s.') % {'error': error})
|
||||||
|
super(InvalidContextObject, self).__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
def parse_file_contents(data):
|
def parse_file_contents(data):
|
||||||
"""Parse the raw contents of a policy file.
|
"""Parse the raw contents of a policy file.
|
||||||
|
|
||||||
|
@ -789,7 +798,8 @@ class Enforcer(object):
|
||||||
the Mapping abstract base class and deep
|
the Mapping abstract base class and deep
|
||||||
copying.
|
copying.
|
||||||
:param dict creds: As much information about the user performing the
|
:param dict creds: As much information about the user performing the
|
||||||
action as possible.
|
action as possible. This parameter can also be an
|
||||||
|
instance of ``oslo_context.context.RequestContext``.
|
||||||
:param do_raise: Whether to raise an exception or not if check
|
:param do_raise: Whether to raise an exception or not if check
|
||||||
fails.
|
fails.
|
||||||
:param exc: Class of the exception to raise if the check fails.
|
:param exc: Class of the exception to raise if the check fails.
|
||||||
|
@ -807,6 +817,23 @@ class Enforcer(object):
|
||||||
|
|
||||||
self.load_rules()
|
self.load_rules()
|
||||||
|
|
||||||
|
if isinstance(creds, context.RequestContext):
|
||||||
|
creds = self._map_context_attributes_into_creds(creds)
|
||||||
|
# NOTE(lbragstad): The oslo.context library exposes the ability to call
|
||||||
|
# a method on RequestContext objects that converts attributes of the
|
||||||
|
# context object to policy values. However, ``to_policy_values()``
|
||||||
|
# doesn't actually return a dictionary, it's a subclass of
|
||||||
|
# collections.MutableMapping, which behaves like a dictionary but
|
||||||
|
# doesn't pass the type check.
|
||||||
|
elif not isinstance(creds, collections.MutableMapping):
|
||||||
|
msg = (
|
||||||
|
'Expected type oslo_context.context.RequestContext, dict, or '
|
||||||
|
'the output of '
|
||||||
|
'oslo_context.context.RequestContext.to_policy_values but '
|
||||||
|
'got %(creds_type)s instead' % {'creds_type': type(creds)}
|
||||||
|
)
|
||||||
|
raise InvalidContextObject(msg)
|
||||||
|
|
||||||
# Allow the rule to be a Check tree
|
# Allow the rule to be a Check tree
|
||||||
if isinstance(rule, _checks.BaseCheck):
|
if isinstance(rule, _checks.BaseCheck):
|
||||||
# If the thing we're given is a Check, we don't know the
|
# If the thing we're given is a Check, we don't know the
|
||||||
|
@ -881,6 +908,27 @@ class Enforcer(object):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _map_context_attributes_into_creds(self, context):
|
||||||
|
creds = {}
|
||||||
|
# port public context attributes into the creds dictionary so long as
|
||||||
|
# the attribute isn't callable
|
||||||
|
context_values = context.to_policy_values()
|
||||||
|
for k, v in context_values.items():
|
||||||
|
creds[k] = v
|
||||||
|
|
||||||
|
# NOTE(lbragstad): We unfortunately have to special case this
|
||||||
|
# attribute. Originally when the system scope when into oslo.policy, we
|
||||||
|
# checked for a key called 'system' in creds. The oslo.context library
|
||||||
|
# uses `system_scope` instead, and the compatibility between
|
||||||
|
# oslo.policy and oslo.context was an afterthought. We'll have to
|
||||||
|
# support services who've been setting creds['system'], but we can do
|
||||||
|
# that by making sure we populate it with what's in the context object
|
||||||
|
# if it has a system_scope attribute.
|
||||||
|
if context.system_scope:
|
||||||
|
creds['system'] = context.system_scope
|
||||||
|
|
||||||
|
return creds
|
||||||
|
|
||||||
def register_default(self, default):
|
def register_default(self, default):
|
||||||
"""Registers a RuleDefault.
|
"""Registers a RuleDefault.
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import os
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_context import context
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from oslotest import base as test_base
|
from oslotest import base as test_base
|
||||||
import six
|
import six
|
||||||
|
@ -646,6 +647,89 @@ class EnforcerTest(base.PolicyBaseTestCase):
|
||||||
self.enforcer.authorize, 'test', {},
|
self.enforcer.authorize, 'test', {},
|
||||||
{'roles': ['test']})
|
{'roles': ['test']})
|
||||||
|
|
||||||
|
def test_enforcer_accepts_context_objects(self):
|
||||||
|
rule = policy.RuleDefault(name='fake_rule', check_str='role:test')
|
||||||
|
self.enforcer.register_default(rule)
|
||||||
|
|
||||||
|
request_context = context.RequestContext()
|
||||||
|
target_dict = {}
|
||||||
|
self.enforcer.enforce('fake_rule', target_dict, request_context)
|
||||||
|
|
||||||
|
def test_enforcer_accepts_subclassed_context_objects(self):
|
||||||
|
rule = policy.RuleDefault(name='fake_rule', check_str='role:test')
|
||||||
|
self.enforcer.register_default(rule)
|
||||||
|
|
||||||
|
class SpecializedContext(context.RequestContext):
|
||||||
|
pass
|
||||||
|
|
||||||
|
request_context = SpecializedContext()
|
||||||
|
target_dict = {}
|
||||||
|
self.enforcer.enforce('fake_rule', target_dict, request_context)
|
||||||
|
|
||||||
|
def test_enforcer_rejects_non_context_objects(self):
|
||||||
|
rule = policy.RuleDefault(name='fake_rule', check_str='role:test')
|
||||||
|
self.enforcer.register_default(rule)
|
||||||
|
|
||||||
|
class InvalidContext(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
request_context = InvalidContext()
|
||||||
|
target_dict = {}
|
||||||
|
self.assertRaises(
|
||||||
|
policy.InvalidContextObject, self.enforcer.enforce, 'fake_rule',
|
||||||
|
target_dict, request_context
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch.object(policy.Enforcer, '_map_context_attributes_into_creds')
|
||||||
|
def test_enforcer_call_map_context_attributes(self, map_mock):
|
||||||
|
rule = policy.RuleDefault(name='fake_rule', check_str='role:test')
|
||||||
|
self.enforcer.register_default(rule)
|
||||||
|
|
||||||
|
request_context = context.RequestContext()
|
||||||
|
target_dict = {}
|
||||||
|
self.enforcer.enforce('fake_rule', target_dict, request_context)
|
||||||
|
map_mock.assert_called_once_with(request_context)
|
||||||
|
|
||||||
|
def test_enforcer_consolidates_context_attributes_with_creds(self):
|
||||||
|
request_context = context.RequestContext()
|
||||||
|
expected_creds = request_context.to_policy_values()
|
||||||
|
|
||||||
|
creds = self.enforcer._map_context_attributes_into_creds(
|
||||||
|
request_context
|
||||||
|
)
|
||||||
|
|
||||||
|
# We don't use self.assertDictEqual here because to_policy_values
|
||||||
|
# actaully returns a non-dict object that just behaves like a
|
||||||
|
# dictionary, but does some special handling when people access
|
||||||
|
# deprecated policy values.
|
||||||
|
for k, v in expected_creds.items():
|
||||||
|
self.assertEqual(expected_creds[k], creds[k])
|
||||||
|
|
||||||
|
def test_map_context_attributes_populated_system(self):
|
||||||
|
request_context = context.RequestContext(system_scope='all')
|
||||||
|
expected_creds = request_context.to_policy_values()
|
||||||
|
expected_creds['system'] = 'all'
|
||||||
|
|
||||||
|
creds = self.enforcer._map_context_attributes_into_creds(
|
||||||
|
request_context
|
||||||
|
)
|
||||||
|
|
||||||
|
# We don't use self.assertDictEqual here because to_policy_values
|
||||||
|
# actaully returns a non-dict object that just behaves like a
|
||||||
|
# dictionary, but does some special handling when people access
|
||||||
|
# deprecated policy values.
|
||||||
|
for k, v in expected_creds.items():
|
||||||
|
self.assertEqual(expected_creds[k], creds[k])
|
||||||
|
|
||||||
|
def test_enforcer_accepts_policy_values_from_context(self):
|
||||||
|
rule = policy.RuleDefault(name='fake_rule', check_str='role:test')
|
||||||
|
self.enforcer.register_default(rule)
|
||||||
|
|
||||||
|
request_context = context.RequestContext()
|
||||||
|
policy_values = request_context.to_policy_values()
|
||||||
|
target_dict = {}
|
||||||
|
self.enforcer.enforce('fake_rule', target_dict, policy_values)
|
||||||
|
|
||||||
|
|
||||||
class EnforcerNoPolicyFileTest(base.PolicyBaseTestCase):
|
class EnforcerNoPolicyFileTest(base.PolicyBaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
[`bug 1779172 <https://bugs.launchpad.net/keystone/+bug/1779172>`_]
|
||||||
|
The ``enforce()`` method now supports the ability to parse ``oslo.context``
|
||||||
|
objects if passed into ``enforce()`` as ``creds``. This provides more
|
||||||
|
consistent policy enforcement for service developers by ensuring the
|
||||||
|
attributes provided in policy enforcement are standardized. In this case
|
||||||
|
they are being standardized through the
|
||||||
|
``oslo_context.context.RequestContext.to_policy_values()`` method.
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
[`bug 1779172 <https://bugs.launchpad.net/keystone/+bug/1779172>`_]
|
||||||
|
The ``enforce()`` method now supports the ability to parse ``oslo.context``
|
||||||
|
objects if passed into ``enforce()`` as ``creds``. This provides more
|
||||||
|
consistent policy enforcement for service developers by ensuring the
|
||||||
|
attributes provided in policy enforcement are standardized. In this case
|
||||||
|
they are being standardized through the
|
||||||
|
``oslo_context.context.RequestContext.to_policy_values()`` method.
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
requests>=2.14.2 # Apache-2.0
|
requests>=2.14.2 # Apache-2.0
|
||||||
oslo.config>=5.2.0 # Apache-2.0
|
oslo.config>=5.2.0 # Apache-2.0
|
||||||
|
oslo.context>=2.21.0 # Apache-2.0
|
||||||
oslo.i18n>=3.15.3 # Apache-2.0
|
oslo.i18n>=3.15.3 # Apache-2.0
|
||||||
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
|
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
|
||||||
PyYAML>=3.12 # MIT
|
PyYAML>=3.12 # MIT
|
||||||
|
|
|
@ -5,6 +5,7 @@ hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
|
||||||
oslotest>=3.2.0 # Apache-2.0
|
oslotest>=3.2.0 # Apache-2.0
|
||||||
requests-mock>=1.1.0 # Apache-2.0
|
requests-mock>=1.1.0 # Apache-2.0
|
||||||
stestr>=2.0.0 # Apache-2.0
|
stestr>=2.0.0 # Apache-2.0
|
||||||
|
oslo.context>=2.21.0 # Apache-2.0
|
||||||
|
|
||||||
# computes code coverage percentages
|
# computes code coverage percentages
|
||||||
coverage!=4.4,>=4.0 # Apache-2.0
|
coverage!=4.4,>=4.0 # Apache-2.0
|
||||||
|
|
Loading…
Reference in New Issue