]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add hook to create a context from the headers
authorKevin Benton <blak111@gmail.com>
Thu, 11 Jun 2015 10:46:22 +0000 (03:46 -0700)
committerKevin Benton <kevinbenton@buttewifi.com>
Sat, 1 Aug 2015 18:55:20 +0000 (18:55 +0000)
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

neutron/newapi/app.py
neutron/newapi/hooks/__init__.py
neutron/newapi/hooks/context.py [new file with mode: 0644]
neutron/tests/functional/newapi/test_functional.py

index 5eb294f85db33e1be2b524b726be2eca68291471..fc59ba26dc9dc870e0bc8f743a53c5f30ed4a565 100644 (file)
@@ -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(
index 4e2c75bd214ef66f1108905241eebce5ac02e9a9..eaffa81287d48458821fe6f99baddf3f5be9cb75 100644 (file)
@@ -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 (file)
index 0000000..3136924
--- /dev/null
@@ -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
index 9e43bcde1c783ebe93ec5551f3fd4c4f77e8a0ab..8a57fb007779ca68a83d608b803c3d5cf9ab953b 100644 (file)
@@ -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)