Add authZ through incorporation of policy checks.
Adds the policy openstack-common module and implements policy checks for the v2 API. Note that this cut only addresses whole objects (i.e., a subnet or a network or a port), not specific fields within objects. (This means that attributes are not filtered out based on policies.) Implements blueprint authorization-support-for-quantum. Change-Id: I1b52b1791a1f14f0af6508a63a40a38e440f15fe
This commit is contained in:
parent
2d2f656689
commit
b3a970a5e4
19
etc/policy.json
Normal file
19
etc/policy.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"admin_or_owner": [["role:admin"], ["tenant_id:%(tenant_id)s"]],
|
||||||
|
"default": [["rule:admin_or_owner"]],
|
||||||
|
|
||||||
|
"create_subnet": [],
|
||||||
|
"get_subnet": [["rule:admin_or_owner"]],
|
||||||
|
"update_subnet": [["rule:admin_or_owner"]],
|
||||||
|
"delete_subnet": [["rule:admin_or_owner"]],
|
||||||
|
|
||||||
|
"create_network": [],
|
||||||
|
"get_network": [["rule:admin_or_owner"]],
|
||||||
|
"update_network": [["rule:admin_or_owner"]],
|
||||||
|
"delete_network": [["rule:admin_or_owner"]],
|
||||||
|
|
||||||
|
"create_port": [],
|
||||||
|
"get_port": [["rule:admin_or_owner"]],
|
||||||
|
"update_port": [["rule:admin_or_owner"]],
|
||||||
|
"delete_port": [["rule:admin_or_owner"]]
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
|
|
||||||
# The list of modules to copy from openstack-common
|
# The list of modules to copy from openstack-common
|
||||||
modules=cfg,exception,importutils,iniparser,jsonutils,setup
|
modules=cfg,exception,importutils,iniparser,jsonutils,policy,setup
|
||||||
|
|
||||||
# The base module to hold the copy of openstack.common
|
# The base module to hold the copy of openstack.common
|
||||||
base=quantum
|
base=quantum
|
||||||
|
@ -17,10 +17,11 @@ import logging
|
|||||||
|
|
||||||
import webob.exc
|
import webob.exc
|
||||||
|
|
||||||
from quantum.common import exceptions
|
|
||||||
from quantum.api.v2 import resource as wsgi_resource
|
from quantum.api.v2 import resource as wsgi_resource
|
||||||
from quantum.common import utils
|
|
||||||
from quantum.api.v2 import views
|
from quantum.api.v2 import views
|
||||||
|
from quantum.common import exceptions
|
||||||
|
from quantum.common import utils
|
||||||
|
from quantum import policy
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0'
|
XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0'
|
||||||
@ -100,7 +101,7 @@ class Controller(object):
|
|||||||
self._attr_info = attr_info
|
self._attr_info = attr_info
|
||||||
self._view = getattr(views, self._resource)
|
self._view = getattr(views, self._resource)
|
||||||
|
|
||||||
def _items(self, request):
|
def _items(self, request, do_authz=False):
|
||||||
"""Retrieves and formats a list of elements of the requested entity"""
|
"""Retrieves and formats a list of elements of the requested entity"""
|
||||||
kwargs = {'filters': filters(request),
|
kwargs = {'filters': filters(request),
|
||||||
'verbose': verbose(request),
|
'verbose': verbose(request),
|
||||||
@ -108,47 +109,100 @@ class Controller(object):
|
|||||||
|
|
||||||
obj_getter = getattr(self._plugin, "get_%s" % self._collection)
|
obj_getter = getattr(self._plugin, "get_%s" % self._collection)
|
||||||
obj_list = obj_getter(request.context, **kwargs)
|
obj_list = obj_getter(request.context, **kwargs)
|
||||||
|
|
||||||
|
# Check authz
|
||||||
|
if do_authz:
|
||||||
|
# Omit items from list that should not be visible
|
||||||
|
obj_list = [obj for obj in obj_list
|
||||||
|
if policy.check(request.context,
|
||||||
|
"get_%s" % self._resource,
|
||||||
|
obj)]
|
||||||
|
|
||||||
return {self._collection: [self._view(obj) for obj in obj_list]}
|
return {self._collection: [self._view(obj) for obj in obj_list]}
|
||||||
|
|
||||||
def _item(self, request, id):
|
def _item(self, request, id, do_authz=False):
|
||||||
"""Retrieves and formats a single element of the requested entity"""
|
"""Retrieves and formats a single element of the requested entity"""
|
||||||
kwargs = {'verbose': verbose(request),
|
kwargs = {'verbose': verbose(request),
|
||||||
'fields': fields(request)}
|
'fields': fields(request)}
|
||||||
obj_getter = getattr(self._plugin,
|
action = "get_%s" % self._resource
|
||||||
"get_%s" % self._resource)
|
obj_getter = getattr(self._plugin, action)
|
||||||
obj = obj_getter(request.context, id, **kwargs)
|
obj = obj_getter(request.context, id, **kwargs)
|
||||||
|
|
||||||
|
# Check authz
|
||||||
|
if do_authz:
|
||||||
|
policy.enforce(request.context, action, obj)
|
||||||
|
|
||||||
return {self._resource: self._view(obj)}
|
return {self._resource: self._view(obj)}
|
||||||
|
|
||||||
def index(self, request):
|
def index(self, request):
|
||||||
"""Returns a list of the requested entity"""
|
"""Returns a list of the requested entity"""
|
||||||
return self._items(request)
|
return self._items(request, True)
|
||||||
|
|
||||||
def show(self, request, id):
|
def show(self, request, id):
|
||||||
"""Returns detailed information about the requested entity"""
|
"""Returns detailed information about the requested entity"""
|
||||||
return self._item(request, id)
|
try:
|
||||||
|
return self._item(request, id, True)
|
||||||
|
except exceptions.PolicyNotAuthorized:
|
||||||
|
# To avoid giving away information, pretend that it
|
||||||
|
# doesn't exist
|
||||||
|
raise webob.exc.HTTPNotFound()
|
||||||
|
|
||||||
def create(self, request, body=None):
|
def create(self, request, body=None):
|
||||||
"""Creates a new instance of the requested entity"""
|
"""Creates a new instance of the requested entity"""
|
||||||
|
|
||||||
body = self._prepare_request_body(request.context, body, True,
|
body = self._prepare_request_body(request.context, body, True,
|
||||||
allow_bulk=True)
|
allow_bulk=True)
|
||||||
obj_creator = getattr(self._plugin,
|
|
||||||
"create_%s" % self._resource)
|
action = "create_%s" % self._resource
|
||||||
|
|
||||||
|
# Check authz
|
||||||
|
try:
|
||||||
|
if self._collection in body:
|
||||||
|
# Have to account for bulk create
|
||||||
|
for item in body[self._collection]:
|
||||||
|
policy.enforce(request.context, action,
|
||||||
|
item[self._resource])
|
||||||
|
else:
|
||||||
|
policy.enforce(request.context, action, body[self._resource])
|
||||||
|
except exceptions.PolicyNotAuthorized:
|
||||||
|
raise webob.exc.HTTPForbidden()
|
||||||
|
|
||||||
|
obj_creator = getattr(self._plugin, action)
|
||||||
kwargs = {self._resource: body}
|
kwargs = {self._resource: body}
|
||||||
obj = obj_creator(request.context, **kwargs)
|
obj = obj_creator(request.context, **kwargs)
|
||||||
return {self._resource: self._view(obj)}
|
return {self._resource: self._view(obj)}
|
||||||
|
|
||||||
def delete(self, request, id):
|
def delete(self, request, id):
|
||||||
"""Deletes the specified entity"""
|
"""Deletes the specified entity"""
|
||||||
obj_deleter = getattr(self._plugin,
|
action = "delete_%s" % self._resource
|
||||||
"delete_%s" % self._resource)
|
|
||||||
|
# Check authz
|
||||||
|
obj = self._item(request, id)
|
||||||
|
try:
|
||||||
|
policy.enforce(request.context, action, obj)
|
||||||
|
except exceptions.PolicyNotAuthorized:
|
||||||
|
# To avoid giving away information, pretend that it
|
||||||
|
# doesn't exist
|
||||||
|
raise webob.exc.HTTPNotFound()
|
||||||
|
|
||||||
|
obj_deleter = getattr(self._plugin, action)
|
||||||
obj_deleter(request.context, id)
|
obj_deleter(request.context, id)
|
||||||
|
|
||||||
def update(self, request, id, body=None):
|
def update(self, request, id, body=None):
|
||||||
"""Updates the specified entity's attributes"""
|
"""Updates the specified entity's attributes"""
|
||||||
body = self._prepare_request_body(request.context, body, False)
|
body = self._prepare_request_body(request.context, body, False)
|
||||||
obj_updater = getattr(self._plugin,
|
action = "update_%s" % self._resource
|
||||||
"update_%s" % self._resource)
|
|
||||||
|
# Check authz
|
||||||
|
orig_obj = self._item(request, id)
|
||||||
|
try:
|
||||||
|
policy.enforce(request.context, action, orig_obj)
|
||||||
|
except exceptions.PolicyNotAuthorized:
|
||||||
|
# To avoid giving away information, pretend that it
|
||||||
|
# doesn't exist
|
||||||
|
raise webob.exc.HTTPNotFound()
|
||||||
|
|
||||||
|
obj_updater = getattr(self._plugin, action)
|
||||||
kwargs = {self._resource: body}
|
kwargs = {self._resource: body}
|
||||||
obj = obj_updater(request.context, id, **kwargs)
|
obj = obj_updater(request.context, id, **kwargs)
|
||||||
return {self._resource: self._view(obj)}
|
return {self._resource: self._view(obj)}
|
||||||
|
@ -46,6 +46,10 @@ class AdminRequired(NotAuthorized):
|
|||||||
message = _("User does not have admin privileges: %(reason)s")
|
message = _("User does not have admin privileges: %(reason)s")
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyNotAuthorized(NotAuthorized):
|
||||||
|
message = _("Policy doesn't allow %(action)s to be performed.")
|
||||||
|
|
||||||
|
|
||||||
class ClassNotFound(NotFound):
|
class ClassNotFound(NotFound):
|
||||||
message = _("Class %(class_name)s could not be found")
|
message = _("Class %(class_name)s could not be found")
|
||||||
|
|
||||||
@ -63,6 +67,10 @@ class PortNotFound(NotFound):
|
|||||||
"on network %(net_id)s")
|
"on network %(net_id)s")
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyNotFound(NotFound):
|
||||||
|
message = _("Policy configuration policy.json could not be found")
|
||||||
|
|
||||||
|
|
||||||
class StateInvalid(QuantumException):
|
class StateInvalid(QuantumException):
|
||||||
message = _("Unsupported port state: %(port_state)s")
|
message = _("Unsupported port state: %(port_state)s")
|
||||||
|
|
||||||
|
238
quantum/openstack/common/policy.py
Normal file
238
quantum/openstack/common/policy.py
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (c) 2011 OpenStack, LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""Common Policy Engine Implementation"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
_BRAIN = None
|
||||||
|
|
||||||
|
|
||||||
|
def set_brain(brain):
|
||||||
|
"""Set the brain used by enforce().
|
||||||
|
|
||||||
|
Defaults use Brain() if not set.
|
||||||
|
|
||||||
|
"""
|
||||||
|
global _BRAIN
|
||||||
|
_BRAIN = brain
|
||||||
|
|
||||||
|
|
||||||
|
def reset():
|
||||||
|
"""Clear the brain used by enforce()."""
|
||||||
|
global _BRAIN
|
||||||
|
_BRAIN = None
|
||||||
|
|
||||||
|
|
||||||
|
def enforce(match_list, target_dict, credentials_dict, exc=None,
|
||||||
|
*args, **kwargs):
|
||||||
|
"""Enforces authorization of some rules against credentials.
|
||||||
|
|
||||||
|
:param match_list: nested tuples of data to match against
|
||||||
|
|
||||||
|
The basic brain supports three types of match lists:
|
||||||
|
|
||||||
|
1) rules
|
||||||
|
|
||||||
|
looks like: ``('rule:compute:get_instance',)``
|
||||||
|
|
||||||
|
Retrieves the named rule from the rules dict and recursively
|
||||||
|
checks against the contents of the rule.
|
||||||
|
|
||||||
|
2) roles
|
||||||
|
|
||||||
|
looks like: ``('role:compute:admin',)``
|
||||||
|
|
||||||
|
Matches if the specified role is in credentials_dict['roles'].
|
||||||
|
|
||||||
|
3) generic
|
||||||
|
|
||||||
|
looks like: ``('tenant_id:%(tenant_id)s',)``
|
||||||
|
|
||||||
|
Substitutes values from the target dict into the match using
|
||||||
|
the % operator and matches them against the creds dict.
|
||||||
|
|
||||||
|
Combining rules:
|
||||||
|
|
||||||
|
The brain returns True if any of the outer tuple of rules
|
||||||
|
match and also True if all of the inner tuples match. You
|
||||||
|
can use this to perform simple boolean logic. For
|
||||||
|
example, the following rule would return True if the creds
|
||||||
|
contain the role 'admin' OR the if the tenant_id matches
|
||||||
|
the target dict AND the the creds contains the role
|
||||||
|
'compute_sysadmin':
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
"rule:combined": (
|
||||||
|
'role:admin',
|
||||||
|
('tenant_id:%(tenant_id)s', 'role:compute_sysadmin')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that rule and role are reserved words in the credentials match, so
|
||||||
|
you can't match against properties with those names. Custom brains may
|
||||||
|
also add new reserved words. For example, the HttpBrain adds http as a
|
||||||
|
reserved word.
|
||||||
|
|
||||||
|
:param target_dict: dict of object properties
|
||||||
|
|
||||||
|
Target dicts contain as much information as we can about the object being
|
||||||
|
operated on.
|
||||||
|
|
||||||
|
:param credentials_dict: dict of actor properties
|
||||||
|
|
||||||
|
Credentials dicts contain as much information as we can about the user
|
||||||
|
performing the action.
|
||||||
|
|
||||||
|
:param exc: exception to raise
|
||||||
|
|
||||||
|
Class of the exception to raise if the check fails. Any remaining
|
||||||
|
arguments passed to enforce() (both positional and keyword arguments)
|
||||||
|
will be passed to the exception class. If exc is not provided, returns
|
||||||
|
False.
|
||||||
|
|
||||||
|
:return: True if the policy allows the action
|
||||||
|
:return: False if the policy does not allow the action and exc is not set
|
||||||
|
"""
|
||||||
|
global _BRAIN
|
||||||
|
if not _BRAIN:
|
||||||
|
_BRAIN = Brain()
|
||||||
|
if not _BRAIN.check(match_list, target_dict, credentials_dict):
|
||||||
|
if exc:
|
||||||
|
raise exc(*args, **kwargs)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Brain(object):
|
||||||
|
"""Implements policy checking."""
|
||||||
|
@classmethod
|
||||||
|
def load_json(cls, data, default_rule=None):
|
||||||
|
"""Init a brain using json instead of a rules dictionary."""
|
||||||
|
rules_dict = json.loads(data)
|
||||||
|
return cls(rules=rules_dict, default_rule=default_rule)
|
||||||
|
|
||||||
|
def __init__(self, rules=None, default_rule=None):
|
||||||
|
self.rules = rules or {}
|
||||||
|
self.default_rule = default_rule
|
||||||
|
|
||||||
|
def add_rule(self, key, match):
|
||||||
|
self.rules[key] = match
|
||||||
|
|
||||||
|
def _check(self, match, target_dict, cred_dict):
|
||||||
|
try:
|
||||||
|
match_kind, match_value = match.split(':', 1)
|
||||||
|
except Exception:
|
||||||
|
LOG.exception(_("Failed to understand rule %(match)r") % locals())
|
||||||
|
# If the rule is invalid, fail closed
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
f = getattr(self, '_check_%s' % match_kind)
|
||||||
|
except AttributeError:
|
||||||
|
if not self._check_generic(match, target_dict, cred_dict):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if not f(match_value, target_dict, cred_dict):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check(self, match_list, target_dict, cred_dict):
|
||||||
|
"""Checks authorization of some rules against credentials.
|
||||||
|
|
||||||
|
Detailed description of the check with examples in policy.enforce().
|
||||||
|
|
||||||
|
:param match_list: nested tuples of data to match against
|
||||||
|
:param target_dict: dict of object properties
|
||||||
|
:param credentials_dict: dict of actor properties
|
||||||
|
|
||||||
|
:returns: True if the check passes
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not match_list:
|
||||||
|
return True
|
||||||
|
for and_list in match_list:
|
||||||
|
if isinstance(and_list, basestring):
|
||||||
|
and_list = (and_list,)
|
||||||
|
if all([self._check(item, target_dict, cred_dict)
|
||||||
|
for item in and_list]):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _check_rule(self, match, target_dict, cred_dict):
|
||||||
|
"""Recursively checks credentials based on the brains rules."""
|
||||||
|
try:
|
||||||
|
new_match_list = self.rules[match]
|
||||||
|
except KeyError:
|
||||||
|
if self.default_rule and match != self.default_rule:
|
||||||
|
new_match_list = ('rule:%s' % self.default_rule,)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.check(new_match_list, target_dict, cred_dict)
|
||||||
|
|
||||||
|
def _check_role(self, match, target_dict, cred_dict):
|
||||||
|
"""Check that there is a matching role in the cred dict."""
|
||||||
|
return match.lower() in [x.lower() for x in cred_dict['roles']]
|
||||||
|
|
||||||
|
def _check_generic(self, match, target_dict, cred_dict):
|
||||||
|
"""Check an individual match.
|
||||||
|
|
||||||
|
Matches look like:
|
||||||
|
|
||||||
|
tenant:%(tenant_id)s
|
||||||
|
role:compute:admin
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO(termie): do dict inspection via dot syntax
|
||||||
|
match = match % target_dict
|
||||||
|
key, value = match.split(':', 1)
|
||||||
|
if key in cred_dict:
|
||||||
|
return value == cred_dict[key]
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class HttpBrain(Brain):
|
||||||
|
"""A brain that can check external urls for policy.
|
||||||
|
|
||||||
|
Posts json blobs for target and credentials.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _check_http(self, match, target_dict, cred_dict):
|
||||||
|
"""Check http: rules by calling to a remote server.
|
||||||
|
|
||||||
|
This example implementation simply verifies that the response is
|
||||||
|
exactly 'True'. A custom brain using response codes could easily
|
||||||
|
be implemented.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = match % target_dict
|
||||||
|
data = {'target': json.dumps(target_dict),
|
||||||
|
'credentials': json.dumps(cred_dict)}
|
||||||
|
post_data = urllib.urlencode(data)
|
||||||
|
f = urllib2.urlopen(url, post_data)
|
||||||
|
return f.read() == "True"
|
93
quantum/policy.py
Normal file
93
quantum/policy.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (c) 2012 OpenStack, LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Policy engine for quantum. Largely copied from nova.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from quantum.common import config
|
||||||
|
from quantum.common import exceptions
|
||||||
|
from quantum.openstack.common import policy
|
||||||
|
|
||||||
|
|
||||||
|
_POLICY_PATH = None
|
||||||
|
|
||||||
|
|
||||||
|
def reset():
|
||||||
|
global _POLICY_PATH
|
||||||
|
_POLICY_PATH = None
|
||||||
|
policy.reset()
|
||||||
|
|
||||||
|
|
||||||
|
def init():
|
||||||
|
global _POLICY_PATH
|
||||||
|
if not _POLICY_PATH:
|
||||||
|
_POLICY_PATH = config.find_config_file({}, [], 'policy.json')
|
||||||
|
if not _POLICY_PATH:
|
||||||
|
raise exceptions.PolicyNotFound(path=FLAGS.policy_file)
|
||||||
|
with open(_POLICY_PATH) as f:
|
||||||
|
_set_brain(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
def _set_brain(data):
|
||||||
|
default_rule = 'default'
|
||||||
|
policy.set_brain(policy.HttpBrain.load_json(data, default_rule))
|
||||||
|
|
||||||
|
|
||||||
|
def check(context, action, target):
|
||||||
|
"""Verifies that the action is valid on the target in this context.
|
||||||
|
|
||||||
|
:param context: quantum context
|
||||||
|
:param action: string representing the action to be checked
|
||||||
|
this should be colon separated for clarity.
|
||||||
|
:param object: dictionary representing the object of the action
|
||||||
|
for object creation this should be a dictionary representing the
|
||||||
|
location of the object e.g. ``{'project_id': context.project_id}``
|
||||||
|
|
||||||
|
:return: Returns True if access is permitted else False.
|
||||||
|
"""
|
||||||
|
|
||||||
|
init()
|
||||||
|
|
||||||
|
match_list = ('rule:%s' % action,)
|
||||||
|
credentials = context.to_dict()
|
||||||
|
|
||||||
|
return policy.enforce(match_list, target, credentials)
|
||||||
|
|
||||||
|
|
||||||
|
def enforce(context, action, target):
|
||||||
|
"""Verifies that the action is valid on the target in this context.
|
||||||
|
|
||||||
|
:param context: quantum context
|
||||||
|
:param action: string representing the action to be checked
|
||||||
|
this should be colon separated for clarity.
|
||||||
|
:param object: dictionary representing the object of the action
|
||||||
|
for object creation this should be a dictionary representing the
|
||||||
|
location of the object e.g. ``{'project_id': context.project_id}``
|
||||||
|
|
||||||
|
:raises quantum.exceptions.PolicyNotAllowed: if verification fails.
|
||||||
|
"""
|
||||||
|
|
||||||
|
init()
|
||||||
|
|
||||||
|
match_list = ('rule:%s' % action,)
|
||||||
|
credentials = context.to_dict()
|
||||||
|
|
||||||
|
policy.enforce(match_list, target, credentials,
|
||||||
|
exceptions.PolicyNotAuthorized, action=action)
|
Loading…
Reference in New Issue
Block a user