diff --git a/gluon/api/app.py b/gluon/api/app.py index 3952f43..bdd80fc 100644 --- a/gluon/api/app.py +++ b/gluon/api/app.py @@ -1,4 +1,5 @@ # Copyright 2016, Ericsson AB +# Copyright 2017, Nokia # # 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 @@ -12,18 +13,19 @@ # License for the specific language governing permissions and limitations # under the License. +from keystonemiddleware import auth_token import pecan -from keystonemiddleware import auth_token from oslo_config import cfg from oslo_log import log as logging - from oslo_middleware import cors from oslo_middleware import http_proxy_to_wsgi from oslo_middleware import request_id +from gluon.api import hooks from gluon.common import exception as g_exc + # TODO(enikher) # from gluon.api import middleware @@ -44,16 +46,16 @@ app_dic = {'root': 'gluon.api.root.RootController', def setup_app(config=None): - # app_hooks = [ - # hooks.PolicyHook(), - # hooks.ContextHook() - # ] + app_hooks = [ + hooks.ContextHook(), + hooks.PolicyHook() + ] app = pecan.make_app( app_dic.pop('root'), logging=getattr(config, 'logging', {}), - # wrap_app=_wrap_app, - # hooks=app_hooks, + wrap_app=_wrap_app, + hooks=app_hooks, # TODO(enikher) # wrap_app=middleware.ParsableErrorMiddleware, **app_dic @@ -73,9 +75,9 @@ def setup_app(config=None): def _wrap_app(app): app = request_id.RequestId(app) - if CONF.auth_strategy == 'noauth': + if CONF.api.auth_strategy == 'noauth': pass - elif CONF.auth_strategy == 'keystone': + elif CONF.api.auth_strategy == 'keystone': app = auth_token.AuthProtocol(app, {}) LOG.info("Keystone authentication is enabled") else: diff --git a/gluon/api/baseObject.py b/gluon/api/baseObject.py index de29696..45d21a5 100644 --- a/gluon/api/baseObject.py +++ b/gluon/api/baseObject.py @@ -14,18 +14,19 @@ import datetime import six - -from pecan import rest import wsme -from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from pecan import expose +from pecan import rest + +from wsme import types as wtypes + from gluon.db import api as dbapi from gluon.managers.manager_base import get_api_manager class APIBase(wtypes.Base): - # TBD created_at = wsme.wsattr(datetime.datetime, readonly=True) """The time in UTC at which the object is created""" @@ -59,7 +60,6 @@ class APIBase(wtypes.Base): class APIBaseObject(APIBase): - @classmethod def class_builder(base_cls, name, _db_model, attributes): new_cls = type(name, (base_cls,), attributes) @@ -107,7 +107,6 @@ class APIBaseObject(APIBase): class APIBaseList(APIBase): - @classmethod def class_builder(base_cls, name, list_name, api_object_class): new_cls = type(name, (base_cls,), {list_name: [api_object_class]}) @@ -133,6 +132,7 @@ class RootObjectController(rest.RestController): def class_builder(base_cls, name, api_obj_class, primary_key_type, api_name): new_cls = type(name, (base_cls,), {}) + new_cls.resource_name = name new_cls.list_object_class = APIBaseList.class_builder(name + 'List', name, api_obj_class) @@ -143,6 +143,7 @@ class RootObjectController(rest.RestController): @wsme_pecan.wsexpose(new_cls.list_object_class, template='json') def get_all(self): return self.list_object_class.build() + new_cls.get_all = classmethod(get_all) @wsme_pecan.wsexpose(new_cls.api_object_class, @@ -150,6 +151,7 @@ class RootObjectController(rest.RestController): template='json') def get_one(self, key): return self.api_object_class.get_from_db(key) + new_cls.get_one = classmethod(get_one) @wsme_pecan.wsexpose(new_cls.api_object_class, @@ -157,6 +159,7 @@ class RootObjectController(rest.RestController): status_code=201) def post(self, body): return self.api_mgr.handle_create(self, body.as_dict()) + new_cls.post = classmethod(post) @wsme_pecan.wsexpose(new_cls.api_object_class, @@ -164,15 +167,29 @@ class RootObjectController(rest.RestController): body=new_cls.api_object_class, template='json') def put(self, key, body): return self.api_mgr.handle_update(self, key, body.as_dict()) + new_cls.put = classmethod(put) @wsme_pecan.wsexpose(None, new_cls.primary_key_type, template='json') def delete(self, key): return self.api_mgr.handle_delete(self, key) + new_cls.delete = classmethod(delete) return new_cls + @expose() + def _route(self, args, request): + result = super(RootObjectController, self)._route(args, request) + request.context['resource'] = result[0].im_self.resource_name + return result + # @expose() + # def _lookup(self, collection, *remainder): + # #Set resource_action in the context to denote that + # #this is a show operation and not list + # request.context['resource_action'] = 'show' + # return self + # TODO(hambtw) Needs to be reworked # class SubObjectController(RootObjectController): # diff --git a/gluon/api/hooks/policy_enforcement.py b/gluon/api/hooks/policy_enforcement.py index 38408f5..e6bc4b0 100644 --- a/gluon/api/hooks/policy_enforcement.py +++ b/gluon/api/hooks/policy_enforcement.py @@ -13,14 +13,17 @@ # License for the specific language governing permissions and limitations # under the License. +import webob + +from oslo_config import cfg from oslo_policy import policy as oslo_policy from oslo_utils import excutils from pecan import hooks -import webob + +from gluon import constants as gluon_constants +from gluon import policy from gluon._i18n import _ -from gluon import constants -from gluon import policy class PolicyHook(hooks.PecanHook): @@ -28,140 +31,37 @@ class PolicyHook(hooks.PecanHook): def before(self, state): - # This hook should be run only for PUT,POST and DELETE methods - resources = state.request.context.get('resources', []) - - if state.request.method not in ('POST', 'PUT', 'DELETE'): + if cfg.CONF.api.auth_strategy == 'noauth': + return + + if state.request.method not in ('GET', 'POST', 'PUT', 'DELETE'): + return + + method = gluon_constants.ACTION_MAP[state.request.method] + + path_info = state.request.path_info + + if not path_info: return - # As this routine will likely alter the resources, do a shallow copy - resources_copy = resources[:] - gluon_context = state.request.context.get('gluon_context') resource = state.request.context.get('resource') - # If there is no resource for this request, don't bother running authZ - # policies if not resource: return - # controller = utils.get_controller(state) - controller = state.arguments.args[0] + action = "%s_%s" % (method, resource) - # if not controller or utils.is_member_action(controller): - # return + gluon_context = state.request.context.get('gluon_context') - collection = state.request.context.get('collection') - needs_prefetch = (state.request.method == 'PUT' or - state.request.method == 'DELETE') policy.init() - action = controller.plugin_handlers[ - constants.ACTION_MAP[state.request.method]] - - for item in resources_copy: - try: - policy.enforce( - gluon_context, action, item, - pluralized=collection) - except oslo_policy.PolicyNotAuthorized: - with excutils.save_and_reraise_exception() as ctxt: - # If a tenant is modifying it's own object, it's safe to - # return a 403. Otherwise, pretend that it doesn't exist - # to avoid giving away information. - orig_item_tenant_id = item.get('tenant_id') - if (needs_prefetch and - (gluon_context.tenant_id != orig_item_tenant_id or - orig_item_tenant_id is None)): - ctxt.reraise = False - msg = _('The resource could not be found.') - raise webob.exc.HTTPNotFound(msg) - - def after(self, state): - gluon_context = state.request.context.get('gluon_context') - resource = state.request.context.get('resource') - collection = state.request.context.get('collection') - # = utils.get_controller(state) - controller = state.arguments.args[0] - - if not resource: - # can't filter a resource we don't recognize - return - - if resource == 'extension': - return try: - data = state.response.json - except ValueError: - return - - if state.request.method not in constants.ACTION_MAP: - return - - action = '%s_%s' % (constants.ACTION_MAP[state.request.method], - resource) - - if not data or (resource not in data and collection not in data): - return - - is_single = resource in data - key = resource if is_single else collection - to_process = [data[resource]] if is_single else data[collection] - - # in the single case, we enforce which raises on violation - # in the plural case, we just check so violating items are hidden - policy_method = policy.enforce if is_single else policy.check - - try: - resp = [self._get_filtered_item(state.request, controller, - resource, collection, item) - for item in to_process - if (state.request.method != 'GET' or - policy_method(gluon_context, action, item, - pluralized=collection))] + policy.enforce( + gluon_context, action, None) except oslo_policy.PolicyNotAuthorized as e: - # This exception must be explicitly caught as the exception - # translation hook won't be called if an error occurs in the - # 'after' handler. raise webob.exc.HTTPForbidden(str(e)) - if is_single: - resp = resp[0] - state.response.json = {key: resp} - - def _get_filtered_item(self, request, controller, resource, collection, - data): - gluon_context = request.context.get('gluon_context') - to_exclude = self._exclude_attributes_by_policy( - gluon_context, controller, resource, collection, data) - return self._filter_attributes(request, data, to_exclude) - - def _filter_attributes(self, request, data, fields_to_strip): - # This routine will remove the fields that were requested to the - # plugin for policy evaluation but were not specified in the - # API request - user_fields = request.params.getall('fields') - return dict(item for item in data.items() - if (item[0] not in fields_to_strip and - (not user_fields or item[0] in user_fields))) - - def _exclude_attributes_by_policy(self, context, controller, resource, - collection, data): - """Identifies attributes to exclude according to authZ policies. - - Return a list of attribute names which should be stripped from the - response returned to the user because the user is not authorized - to see them. - """ - attributes_to_exclude = [] - for attr_name in data.keys(): - attr_data = controller.resource_info.get(attr_name) - if attr_data and attr_data['is_visible']: - if policy.check(context, 'get_%s:%s' % (resource, attr_name), - data, might_not_exist=True, - pluralized=collection): - # this attribute is visible, check next one - continue - # if the code reaches this point then either the policy check - # failed or the attribute was not visible in the first place - attributes_to_exclude.append(attr_name) - return attributes_to_exclude + def after(self, state): + # This method could be used for implementing access control + # at the attribute level. + return diff --git a/gluon/cmd/config.py b/gluon/cmd/config.py index 329d2a0..eabdedb 100644 --- a/gluon/cmd/config.py +++ b/gluon/cmd/config.py @@ -29,7 +29,10 @@ API_SERVICE_OPTS = [ help='etcd host'), cfg.IntOpt('etcd_port', default=2379, - help='etcd port') + help='etcd port'), + cfg.StrOpt('auth_strategy', + default='noauth', + help='the type of authentication to use') ] CONF = cfg.CONF