RBAC support based on oslo.policy

This commit adds support for RBAC using oslo.policy. This allows Zaqar
for having a fine-grained access control to the resources it exposes.
As of this patch, the implementation allows to have access control in
a per-operation basis rather than specific resources.

Co-Authored-by: Thomas Herve <therve@redhat.com>
Co-Authored-by: Flavio Percoco <flaper87@gmail.com>

blueprint: fine-grained-permissions

Change-Id: I90374a11815ac2bd9d31768588719d2d4c4e7f5d
This commit is contained in:
Fei Long Wang 2015-05-04 16:17:37 +12:00
parent e061305356
commit d08f4913ca
20 changed files with 273 additions and 6 deletions

View File

@ -37,6 +37,7 @@ ZAQAR_DIR=$DEST/zaqar
ZAQARCLIENT_DIR=$DEST/python-zaqarclient ZAQARCLIENT_DIR=$DEST/python-zaqarclient
ZAQAR_CONF_DIR=/etc/zaqar ZAQAR_CONF_DIR=/etc/zaqar
ZAQAR_CONF=$ZAQAR_CONF_DIR/zaqar.conf ZAQAR_CONF=$ZAQAR_CONF_DIR/zaqar.conf
ZAQAR_POLICY_CONF=$ZAQAR_CONF_DIR/policy.json
ZAQAR_UWSGI_CONF=$ZAQAR_CONF_DIR/uwsgi.conf ZAQAR_UWSGI_CONF=$ZAQAR_CONF_DIR/uwsgi.conf
ZAQAR_API_LOG_DIR=/var/log/zaqar ZAQAR_API_LOG_DIR=/var/log/zaqar
ZAQAR_API_LOG_FILE=$ZAQAR_API_LOG_DIR/queues.log ZAQAR_API_LOG_FILE=$ZAQAR_API_LOG_DIR/queues.log
@ -112,6 +113,10 @@ function configure_zaqar {
[ ! -d $ZAQAR_CONF_DIR ] && sudo mkdir -m 755 -p $ZAQAR_CONF_DIR [ ! -d $ZAQAR_CONF_DIR ] && sudo mkdir -m 755 -p $ZAQAR_CONF_DIR
sudo chown $USER $ZAQAR_CONF_DIR sudo chown $USER $ZAQAR_CONF_DIR
if [[ -f $ZAQAR_DIR/etc/policy.json.sample ]]; then
cp -p $ZAQAR_DIR/etc/policy.json.sample $ZAQAR_POLICY_CONF
fi
[ ! -d $ZAQAR_API_LOG_DIR ] && sudo mkdir -m 755 -p $ZAQAR_API_LOG_DIR [ ! -d $ZAQAR_API_LOG_DIR ] && sudo mkdir -m 755 -p $ZAQAR_API_LOG_DIR
sudo chown $USER $ZAQAR_API_LOG_DIR sudo chown $USER $ZAQAR_API_LOG_DIR

45
etc/policy.json.sample Normal file
View File

@ -0,0 +1,45 @@
{
"context_is_admin": "role:admin",
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
"default": "rule:admin_or_owner",
"queues:get_all": "",
"queues:create": "",
"queues:get": "",
"queues:delete": "",
"queues:update": "",
"queues:stats": "",
"messages:get_all": "",
"messages:create": "",
"messages:get": "",
"messages:delete": "",
"messages:delete_all": "",
"claims:get_all": "",
"claims:create": "",
"claims:get": "",
"claims:delete": "",
"claims:update": "",
"subscription:get_all": "",
"subscription:create": "",
"subscription:get": "",
"subscription:delete": "",
"subscription:update": "",
"pools:get_all": "rule:context_is_admin",
"pools:create": "rule:context_is_admin",
"pools:get": "rule:context_is_admin",
"pools:delete": "rule:context_is_admin",
"pools:update": "rule:context_is_admin",
"flavors:get_all": "",
"flavors:create": "rule:context_is_admin",
"flavors:get": "",
"flavors:delete": "rule:context_is_admin",
"flavors:update": "rule:context_is_admin",
"ping:get": "",
"health:get": "rule:context_is_admin"
}

View File

@ -21,6 +21,7 @@ oslo.i18n>=1.5.0 # Apache-2.0
oslo.log>=1.8.0 # Apache-2.0 oslo.log>=1.8.0 # Apache-2.0
oslo.serialization>=1.4.0 # Apache-2.0 oslo.serialization>=1.4.0 # Apache-2.0
oslo.utils>=2.0.0 # Apache-2.0 oslo.utils>=2.0.0 # Apache-2.0
oslo.policy>=0.5.0 # Apache-2.0
SQLAlchemy<1.1.0,>=0.9.9 SQLAlchemy<1.1.0,>=0.9.9
enum34;python_version=='2.7' or python_version=='2.6' enum34;python_version=='2.7' or python_version=='2.6'
trollius>=1.0 trollius>=1.0

View File

@ -200,7 +200,19 @@ def inject_context(req, resp, params):
""" """
client_id = req.get_header('Client-ID') client_id = req.get_header('Client-ID')
project_id = params.get('project_id', None) project_id = params.get('project_id', None)
request_id = req.headers.get('X-Openstack-Request-ID'),
auth_token = req.headers.get('X-AUTH-TOKEN')
user = req.headers.get('X-USER-ID')
tenant = req.headers.get('X-TENANT-ID')
roles = req.headers.get('X-ROLES')
roles = roles and roles.split(',') or []
ctxt = context.RequestContext(project_id=project_id, ctxt = context.RequestContext(project_id=project_id,
client_id=client_id) client_id=client_id,
request_id=request_id,
auth_token=auth_token,
user=user,
tenant=tenant,
roles=roles)
req.env['zaqar.context'] = ctxt req.env['zaqar.context'] = ctxt

View File

@ -26,7 +26,7 @@ class RequestContext(context.RequestContext):
auth_token=None, user=None, tenant=None, domain=None, auth_token=None, user=None, tenant=None, domain=None,
user_domain=None, project_domain=None, is_admin=False, user_domain=None, project_domain=None, is_admin=False,
read_only=False, show_deleted=False, request_id=None, read_only=False, show_deleted=False, request_id=None,
instance_uuid=None, **kwargs): instance_uuid=None, roles=None, **kwargs):
super(RequestContext, self).__init__(auth_token=auth_token, super(RequestContext, self).__init__(auth_token=auth_token,
user=user, user=user,
tenant=tenant, tenant=tenant,
@ -39,6 +39,7 @@ class RequestContext(context.RequestContext):
request_id=request_id) request_id=request_id)
self.project_id = project_id self.project_id = project_id
self.client_id = client_id self.client_id = client_id
self.roles = roles
if overwrite or not hasattr(context._request_store, 'context'): if overwrite or not hasattr(context._request_store, 'context'):
self.update_store() self.update_store()
@ -49,6 +50,8 @@ class RequestContext(context.RequestContext):
ctx = super(RequestContext, self).to_dict() ctx = super(RequestContext, self).to_dict()
ctx.update({ ctx.update({
'project_id': self.project_id, 'project_id': self.project_id,
'client_id': self.client_id 'client_id': self.client_id,
'tenant': self.tenant,
'roles': self.roles
}) })
return ctx return ctx

View File

@ -0,0 +1,45 @@
{
"context_is_admin": "role:admin",
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
"default": "rule:admin_or_owner",
"queues:get_all": "",
"queues:create": "",
"queues:get": "",
"queues:delete": "",
"queues:update": "",
"queues:stats": "",
"messages:get_all": "",
"messages:create": "",
"messages:get": "",
"messages:delete": "",
"messages:delete_all": "",
"claims:get_all": "",
"claims:create": "",
"claims:get": "",
"claims:delete": "",
"claims:update": "",
"subscription:get_all": "",
"subscription:create": "",
"subscription:get": "",
"subscription:delete": "",
"subscription:update": "",
"pools:get_all": "rule:context_is_admin",
"pools:create": "rule:context_is_admin",
"pools:get": "rule:context_is_admin",
"pools:delete": "rule:context_is_admin",
"pools:update": "rule:context_is_admin",
"flavors:get_all": "",
"flavors:create": "rule:context_is_admin",
"flavors:get": "",
"flavors:delete": "rule:context_is_admin",
"flavors:update": "rule:context_is_admin",
"ping:get": "",
"health:get": "rule:context_is_admin"
}

View File

@ -0,0 +1,57 @@
# Copyright (c) 2015 Catalyst IT Ltd.
#
# 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.
from collections import namedtuple
from oslo_policy import policy
from zaqar import context
from zaqar.tests import base
from zaqar.transport import acl
from zaqar.transport.wsgi import errors
class TestAcl(base.TestBase):
def setUp(self):
super(TestAcl, self).setUp()
ctx = context.RequestContext()
request_class = namedtuple("Request", ("env",))
self.request = request_class({"zaqar.context": ctx})
def _set_policy(self, json):
acl.setup_policy(self.conf)
rules = policy.Rules.load_json(json)
acl.ENFORCER.set_rules(rules, use_conf=False)
def test_policy_allow(self):
@acl.enforce("queues:get_all")
def test(ign, request):
pass
json = '{"queues:get_all": ""}'
self._set_policy(json)
test(None, self.request)
def test_policy_deny(self):
@acl.enforce("queues:get_all")
def test(ign, request):
pass
json = '{"queues:get_all": "!"}'
self._set_policy(json)
self.assertRaises(errors.HTTPForbidden, test, None, self.request)

View File

@ -53,6 +53,9 @@ class TestBase(testing.TestBase):
self.headers = { self.headers = {
'Client-ID': str(uuid.uuid4()), 'Client-ID': str(uuid.uuid4()),
'X-ROLES': 'admin',
'X-USER-ID': 'a12d157c7d0d41999096639078fd11fc',
'X-TENANT-ID': 'abb69142168841fcaa2785791b92467f',
} }
def tearDown(self): def tearDown(self):

View File

@ -52,10 +52,10 @@ class TestMessagesMongoDB(base.V2Base):
self.assertEqual(self.srmock.status, falcon.HTTP_201) self.assertEqual(self.srmock.status, falcon.HTTP_201)
self.project_id = '7e55e1a7e' self.project_id = '7e55e1a7e'
self.headers = { self.headers.update({
'Client-ID': str(uuid.uuid4()), 'Client-ID': str(uuid.uuid4()),
'X-Project-ID': self.project_id 'X-Project-ID': self.project_id
} })
# TODO(kgriffs): Add support in self.simulate_* for a "base path" # TODO(kgriffs): Add support in self.simulate_* for a "base path"
# so that we don't have to concatenate against self.url_prefix # so that we don't have to concatenate against self.url_prefix

44
zaqar/transport/acl.py Normal file
View File

@ -0,0 +1,44 @@
# Copyright (c) 2015 Catalyst IT Ltd.
#
# 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 enforcer of Zaqar"""
import functools
from oslo_policy import policy
ENFORCER = None
def setup_policy(conf):
global ENFORCER
ENFORCER = policy.Enforcer(conf)
def enforce(rule):
# Late import to prevent cycles
from zaqar.transport.wsgi import errors
def decorator(func):
@functools.wraps(func)
def handler(*args, **kwargs):
ctx = args[1].env['zaqar.context']
ENFORCER.enforce(rule, {}, ctx.to_dict(), do_raise=True,
exc=errors.HTTPForbidden)
return func(*args, **kwargs)
return handler
return decorator

View File

@ -25,6 +25,7 @@ from zaqar.common import decorators
from zaqar.common.transport.wsgi import helpers from zaqar.common.transport.wsgi import helpers
from zaqar.i18n import _ from zaqar.i18n import _
from zaqar import transport from zaqar import transport
from zaqar.transport import acl
from zaqar.transport import auth from zaqar.transport import auth
from zaqar.transport import validation from zaqar.transport import validation
from zaqar.transport.wsgi import v1_0 from zaqar.transport.wsgi import v1_0
@ -117,6 +118,8 @@ class Driver(transport.DriverBase):
self.app = auth.SignedHeadersAuth(self.app, auth_app) self.app = auth.SignedHeadersAuth(self.app, auth_app)
acl.setup_policy(self._conf)
def _error_handler(self, exc, request, response, params): def _error_handler(self, exc, request, response, params):
if isinstance(exc, falcon.HTTPError): if isinstance(exc, falcon.HTTPError):
raise exc raise exc

View File

@ -55,3 +55,13 @@ class HTTPDocumentTypeNotSupported(HTTPBadRequestBody):
def __init__(self): def __init__(self):
super(HTTPDocumentTypeNotSupported, self).__init__(self.DESCRIPTION) super(HTTPDocumentTypeNotSupported, self).__init__(self.DESCRIPTION)
class HTTPForbidden(falcon.HTTPForbidden):
"""Wraps falcon.HTTPForbidden with a contextual title."""
TITLE = _(u'Not authorized')
DESCRIPTION = _(u'You are not authorized to complete this action.')
def __init__(self):
super(HTTPForbidden, self).__init__(self.TITLE, self.DESCRIPTION)

View File

@ -19,6 +19,7 @@ import six
from zaqar.i18n import _ from zaqar.i18n import _
from zaqar.storage import errors as storage_errors from zaqar.storage import errors as storage_errors
from zaqar.transport import acl
from zaqar.transport import utils from zaqar.transport import utils
from zaqar.transport import validation from zaqar.transport import validation
from zaqar.transport.wsgi import errors as wsgi_errors from zaqar.transport.wsgi import errors as wsgi_errors
@ -53,6 +54,7 @@ class CollectionResource(object):
'grace': default_grace_ttl, 'grace': default_grace_ttl,
} }
@acl.enforce("claims:create")
def on_post(self, req, resp, project_id, queue_name): def on_post(self, req, resp, project_id, queue_name):
LOG.debug(u'Claims collection POST - queue: %(queue)s, ' LOG.debug(u'Claims collection POST - queue: %(queue)s, '
u'project: %(project)s', u'project: %(project)s',
@ -125,6 +127,7 @@ class ItemResource(object):
('grace', int, default_grace_ttl), ('grace', int, default_grace_ttl),
) )
@acl.enforce("claims:get")
def on_get(self, req, resp, project_id, queue_name, claim_id): def on_get(self, req, resp, project_id, queue_name, claim_id):
LOG.debug(u'Claim item GET - claim: %(claim_id)s, ' LOG.debug(u'Claim item GET - claim: %(claim_id)s, '
u'queue: %(queue_name)s, project: %(project_id)s', u'queue: %(queue_name)s, project: %(project_id)s',
@ -162,6 +165,7 @@ class ItemResource(object):
resp.body = utils.to_json(meta) resp.body = utils.to_json(meta)
# status defaults to 200 # status defaults to 200
@acl.enforce("claims:update")
def on_patch(self, req, resp, project_id, queue_name, claim_id): def on_patch(self, req, resp, project_id, queue_name, claim_id):
LOG.debug(u'Claim Item PATCH - claim: %(claim_id)s, ' LOG.debug(u'Claim Item PATCH - claim: %(claim_id)s, '
u'queue: %(queue_name)s, project:%(project_id)s' % u'queue: %(queue_name)s, project:%(project_id)s' %
@ -196,6 +200,7 @@ class ItemResource(object):
description = _(u'Claim could not be updated.') description = _(u'Claim could not be updated.')
raise wsgi_errors.HTTPServiceUnavailable(description) raise wsgi_errors.HTTPServiceUnavailable(description)
@acl.enforce("claims:delete")
def on_delete(self, req, resp, project_id, queue_name, claim_id): def on_delete(self, req, resp, project_id, queue_name, claim_id):
LOG.debug(u'Claim item DELETE - claim: %(claim_id)s, ' LOG.debug(u'Claim item DELETE - claim: %(claim_id)s, '
u'queue: %(queue_name)s, project: %(project_id)s' % u'queue: %(queue_name)s, project: %(project_id)s' %

View File

@ -21,6 +21,7 @@ from zaqar.common.api.schemas import flavors as schema
from zaqar.common import utils as common_utils from zaqar.common import utils as common_utils
from zaqar.i18n import _ from zaqar.i18n import _
from zaqar.storage import errors from zaqar.storage import errors
from zaqar.transport import acl
from zaqar.transport import utils as transport_utils from zaqar.transport import utils as transport_utils
from zaqar.transport.wsgi import errors as wsgi_errors from zaqar.transport.wsgi import errors as wsgi_errors
from zaqar.transport.wsgi import utils as wsgi_utils from zaqar.transport.wsgi import utils as wsgi_utils
@ -38,6 +39,7 @@ class Listing(object):
self._ctrl = flavors_controller self._ctrl = flavors_controller
self._pools_ctrl = pools_controller self._pools_ctrl = pools_controller
@acl.enforce("flavors:get_all")
def on_get(self, request, response, project_id): def on_get(self, request, response, project_id):
"""Returns a flavor listing as objects embedded in an object: """Returns a flavor listing as objects embedded in an object:
@ -113,6 +115,7 @@ class Resource(object):
'capabilities': validator_type(schema.patch_capabilities), 'capabilities': validator_type(schema.patch_capabilities),
} }
@acl.enforce("flavors:get")
def on_get(self, request, response, project_id, flavor): def on_get(self, request, response, project_id, flavor):
"""Returns a JSON object for a single flavor entry: """Returns a JSON object for a single flavor entry:
@ -140,6 +143,7 @@ class Resource(object):
response.body = transport_utils.to_json(data) response.body = transport_utils.to_json(data)
@acl.enforce("flavors:create")
def on_put(self, request, response, project_id, flavor): def on_put(self, request, response, project_id, flavor):
"""Registers a new flavor. Expects the following input: """Registers a new flavor. Expects the following input:
@ -170,6 +174,7 @@ class Resource(object):
dict(flavor=flavor, pool=data['pool'])) dict(flavor=flavor, pool=data['pool']))
raise falcon.HTTPBadRequest(_('Unable to create'), description) raise falcon.HTTPBadRequest(_('Unable to create'), description)
@acl.enforce("flavors:delete")
def on_delete(self, request, response, project_id, flavor): def on_delete(self, request, response, project_id, flavor):
"""Deregisters a flavor. """Deregisters a flavor.
@ -180,6 +185,7 @@ class Resource(object):
self._ctrl.delete(flavor, project=project_id) self._ctrl.delete(flavor, project=project_id)
response.status = falcon.HTTP_204 response.status = falcon.HTTP_204
@acl.enforce("flavors:update")
def on_patch(self, request, response, project_id, flavor): def on_patch(self, request, response, project_id, flavor):
"""Allows one to update a flavors's pool and/or capabilities. """Allows one to update a flavors's pool and/or capabilities.

View File

@ -16,6 +16,7 @@
from oslo_log import log as logging from oslo_log import log as logging
from zaqar.i18n import _ from zaqar.i18n import _
from zaqar.transport import acl
from zaqar.transport import utils from zaqar.transport import utils
from zaqar.transport.wsgi import errors as wsgi_errors from zaqar.transport.wsgi import errors as wsgi_errors
@ -29,6 +30,7 @@ class Resource(object):
def __init__(self, driver): def __init__(self, driver):
self._driver = driver self._driver = driver
@acl.enforce("health:get")
def on_get(self, req, resp, **kwargs): def on_get(self, req, resp, **kwargs):
try: try:
resp_dict = self._driver.health() resp_dict = self._driver.health()

View File

@ -20,6 +20,7 @@ import six
from zaqar.common.transport.wsgi import helpers as wsgi_helpers from zaqar.common.transport.wsgi import helpers as wsgi_helpers
from zaqar.i18n import _ from zaqar.i18n import _
from zaqar.storage import errors as storage_errors from zaqar.storage import errors as storage_errors
from zaqar.transport import acl
from zaqar.transport import utils from zaqar.transport import utils
from zaqar.transport import validation from zaqar.transport import validation
from zaqar.transport.wsgi import errors as wsgi_errors from zaqar.transport.wsgi import errors as wsgi_errors
@ -149,6 +150,7 @@ class CollectionResource(object):
# Interface # Interface
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
@acl.enforce("messages:create")
def on_post(self, req, resp, project_id, queue_name): def on_post(self, req, resp, project_id, queue_name):
LOG.debug(u'Messages collection POST - queue: %(queue)s, ' LOG.debug(u'Messages collection POST - queue: %(queue)s, '
u'project: %(project)s', u'project: %(project)s',
@ -213,6 +215,7 @@ class CollectionResource(object):
resp.body = utils.to_json(body) resp.body = utils.to_json(body)
resp.status = falcon.HTTP_201 resp.status = falcon.HTTP_201
@acl.enforce("messages:get_all")
def on_get(self, req, resp, project_id, queue_name): def on_get(self, req, resp, project_id, queue_name):
LOG.debug(u'Messages collection GET - queue: %(queue)s, ' LOG.debug(u'Messages collection GET - queue: %(queue)s, '
u'project: %(project)s', u'project: %(project)s',
@ -237,6 +240,7 @@ class CollectionResource(object):
resp.body = utils.to_json(response) resp.body = utils.to_json(response)
# status defaults to 200 # status defaults to 200
@acl.enforce("messages:delete_all")
def on_delete(self, req, resp, project_id, queue_name): def on_delete(self, req, resp, project_id, queue_name):
LOG.debug(u'Messages collection DELETE - queue: %(queue)s, ' LOG.debug(u'Messages collection DELETE - queue: %(queue)s, '
u'project: %(project)s', u'project: %(project)s',
@ -306,6 +310,7 @@ class ItemResource(object):
def __init__(self, message_controller): def __init__(self, message_controller):
self._message_controller = message_controller self._message_controller = message_controller
@acl.enforce("messages:get")
def on_get(self, req, resp, project_id, queue_name, message_id): def on_get(self, req, resp, project_id, queue_name, message_id):
LOG.debug(u'Messages item GET - message: %(message)s, ' LOG.debug(u'Messages item GET - message: %(message)s, '
u'queue: %(queue)s, project: %(project)s', u'queue: %(queue)s, project: %(project)s',
@ -336,6 +341,7 @@ class ItemResource(object):
resp.body = utils.to_json(message) resp.body = utils.to_json(message)
# status defaults to 200 # status defaults to 200
@acl.enforce("messages:delete")
def on_delete(self, req, resp, project_id, queue_name, message_id): def on_delete(self, req, resp, project_id, queue_name, message_id):
LOG.debug(u'Messages item DELETE - message: %(message)s, ' LOG.debug(u'Messages item DELETE - message: %(message)s, '

View File

@ -14,6 +14,8 @@
import falcon import falcon
from zaqar.transport import acl
class Resource(object): class Resource(object):
@ -22,9 +24,11 @@ class Resource(object):
def __init__(self, driver): def __init__(self, driver):
self._driver = driver self._driver = driver
@acl.enforce("ping:get")
def on_get(self, req, resp, **kwargs): def on_get(self, req, resp, **kwargs):
resp.status = (falcon.HTTP_204 if self._driver.is_alive() resp.status = (falcon.HTTP_204 if self._driver.is_alive()
else falcon.HTTP_503) else falcon.HTTP_503)
@acl.enforce("ping:get")
def on_head(self, req, resp, **kwargs): def on_head(self, req, resp, **kwargs):
resp.status = falcon.HTTP_204 resp.status = falcon.HTTP_204

View File

@ -45,6 +45,7 @@ from zaqar.common import utils as common_utils
from zaqar.i18n import _ from zaqar.i18n import _
from zaqar.storage import errors from zaqar.storage import errors
from zaqar.storage import utils as storage_utils from zaqar.storage import utils as storage_utils
from zaqar.transport import acl
from zaqar.transport import utils as transport_utils from zaqar.transport import utils as transport_utils
from zaqar.transport.wsgi import errors as wsgi_errors from zaqar.transport.wsgi import errors as wsgi_errors
from zaqar.transport.wsgi import utils as wsgi_utils from zaqar.transport.wsgi import utils as wsgi_utils
@ -61,6 +62,7 @@ class Listing(object):
def __init__(self, pools_controller): def __init__(self, pools_controller):
self._ctrl = pools_controller self._ctrl = pools_controller
@acl.enforce("pools:get_all")
def on_get(self, request, response, project_id): def on_get(self, request, response, project_id):
"""Returns a pool listing as objects embedded in an object: """Returns a pool listing as objects embedded in an object:
@ -128,6 +130,7 @@ class Resource(object):
'create': validator_type(schema.create) 'create': validator_type(schema.create)
} }
@acl.enforce("pools:get")
def on_get(self, request, response, project_id, pool): def on_get(self, request, response, project_id, pool):
"""Returns a JSON object for a single pool entry: """Returns a JSON object for a single pool entry:
@ -153,6 +156,7 @@ class Resource(object):
response.body = transport_utils.to_json(data) response.body = transport_utils.to_json(data)
@acl.enforce("pools:create")
def on_put(self, request, response, project_id, pool): def on_put(self, request, response, project_id, pool):
"""Registers a new pool. Expects the following input: """Registers a new pool. Expects the following input:
@ -181,6 +185,7 @@ class Resource(object):
response.status = falcon.HTTP_201 response.status = falcon.HTTP_201
response.location = request.path response.location = request.path
@acl.enforce("pools:delete")
def on_delete(self, request, response, project_id, pool): def on_delete(self, request, response, project_id, pool):
"""Deregisters a pool. """Deregisters a pool.
@ -201,6 +206,7 @@ class Resource(object):
response.status = falcon.HTTP_204 response.status = falcon.HTTP_204
@acl.enforce("pools:update")
def on_patch(self, request, response, project_id, pool): def on_patch(self, request, response, project_id, pool):
"""Allows one to update a pool's weight, uri, and/or options. """Allows one to update a pool's weight, uri, and/or options.

View File

@ -19,12 +19,12 @@ import six
from zaqar.i18n import _ from zaqar.i18n import _
from zaqar.storage import errors as storage_errors from zaqar.storage import errors as storage_errors
from zaqar.transport import acl
from zaqar.transport import utils from zaqar.transport import utils
from zaqar.transport import validation from zaqar.transport import validation
from zaqar.transport.wsgi import errors as wsgi_errors from zaqar.transport.wsgi import errors as wsgi_errors
from zaqar.transport.wsgi import utils as wsgi_utils from zaqar.transport.wsgi import utils as wsgi_utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -37,6 +37,7 @@ class ItemResource(object):
self._queue_controller = queue_controller self._queue_controller = queue_controller
self._message_controller = message_controller self._message_controller = message_controller
@acl.enforce("queues:get")
def on_get(self, req, resp, project_id, queue_name): def on_get(self, req, resp, project_id, queue_name):
LOG.debug(u'Queue metadata GET - queue: %(queue)s, ' LOG.debug(u'Queue metadata GET - queue: %(queue)s, '
u'project: %(project)s', u'project: %(project)s',
@ -58,6 +59,7 @@ class ItemResource(object):
resp.body = utils.to_json(resp_dict) resp.body = utils.to_json(resp_dict)
# status defaults to 200 # status defaults to 200
@acl.enforce("queues:create")
def on_put(self, req, resp, project_id, queue_name): def on_put(self, req, resp, project_id, queue_name):
LOG.debug(u'Queue item PUT - queue: %(queue)s, ' LOG.debug(u'Queue item PUT - queue: %(queue)s, '
u'project: %(project)s', u'project: %(project)s',
@ -93,6 +95,7 @@ class ItemResource(object):
resp.status = falcon.HTTP_201 if created else falcon.HTTP_204 resp.status = falcon.HTTP_201 if created else falcon.HTTP_204
resp.location = req.path resp.location = req.path
@acl.enforce("queues:delete")
def on_delete(self, req, resp, project_id, queue_name): def on_delete(self, req, resp, project_id, queue_name):
LOG.debug(u'Queue item DELETE - queue: %(queue)s, ' LOG.debug(u'Queue item DELETE - queue: %(queue)s, '
u'project: %(project)s', u'project: %(project)s',
@ -116,6 +119,7 @@ class CollectionResource(object):
self._queue_controller = queue_controller self._queue_controller = queue_controller
self._validate = validate self._validate = validate
@acl.enforce("queues:get_all")
def on_get(self, req, resp, project_id): def on_get(self, req, resp, project_id):
LOG.debug(u'Queue collection GET - project: %(project)s', LOG.debug(u'Queue collection GET - project: %(project)s',
{'project': project_id}) {'project': project_id})

View File

@ -19,6 +19,7 @@ import six
from zaqar.i18n import _ from zaqar.i18n import _
from zaqar.storage import errors as storage_errors from zaqar.storage import errors as storage_errors
from zaqar.transport import acl
from zaqar.transport import utils from zaqar.transport import utils
from zaqar.transport import validation from zaqar.transport import validation
from zaqar.transport.wsgi import errors as wsgi_errors from zaqar.transport.wsgi import errors as wsgi_errors
@ -36,6 +37,7 @@ class ItemResource(object):
self._validate = validate self._validate = validate
self._subscription_controller = subscription_controller self._subscription_controller = subscription_controller
@acl.enforce("subscription:get")
def on_get(self, req, resp, project_id, queue_name, subscription_id): def on_get(self, req, resp, project_id, queue_name, subscription_id):
LOG.debug(u'Subscription GET - subscription id: %(subscription_id)s,' LOG.debug(u'Subscription GET - subscription id: %(subscription_id)s,'
u' project: %(project)s, queue: %(queue)s', u' project: %(project)s, queue: %(queue)s',
@ -58,6 +60,7 @@ class ItemResource(object):
resp.body = utils.to_json(resp_dict) resp.body = utils.to_json(resp_dict)
# status defaults to 200 # status defaults to 200
@acl.enforce("subscription:delete")
def on_delete(self, req, resp, project_id, queue_name, subscription_id): def on_delete(self, req, resp, project_id, queue_name, subscription_id):
LOG.debug(u'Subscription DELETE - ' LOG.debug(u'Subscription DELETE - '
u'subscription id: %(subscription_id)s,' u'subscription id: %(subscription_id)s,'
@ -76,6 +79,7 @@ class ItemResource(object):
resp.status = falcon.HTTP_204 resp.status = falcon.HTTP_204
@acl.enforce("subscription:update")
def on_patch(self, req, resp, project_id, queue_name, subscription_id): def on_patch(self, req, resp, project_id, queue_name, subscription_id):
LOG.debug(u'Subscription PATCH - subscription id: %(subscription_id)s,' LOG.debug(u'Subscription PATCH - subscription id: %(subscription_id)s,'
u' project: %(project)s, queue: %(queue)s', u' project: %(project)s, queue: %(queue)s',
@ -117,6 +121,7 @@ class CollectionResource(object):
self._subscription_controller = subscription_controller self._subscription_controller = subscription_controller
self._validate = validate self._validate = validate
@acl.enforce("subscription:get_all")
def on_get(self, req, resp, project_id, queue_name): def on_get(self, req, resp, project_id, queue_name):
LOG.debug(u'Subscription collection GET - project: %(project)s, ' LOG.debug(u'Subscription collection GET - project: %(project)s, '
u'queue: %(queue)s', u'queue: %(queue)s',
@ -162,6 +167,7 @@ class CollectionResource(object):
resp.body = utils.to_json(response_body) resp.body = utils.to_json(response_body)
# status defaults to 200 # status defaults to 200
@acl.enforce("subscription:create")
def on_post(self, req, resp, project_id, queue_name): def on_post(self, req, resp, project_id, queue_name):
LOG.debug(u'Subscription item POST - project: %(project)s, ' LOG.debug(u'Subscription item POST - project: %(project)s, '
u'queue: %(queue)s', u'queue: %(queue)s',