From 9a946b2dfdf9ec6765c63171ff4f9f435d92e90b Mon Sep 17 00:00:00 2001 From: Serg Melikyan Date: Mon, 4 Mar 2013 15:03:21 +0400 Subject: [PATCH] Added support for keystone-auth --- portas/portas/api/middleware/__init__.py | 1 + portas/portas/api/middleware/context.py | 122 +++++++++++++++++++++++ portas/portas/context.py | 84 ++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 portas/portas/api/middleware/__init__.py create mode 100644 portas/portas/api/middleware/context.py create mode 100644 portas/portas/context.py diff --git a/portas/portas/api/middleware/__init__.py b/portas/portas/api/middleware/__init__.py new file mode 100644 index 0000000..0a754b6 --- /dev/null +++ b/portas/portas/api/middleware/__init__.py @@ -0,0 +1 @@ +__author__ = 'sad' diff --git a/portas/portas/api/middleware/context.py b/portas/portas/api/middleware/context.py new file mode 100644 index 0000000..1d22b9e --- /dev/null +++ b/portas/portas/api/middleware/context.py @@ -0,0 +1,122 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011-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. + +import json + +from oslo.config import cfg +import webob.exc + +from glance.common import wsgi +import glance.context +import glance.openstack.common.log as logging + + +context_opts = [ + cfg.BoolOpt('owner_is_tenant', default=True), + cfg.StrOpt('admin_role', default='admin'), + cfg.BoolOpt('allow_anonymous_access', default=False), +] + +CONF = cfg.CONF +CONF.register_opts(context_opts) + +LOG = logging.getLogger(__name__) + + +class BaseContextMiddleware(wsgi.Middleware): + def process_response(self, resp): + try: + request_id = resp.request.context.request_id + except AttributeError: + LOG.warn(_('Unable to retrieve request id from context')) + else: + resp.headers['x-openstack-request-id'] = 'req-%s' % request_id + return resp + + +class ContextMiddleware(BaseContextMiddleware): + def process_request(self, req): + """Convert authentication information into a request context + + Generate a glance.context.RequestContext object from the available + authentication headers and store on the 'context' attribute + of the req object. + + :param req: wsgi request object that will be given the context object + :raises webob.exc.HTTPUnauthorized: when value of the X-Identity-Status + header is not 'Confirmed' and + anonymous access is disallowed + """ + if req.headers.get('X-Identity-Status') == 'Confirmed': + req.context = self._get_authenticated_context(req) + elif CONF.allow_anonymous_access: + req.context = self._get_anonymous_context() + else: + raise webob.exc.HTTPUnauthorized() + + def _get_anonymous_context(self): + kwargs = { + 'user': None, + 'tenant': None, + 'roles': [], + 'is_admin': False, + 'read_only': True, + } + return glance.context.RequestContext(**kwargs) + + def _get_authenticated_context(self, req): + #NOTE(bcwaldon): X-Roles is a csv string, but we need to parse + # it into a list to be useful + roles_header = req.headers.get('X-Roles', '') + roles = [r.strip().lower() for r in roles_header.split(',')] + + #NOTE(bcwaldon): This header is deprecated in favor of X-Auth-Token + deprecated_token = req.headers.get('X-Storage-Token') + + service_catalog = None + if req.headers.get('X-Service-Catalog') is not None: + try: + catalog_header = req.headers.get('X-Service-Catalog') + service_catalog = json.loads(catalog_header) + except ValueError: + raise webob.exc.HTTPInternalServerError( + _('Invalid service catalog json.')) + + kwargs = { + 'user': req.headers.get('X-User-Id'), + 'tenant': req.headers.get('X-Tenant-Id'), + 'roles': roles, + 'is_admin': CONF.admin_role.strip().lower() in roles, + 'auth_tok': req.headers.get('X-Auth-Token', deprecated_token), + 'owner_is_tenant': CONF.owner_is_tenant, + 'service_catalog': service_catalog, + } + + return glance.context.RequestContext(**kwargs) + + +class UnauthenticatedContextMiddleware(BaseContextMiddleware): + def process_request(self, req): + """Create a context without an authorized user.""" + kwargs = { + 'user': None, + 'tenant': None, + 'roles': [], + 'is_admin': True, + } + + req.context = glance.context.RequestContext(**kwargs) diff --git a/portas/portas/context.py b/portas/portas/context.py new file mode 100644 index 0000000..8e3d9a4 --- /dev/null +++ b/portas/portas/context.py @@ -0,0 +1,84 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011-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. + +from glance.openstack.common import local +from glance.openstack.common import uuidutils + + +class RequestContext(object): + """ + Stores information about the security context under which the user + accesses the system, as well as additional request information. + """ + + def __init__(self, auth_tok=None, user=None, tenant=None, roles=None, + is_admin=False, read_only=False, show_deleted=False, + owner_is_tenant=True, service_catalog=None): + self.auth_tok = auth_tok + self.user = user + self.tenant = tenant + self.roles = roles or [] + self.is_admin = is_admin + self.read_only = read_only + self._show_deleted = show_deleted + self.owner_is_tenant = owner_is_tenant + self.request_id = uuidutils.generate_uuid() + self.service_catalog = service_catalog + + if not hasattr(local.store, 'context'): + self.update_store() + + def to_dict(self): + # NOTE(ameade): These keys are named to correspond with the default + # format string for logging the context in openstack common + return { + 'request_id': self.request_id, + + #NOTE(bcwaldon): openstack-common logging expects 'user' + 'user': self.user, + 'user_id': self.user, + + #NOTE(bcwaldon): openstack-common logging expects 'tenant' + 'tenant': self.tenant, + 'tenant_id': self.tenant, + 'project_id': self.tenant, + + 'is_admin': self.is_admin, + 'read_deleted': self.show_deleted, + 'roles': self.roles, + 'auth_token': self.auth_tok, + 'service_catalog': self.service_catalog, + } + + @classmethod + def from_dict(cls, values): + return cls(**values) + + def update_store(self): + local.store.context = self + + @property + def owner(self): + """Return the owner to correlate with an image.""" + return self.tenant if self.owner_is_tenant else self.user + + @property + def show_deleted(self): + """Admins can see deleted by default""" + if self._show_deleted or self.is_admin: + return True + return False