From: Kevin Benton Date: Thu, 11 Jun 2015 10:46:22 +0000 (-0700) Subject: Add hook to create a context from the headers X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=40b1c798546dcd57509e25339a449de9047abc0f;p=openstack-build%2Fneutron-build.git Add hook to create a context from the headers Adds a hook to parse the HTTP headers and turn them into a Neutron context. The context is then attached to the thread-local request object so future hooks and the controller can operate on it. Partially-Implements: blueprint wsgi-pecan-switch Change-Id: I982f7dc7f736c2b3539dffcf81c4ad141434b68f --- diff --git a/neutron/newapi/app.py b/neutron/newapi/app.py index 5eb294f85..fc59ba26d 100644 --- a/neutron/newapi/app.py +++ b/neutron/newapi/app.py @@ -42,7 +42,8 @@ def setup_app(*args, **kwargs): pecan_config = pecan.configuration.conf_from_dict(config) app_hooks = [ - hooks.ExceptionTranslationHook(), + hooks.ExceptionTranslationHook(), # priority 100 + hooks.ContextHook(), # priority 95 ] app = pecan.make_app( diff --git a/neutron/newapi/hooks/__init__.py b/neutron/newapi/hooks/__init__.py index 4e2c75bd2..eaffa8128 100644 --- a/neutron/newapi/hooks/__init__.py +++ b/neutron/newapi/hooks/__init__.py @@ -13,7 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron.newapi.hooks import context from neutron.newapi.hooks import translation ExceptionTranslationHook = translation.ExceptionTranslationHook +ContextHook = context.ContextHook diff --git a/neutron/newapi/hooks/context.py b/neutron/newapi/hooks/context.py new file mode 100644 index 000000000..313692496 --- /dev/null +++ b/neutron/newapi/hooks/context.py @@ -0,0 +1,58 @@ +# Copyright 2012 New Dream Network, LLC (DreamHost) +# 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 oslo_middleware import request_id +from pecan import hooks + +from neutron import context + + +class ContextHook(hooks.PecanHook): + """Configures a request context and attaches it to the request. + The following HTTP request headers are used: + X-User-Id or X-User: + Used for context.user_id. + X-Tenant-Id or X-Tenant: + Used for context.tenant. + X-Auth-Token: + Used for context.auth_token. + X-Roles: + Used for setting context.is_admin flag to either True or False. + The flag is set to True, if X-Roles contains either an administrator + or admin substring. Otherwise it is set to False. + """ + + priority = 95 + + def before(self, state): + user_id = state.request.headers.get('X-User-Id') + user_id = state.request.headers.get('X-User', user_id) + user_name = state.request.headers.get('X-User-Name', '') + tenant_id = state.request.headers.get('X-Tenant-Id') + tenant_name = state.request.headers.get('X-Tenant-Name') + auth_token = state.request.headers.get('X-Auth-Token') + roles = state.request.headers.get('X-Roles', '').split(',') + roles = [r.strip() for r in roles] + creds = {'roles': roles} + req_id = state.request.headers.get(request_id.ENV_REQUEST_ID) + # TODO(kevinbenton): is_admin logic + # Create a context with the authentication data + ctx = context.Context(user_id, tenant_id=tenant_id, + roles=creds['roles'], + user_name=user_name, tenant_name=tenant_name, + request_id=req_id, auth_token=auth_token) + + # Inject the context... + state.request.context = ctx diff --git a/neutron/tests/functional/newapi/test_functional.py b/neutron/tests/functional/newapi/test_functional.py index 9e43bcde1..8a57fb007 100644 --- a/neutron/tests/functional/newapi/test_functional.py +++ b/neutron/tests/functional/newapi/test_functional.py @@ -18,6 +18,7 @@ import os import mock from oslo_config import cfg from oslo_utils import uuidutils +from pecan import request from pecan import set_config from pecan.testing import load_test_app import testtools @@ -133,3 +134,21 @@ class TestExceptionTranslationHook(PecanFunctionalTest): response = self.app.get('/v2.0/ports.json', expect_errors=True) self.assertNotIn(response.body, 'secretpassword') self.assertEqual(response.status_int, 500) + + +class TestContextHook(PecanFunctionalTest): + + # TODO(kevinbenton): add tests for X-Roles etc + + def test_context_set_in_request(self): + request_stash = [] + # request.context is thread-local storage so it has to be accessed by + # the controller. We can capture it into a list here to assert on after + # the request finishes. + with mock.patch( + 'neutron.newapi.controllers.root.GeneralController.get', + side_effect=lambda *x, **y: request_stash.append(request.context) + ): + self.app.get('/v2.0/ports.json', + headers={'X-Tenant-Id': 'tenant_id'}) + self.assertEqual('tenant_id', request_stash[0].tenant_id)