diff --git a/openstack-common.conf b/openstack-common.conf index 74e1ce1e..c4112300 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,6 +1,7 @@ [DEFAULT] module=cache module=config.generator +module=context module=excutils module=fileutils module=gettextutils diff --git a/zaqar/cmd/server.py b/zaqar/cmd/server.py index 21afd48f..bf0e37da 100644 --- a/zaqar/cmd/server.py +++ b/zaqar/cmd/server.py @@ -26,6 +26,12 @@ def run(): # to pick up common options from openstack.common.log, since # that module uses the global CONF instance exclusively. conf = cfg.CONF + # NOTE(jeffrey4l): Overwrite the default vaule for + # logging_context_format_string. Add project_id into it. + conf.set_default('logging_context_format_string', + '%(asctime)s.%(msecs)03d %(process)d %(levelname)s' + ' %(name)s [%(request_id)s %(user_identity)s]' + ' [project_id:%(project_id)s] %(message)s') conf(project='zaqar', prog='zaqar-queues') server = bootstrap.Bootstrap(conf) diff --git a/zaqar/common/transport/wsgi/helpers.py b/zaqar/common/transport/wsgi/helpers.py index e4376ebd..1f50eb5f 100644 --- a/zaqar/common/transport/wsgi/helpers.py +++ b/zaqar/common/transport/wsgi/helpers.py @@ -20,6 +20,7 @@ import uuid import falcon import six +from zaqar import context from zaqar.i18n import _ import zaqar.openstack.common.log as logging from zaqar.queues.transport import validation @@ -152,3 +153,23 @@ Endpoint only serves `application/json`; specify client-side media type support with the "Accept" header.''', href=u'http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html', href_text=u'14.1 Accept, Hypertext Transfer Protocol -- HTTP/1.1') + + +def inject_context(req, resp, params): + """Inject context value into request environment. + + :param req: request sent + :type req: falcon.request.Request + :param resp: response object + :type resp: falcon.response.Response + :param params: additional parameters passed to responders + :type params: dict + :rtype: None + + """ + client_id = req.get_header('Client-ID') + project_id = params.get('project_id', None) + + ctxt = context.RequestContext(project_id=project_id, + client_id=client_id) + req.env['zaqar.context'] = ctxt diff --git a/zaqar/context.py b/zaqar/context.py new file mode 100644 index 00000000..fdf6613a --- /dev/null +++ b/zaqar/context.py @@ -0,0 +1,56 @@ +# Copyright 2011 OpenStack Foundation +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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. + +"""RequestContext: context for requests that persist through all of zaqar.""" + +from zaqar.openstack.common import context +from zaqar.openstack.common import local + + +class RequestContext(context.RequestContext): + + def __init__(self, project_id=None, client_id=None, overwrite=True, + 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): + super(RequestContext, self).__init__(auth_token=auth_token, + user=user, + tenant=tenant, + domain=domain, + user_domain=user_domain, + project_domain=project_domain, + is_admin=is_admin, + read_only=read_only, + show_deleted=False, + request_id=request_id, + instance_uuid=instance_uuid) + self.project_id = project_id + self.client_id = client_id + if overwrite or not hasattr(local.store, 'context'): + self.update_store() + + def update_store(self): + local.store.context = self + + def to_dict(self): + ctx = super(RequestContext, self).to_dict() + ctx.update({ + 'project_id': self.project_id, + 'client_id': self.client_id + }) + return ctx diff --git a/zaqar/openstack/common/context.py b/zaqar/openstack/common/context.py new file mode 100644 index 00000000..b612db71 --- /dev/null +++ b/zaqar/openstack/common/context.py @@ -0,0 +1,126 @@ +# Copyright 2011 OpenStack Foundation. +# 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. + +""" +Simple class that stores security context information in the web request. + +Projects should subclass this class if they wish to enhance the request +context or provide additional information in their specific WSGI pipeline. +""" + +import itertools +import uuid + + +def generate_request_id(): + return b'req-' + str(uuid.uuid4()).encode('ascii') + + +class RequestContext(object): + + """Helper class to represent useful information about a request context. + + Stores information about the security context under which the user + accesses the system, as well as additional request information. + """ + + user_idt_format = '{user} {tenant} {domain} {user_domain} {p_domain}' + + def __init__(self, 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): + self.auth_token = auth_token + self.user = user + self.tenant = tenant + self.domain = domain + self.user_domain = user_domain + self.project_domain = project_domain + self.is_admin = is_admin + self.read_only = read_only + self.show_deleted = show_deleted + self.instance_uuid = instance_uuid + if not request_id: + request_id = generate_request_id() + self.request_id = request_id + + def to_dict(self): + user_idt = ( + self.user_idt_format.format(user=self.user or '-', + tenant=self.tenant or '-', + domain=self.domain or '-', + user_domain=self.user_domain or '-', + p_domain=self.project_domain or '-')) + + return {'user': self.user, + 'tenant': self.tenant, + 'domain': self.domain, + 'user_domain': self.user_domain, + 'project_domain': self.project_domain, + 'is_admin': self.is_admin, + 'read_only': self.read_only, + 'show_deleted': self.show_deleted, + 'auth_token': self.auth_token, + 'request_id': self.request_id, + 'instance_uuid': self.instance_uuid, + 'user_identity': user_idt} + + @classmethod + def from_dict(cls, ctx): + return cls( + auth_token=ctx.get("auth_token"), + user=ctx.get("user"), + tenant=ctx.get("tenant"), + domain=ctx.get("domain"), + user_domain=ctx.get("user_domain"), + project_domain=ctx.get("project_domain"), + is_admin=ctx.get("is_admin", False), + read_only=ctx.get("read_only", False), + show_deleted=ctx.get("show_deleted", False), + request_id=ctx.get("request_id"), + instance_uuid=ctx.get("instance_uuid")) + + +def get_admin_context(show_deleted=False): + context = RequestContext(None, + tenant=None, + is_admin=True, + show_deleted=show_deleted) + return context + + +def get_context_from_function_and_args(function, args, kwargs): + """Find an arg of type RequestContext and return it. + + This is useful in a couple of decorators where we don't + know much about the function we're wrapping. + """ + + for arg in itertools.chain(kwargs.values(), args): + if isinstance(arg, RequestContext): + return arg + + return None + + +def is_user_context(context): + """Indicates if the request context is a normal user.""" + if not context: + return False + if context.is_admin: + return False + if not context.user_id or not context.project_id: + return False + return True diff --git a/zaqar/queues/transport/wsgi/driver.py b/zaqar/queues/transport/wsgi/driver.py index 836edfd1..59c12fc9 100644 --- a/zaqar/queues/transport/wsgi/driver.py +++ b/zaqar/queues/transport/wsgi/driver.py @@ -67,6 +67,10 @@ class Driver(transport.DriverBase): helpers.require_client_id, helpers.extract_project_id, + # NOTE(jeffrey4l): Depends on the project_id and client_id being + # extracted above + helpers.inject_context, + # NOTE(kgriffs): Depends on project_id being extracted, above functools.partial(helpers.validate_queue_identification, self._validate.queue_identification) diff --git a/zaqar/queues/transport/wsgi/v1_0/queues.py b/zaqar/queues/transport/wsgi/v1_0/queues.py index eb24ff7d..610eeb75 100644 --- a/zaqar/queues/transport/wsgi/v1_0/queues.py +++ b/zaqar/queues/transport/wsgi/v1_0/queues.py @@ -35,9 +35,8 @@ class ItemResource(object): self._message_controller = message_controller def on_put(self, req, resp, project_id, queue_name): - LOG.debug(u'Queue item PUT - queue: %(queue)s, ' - u'project: %(project)s', - {'queue': queue_name, 'project': project_id}) + LOG.debug(u'Queue item PUT - queue: %(queue)s, ', + {'queue': queue_name}) try: created = self._queue_controller.create( @@ -89,8 +88,7 @@ class CollectionResource(object): self._validate = validate def on_get(self, req, resp, project_id): - LOG.debug(u'Queue collection GET - project: %(project)s', - {'project': project_id}) + LOG.debug(u'Queue collection GET') kwargs = {}