diff --git a/devstack/plugin.sh b/devstack/plugin.sh index f2f3d64fe..0857d4a8b 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -37,6 +37,7 @@ ZAQAR_DIR=$DEST/zaqar ZAQARCLIENT_DIR=$DEST/python-zaqarclient ZAQAR_CONF_DIR=/etc/zaqar ZAQAR_CONF=$ZAQAR_CONF_DIR/zaqar.conf +ZAQAR_POLICY_CONF=$ZAQAR_CONF_DIR/policy.json ZAQAR_UWSGI_CONF=$ZAQAR_CONF_DIR/uwsgi.conf ZAQAR_API_LOG_DIR=/var/log/zaqar 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 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 sudo chown $USER $ZAQAR_API_LOG_DIR diff --git a/etc/policy.json.sample b/etc/policy.json.sample new file mode 100644 index 000000000..96d673376 --- /dev/null +++ b/etc/policy.json.sample @@ -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" +} diff --git a/requirements.txt b/requirements.txt index ad4b25bff..72168b1f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,6 +21,7 @@ oslo.i18n>=1.5.0 # Apache-2.0 oslo.log>=1.8.0 # Apache-2.0 oslo.serialization>=1.4.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 enum34;python_version=='2.7' or python_version=='2.6' trollius>=1.0 diff --git a/zaqar/common/transport/wsgi/helpers.py b/zaqar/common/transport/wsgi/helpers.py index 92236fa04..0069c051e 100644 --- a/zaqar/common/transport/wsgi/helpers.py +++ b/zaqar/common/transport/wsgi/helpers.py @@ -200,7 +200,19 @@ def inject_context(req, resp, params): """ client_id = req.get_header('Client-ID') 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, - 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 diff --git a/zaqar/context.py b/zaqar/context.py index 1c069033b..7d3f400e9 100644 --- a/zaqar/context.py +++ b/zaqar/context.py @@ -26,7 +26,7 @@ class RequestContext(context.RequestContext): auth_token=None, user=None, tenant=None, domain=None, user_domain=None, project_domain=None, is_admin=False, 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, user=user, tenant=tenant, @@ -39,6 +39,7 @@ class RequestContext(context.RequestContext): request_id=request_id) self.project_id = project_id self.client_id = client_id + self.roles = roles if overwrite or not hasattr(context._request_store, 'context'): self.update_store() @@ -49,6 +50,8 @@ class RequestContext(context.RequestContext): ctx = super(RequestContext, self).to_dict() ctx.update({ 'project_id': self.project_id, - 'client_id': self.client_id + 'client_id': self.client_id, + 'tenant': self.tenant, + 'roles': self.roles }) return ctx diff --git a/zaqar/tests/etc/policy.json b/zaqar/tests/etc/policy.json new file mode 100644 index 000000000..96d673376 --- /dev/null +++ b/zaqar/tests/etc/policy.json @@ -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" +} diff --git a/zaqar/tests/unit/transport/test_acl.py b/zaqar/tests/unit/transport/test_acl.py new file mode 100644 index 000000000..470d3ef0e --- /dev/null +++ b/zaqar/tests/unit/transport/test_acl.py @@ -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) diff --git a/zaqar/tests/unit/transport/wsgi/base.py b/zaqar/tests/unit/transport/wsgi/base.py index 82561531e..4b78210e9 100644 --- a/zaqar/tests/unit/transport/wsgi/base.py +++ b/zaqar/tests/unit/transport/wsgi/base.py @@ -53,6 +53,9 @@ class TestBase(testing.TestBase): self.headers = { 'Client-ID': str(uuid.uuid4()), + 'X-ROLES': 'admin', + 'X-USER-ID': 'a12d157c7d0d41999096639078fd11fc', + 'X-TENANT-ID': 'abb69142168841fcaa2785791b92467f', } def tearDown(self): diff --git a/zaqar/tests/unit/transport/wsgi/v2_0/test_messages.py b/zaqar/tests/unit/transport/wsgi/v2_0/test_messages.py index fa872bff2..c667de4bb 100644 --- a/zaqar/tests/unit/transport/wsgi/v2_0/test_messages.py +++ b/zaqar/tests/unit/transport/wsgi/v2_0/test_messages.py @@ -52,10 +52,10 @@ class TestMessagesMongoDB(base.V2Base): self.assertEqual(self.srmock.status, falcon.HTTP_201) self.project_id = '7e55e1a7e' - self.headers = { + self.headers.update({ 'Client-ID': str(uuid.uuid4()), 'X-Project-ID': self.project_id - } + }) # TODO(kgriffs): Add support in self.simulate_* for a "base path" # so that we don't have to concatenate against self.url_prefix diff --git a/zaqar/transport/acl.py b/zaqar/transport/acl.py new file mode 100644 index 000000000..377563d04 --- /dev/null +++ b/zaqar/transport/acl.py @@ -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 diff --git a/zaqar/transport/wsgi/driver.py b/zaqar/transport/wsgi/driver.py index 28f58f145..e1510e50c 100644 --- a/zaqar/transport/wsgi/driver.py +++ b/zaqar/transport/wsgi/driver.py @@ -25,6 +25,7 @@ from zaqar.common import decorators from zaqar.common.transport.wsgi import helpers from zaqar.i18n import _ from zaqar import transport +from zaqar.transport import acl from zaqar.transport import auth from zaqar.transport import validation from zaqar.transport.wsgi import v1_0 @@ -117,6 +118,8 @@ class Driver(transport.DriverBase): self.app = auth.SignedHeadersAuth(self.app, auth_app) + acl.setup_policy(self._conf) + def _error_handler(self, exc, request, response, params): if isinstance(exc, falcon.HTTPError): raise exc diff --git a/zaqar/transport/wsgi/errors.py b/zaqar/transport/wsgi/errors.py index d92c76de9..52ffb06f5 100644 --- a/zaqar/transport/wsgi/errors.py +++ b/zaqar/transport/wsgi/errors.py @@ -55,3 +55,13 @@ class HTTPDocumentTypeNotSupported(HTTPBadRequestBody): def __init__(self): 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) diff --git a/zaqar/transport/wsgi/v2_0/claims.py b/zaqar/transport/wsgi/v2_0/claims.py index 3065c86f3..5987f7271 100644 --- a/zaqar/transport/wsgi/v2_0/claims.py +++ b/zaqar/transport/wsgi/v2_0/claims.py @@ -19,6 +19,7 @@ import six from zaqar.i18n import _ from zaqar.storage import errors as storage_errors +from zaqar.transport import acl from zaqar.transport import utils from zaqar.transport import validation from zaqar.transport.wsgi import errors as wsgi_errors @@ -53,6 +54,7 @@ class CollectionResource(object): 'grace': default_grace_ttl, } + @acl.enforce("claims:create") def on_post(self, req, resp, project_id, queue_name): LOG.debug(u'Claims collection POST - queue: %(queue)s, ' u'project: %(project)s', @@ -125,6 +127,7 @@ class ItemResource(object): ('grace', int, default_grace_ttl), ) + @acl.enforce("claims:get") def on_get(self, req, resp, project_id, queue_name, claim_id): LOG.debug(u'Claim item GET - claim: %(claim_id)s, ' u'queue: %(queue_name)s, project: %(project_id)s', @@ -162,6 +165,7 @@ class ItemResource(object): resp.body = utils.to_json(meta) # status defaults to 200 + @acl.enforce("claims:update") def on_patch(self, req, resp, project_id, queue_name, claim_id): LOG.debug(u'Claim Item PATCH - claim: %(claim_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.') raise wsgi_errors.HTTPServiceUnavailable(description) + @acl.enforce("claims:delete") def on_delete(self, req, resp, project_id, queue_name, claim_id): LOG.debug(u'Claim item DELETE - claim: %(claim_id)s, ' u'queue: %(queue_name)s, project: %(project_id)s' % diff --git a/zaqar/transport/wsgi/v2_0/flavors.py b/zaqar/transport/wsgi/v2_0/flavors.py index bea94259d..3c55f448d 100644 --- a/zaqar/transport/wsgi/v2_0/flavors.py +++ b/zaqar/transport/wsgi/v2_0/flavors.py @@ -21,6 +21,7 @@ from zaqar.common.api.schemas import flavors as schema from zaqar.common import utils as common_utils from zaqar.i18n import _ from zaqar.storage import errors +from zaqar.transport import acl from zaqar.transport import utils as transport_utils from zaqar.transport.wsgi import errors as wsgi_errors from zaqar.transport.wsgi import utils as wsgi_utils @@ -38,6 +39,7 @@ class Listing(object): self._ctrl = flavors_controller self._pools_ctrl = pools_controller + @acl.enforce("flavors:get_all") def on_get(self, request, response, project_id): """Returns a flavor listing as objects embedded in an object: @@ -113,6 +115,7 @@ class Resource(object): 'capabilities': validator_type(schema.patch_capabilities), } + @acl.enforce("flavors:get") def on_get(self, request, response, project_id, flavor): """Returns a JSON object for a single flavor entry: @@ -140,6 +143,7 @@ class Resource(object): response.body = transport_utils.to_json(data) + @acl.enforce("flavors:create") def on_put(self, request, response, project_id, flavor): """Registers a new flavor. Expects the following input: @@ -170,6 +174,7 @@ class Resource(object): dict(flavor=flavor, pool=data['pool'])) raise falcon.HTTPBadRequest(_('Unable to create'), description) + @acl.enforce("flavors:delete") def on_delete(self, request, response, project_id, flavor): """Deregisters a flavor. @@ -180,6 +185,7 @@ class Resource(object): self._ctrl.delete(flavor, project=project_id) response.status = falcon.HTTP_204 + @acl.enforce("flavors:update") def on_patch(self, request, response, project_id, flavor): """Allows one to update a flavors's pool and/or capabilities. diff --git a/zaqar/transport/wsgi/v2_0/health.py b/zaqar/transport/wsgi/v2_0/health.py index def3a3d13..07970e509 100644 --- a/zaqar/transport/wsgi/v2_0/health.py +++ b/zaqar/transport/wsgi/v2_0/health.py @@ -16,6 +16,7 @@ from oslo_log import log as logging from zaqar.i18n import _ +from zaqar.transport import acl from zaqar.transport import utils from zaqar.transport.wsgi import errors as wsgi_errors @@ -29,6 +30,7 @@ class Resource(object): def __init__(self, driver): self._driver = driver + @acl.enforce("health:get") def on_get(self, req, resp, **kwargs): try: resp_dict = self._driver.health() diff --git a/zaqar/transport/wsgi/v2_0/messages.py b/zaqar/transport/wsgi/v2_0/messages.py index 3d742ade8..ea99eb337 100644 --- a/zaqar/transport/wsgi/v2_0/messages.py +++ b/zaqar/transport/wsgi/v2_0/messages.py @@ -20,6 +20,7 @@ import six from zaqar.common.transport.wsgi import helpers as wsgi_helpers from zaqar.i18n import _ from zaqar.storage import errors as storage_errors +from zaqar.transport import acl from zaqar.transport import utils from zaqar.transport import validation from zaqar.transport.wsgi import errors as wsgi_errors @@ -149,6 +150,7 @@ class CollectionResource(object): # Interface # ---------------------------------------------------------------------- + @acl.enforce("messages:create") def on_post(self, req, resp, project_id, queue_name): LOG.debug(u'Messages collection POST - queue: %(queue)s, ' u'project: %(project)s', @@ -213,6 +215,7 @@ class CollectionResource(object): resp.body = utils.to_json(body) resp.status = falcon.HTTP_201 + @acl.enforce("messages:get_all") def on_get(self, req, resp, project_id, queue_name): LOG.debug(u'Messages collection GET - queue: %(queue)s, ' u'project: %(project)s', @@ -237,6 +240,7 @@ class CollectionResource(object): resp.body = utils.to_json(response) # status defaults to 200 + @acl.enforce("messages:delete_all") def on_delete(self, req, resp, project_id, queue_name): LOG.debug(u'Messages collection DELETE - queue: %(queue)s, ' u'project: %(project)s', @@ -306,6 +310,7 @@ class ItemResource(object): def __init__(self, message_controller): self._message_controller = message_controller + @acl.enforce("messages:get") def on_get(self, req, resp, project_id, queue_name, message_id): LOG.debug(u'Messages item GET - message: %(message)s, ' u'queue: %(queue)s, project: %(project)s', @@ -336,6 +341,7 @@ class ItemResource(object): resp.body = utils.to_json(message) # status defaults to 200 + @acl.enforce("messages:delete") def on_delete(self, req, resp, project_id, queue_name, message_id): LOG.debug(u'Messages item DELETE - message: %(message)s, ' diff --git a/zaqar/transport/wsgi/v2_0/ping.py b/zaqar/transport/wsgi/v2_0/ping.py index 1ea344282..8875b1fda 100644 --- a/zaqar/transport/wsgi/v2_0/ping.py +++ b/zaqar/transport/wsgi/v2_0/ping.py @@ -14,6 +14,8 @@ import falcon +from zaqar.transport import acl + class Resource(object): @@ -22,9 +24,11 @@ class Resource(object): def __init__(self, driver): self._driver = driver + @acl.enforce("ping:get") def on_get(self, req, resp, **kwargs): resp.status = (falcon.HTTP_204 if self._driver.is_alive() else falcon.HTTP_503) + @acl.enforce("ping:get") def on_head(self, req, resp, **kwargs): resp.status = falcon.HTTP_204 diff --git a/zaqar/transport/wsgi/v2_0/pools.py b/zaqar/transport/wsgi/v2_0/pools.py index 70372fbc6..f59a3e0bd 100644 --- a/zaqar/transport/wsgi/v2_0/pools.py +++ b/zaqar/transport/wsgi/v2_0/pools.py @@ -45,6 +45,7 @@ from zaqar.common import utils as common_utils from zaqar.i18n import _ from zaqar.storage import errors from zaqar.storage import utils as storage_utils +from zaqar.transport import acl from zaqar.transport import utils as transport_utils from zaqar.transport.wsgi import errors as wsgi_errors from zaqar.transport.wsgi import utils as wsgi_utils @@ -61,6 +62,7 @@ class Listing(object): def __init__(self, pools_controller): self._ctrl = pools_controller + @acl.enforce("pools:get_all") def on_get(self, request, response, project_id): """Returns a pool listing as objects embedded in an object: @@ -128,6 +130,7 @@ class Resource(object): 'create': validator_type(schema.create) } + @acl.enforce("pools:get") def on_get(self, request, response, project_id, pool): """Returns a JSON object for a single pool entry: @@ -153,6 +156,7 @@ class Resource(object): response.body = transport_utils.to_json(data) + @acl.enforce("pools:create") def on_put(self, request, response, project_id, pool): """Registers a new pool. Expects the following input: @@ -181,6 +185,7 @@ class Resource(object): response.status = falcon.HTTP_201 response.location = request.path + @acl.enforce("pools:delete") def on_delete(self, request, response, project_id, pool): """Deregisters a pool. @@ -201,6 +206,7 @@ class Resource(object): response.status = falcon.HTTP_204 + @acl.enforce("pools:update") def on_patch(self, request, response, project_id, pool): """Allows one to update a pool's weight, uri, and/or options. diff --git a/zaqar/transport/wsgi/v2_0/queues.py b/zaqar/transport/wsgi/v2_0/queues.py index a394417ac..f9d68b72b 100644 --- a/zaqar/transport/wsgi/v2_0/queues.py +++ b/zaqar/transport/wsgi/v2_0/queues.py @@ -19,12 +19,12 @@ import six from zaqar.i18n import _ from zaqar.storage import errors as storage_errors +from zaqar.transport import acl from zaqar.transport import utils from zaqar.transport import validation from zaqar.transport.wsgi import errors as wsgi_errors from zaqar.transport.wsgi import utils as wsgi_utils - LOG = logging.getLogger(__name__) @@ -37,6 +37,7 @@ class ItemResource(object): self._queue_controller = queue_controller self._message_controller = message_controller + @acl.enforce("queues:get") def on_get(self, req, resp, project_id, queue_name): LOG.debug(u'Queue metadata GET - queue: %(queue)s, ' u'project: %(project)s', @@ -58,6 +59,7 @@ class ItemResource(object): resp.body = utils.to_json(resp_dict) # status defaults to 200 + @acl.enforce("queues:create") def on_put(self, req, resp, project_id, queue_name): LOG.debug(u'Queue item PUT - queue: %(queue)s, ' u'project: %(project)s', @@ -93,6 +95,7 @@ class ItemResource(object): resp.status = falcon.HTTP_201 if created else falcon.HTTP_204 resp.location = req.path + @acl.enforce("queues:delete") def on_delete(self, req, resp, project_id, queue_name): LOG.debug(u'Queue item DELETE - queue: %(queue)s, ' u'project: %(project)s', @@ -116,6 +119,7 @@ class CollectionResource(object): self._queue_controller = queue_controller self._validate = validate + @acl.enforce("queues:get_all") def on_get(self, req, resp, project_id): LOG.debug(u'Queue collection GET - project: %(project)s', {'project': project_id}) diff --git a/zaqar/transport/wsgi/v2_0/subscriptions.py b/zaqar/transport/wsgi/v2_0/subscriptions.py index 097c6dbc2..ee91136d6 100644 --- a/zaqar/transport/wsgi/v2_0/subscriptions.py +++ b/zaqar/transport/wsgi/v2_0/subscriptions.py @@ -19,6 +19,7 @@ import six from zaqar.i18n import _ from zaqar.storage import errors as storage_errors +from zaqar.transport import acl from zaqar.transport import utils from zaqar.transport import validation from zaqar.transport.wsgi import errors as wsgi_errors @@ -36,6 +37,7 @@ class ItemResource(object): self._validate = validate self._subscription_controller = subscription_controller + @acl.enforce("subscription:get") def on_get(self, req, resp, project_id, queue_name, subscription_id): LOG.debug(u'Subscription GET - subscription id: %(subscription_id)s,' u' project: %(project)s, queue: %(queue)s', @@ -58,6 +60,7 @@ class ItemResource(object): resp.body = utils.to_json(resp_dict) # status defaults to 200 + @acl.enforce("subscription:delete") def on_delete(self, req, resp, project_id, queue_name, subscription_id): LOG.debug(u'Subscription DELETE - ' u'subscription id: %(subscription_id)s,' @@ -76,6 +79,7 @@ class ItemResource(object): resp.status = falcon.HTTP_204 + @acl.enforce("subscription:update") def on_patch(self, req, resp, project_id, queue_name, subscription_id): LOG.debug(u'Subscription PATCH - subscription id: %(subscription_id)s,' u' project: %(project)s, queue: %(queue)s', @@ -117,6 +121,7 @@ class CollectionResource(object): self._subscription_controller = subscription_controller self._validate = validate + @acl.enforce("subscription:get_all") def on_get(self, req, resp, project_id, queue_name): LOG.debug(u'Subscription collection GET - project: %(project)s, ' u'queue: %(queue)s', @@ -162,6 +167,7 @@ class CollectionResource(object): resp.body = utils.to_json(response_body) # status defaults to 200 + @acl.enforce("subscription:create") def on_post(self, req, resp, project_id, queue_name): LOG.debug(u'Subscription item POST - project: %(project)s, ' u'queue: %(queue)s',