Require auth_context middleware in the pipeline

Keystone has shipped with the auth_context middleware in the pipeline
now for a number of releases. In newer parts of the code we rely on it
for auth information and in others we don't.

A prime example of this is the @protected handlers, if the auth_context
middleware isn't present it rebuilds this information. This is yet
another place where keystone validates a token.

Set a flag on the context to indicate that the request was validated by
auth_context middleware and raise Unauthenticated if not. This
essentially requires deployments to have the middleware enabled and
reduces validation to that point. This will help unify the code that is
behind a protected endpoint as we can rely on the context.

Change-Id: Iceca967e5bf698df13da28d0875c441f2a980e90
This commit is contained in:
Jamie Lennox 2016-07-08 15:55:39 +10:00
parent 7fc1b54f9d
commit 1d7c96d6a3
6 changed files with 30 additions and 45 deletions

View File

@ -29,6 +29,7 @@ class RequestContext(oslo_context.RequestContext):
self.username = kwargs.pop('username', None)
self.user_domain_name = kwargs.pop('user_domain_name', None)
self.project_domain_name = kwargs.pop('project_domain_name', None)
self.authenticated = kwargs.pop('authenticated', False)
super(RequestContext, self).__init__(**kwargs)
@classmethod

View File

@ -79,32 +79,7 @@ def _build_policy_check_credentials(self, action, context, kwargs):
'action': action,
'kwargs': kwargs_str})
# see if auth context has already been created. If so use it.
if ('environment' in context and
authorization.AUTH_CONTEXT_ENV in context['environment']):
LOG.debug('RBAC: using auth context from the request environment')
return context['environment'].get(authorization.AUTH_CONTEXT_ENV)
# There is no current auth context, build it from the incoming token.
# TODO(morganfainberg): Collapse this logic with AuthContextMiddleware
# in a sane manner as this just mirrors the logic in AuthContextMiddleware
try:
LOG.debug('RBAC: building auth context from the incoming auth token')
token_ref = token_model.KeystoneToken(
token_id=context['token_id'],
token_data=self.token_provider_api.validate_token(
context['token_id']))
# NOTE(jamielennox): whilst this maybe shouldn't be within this
# function it would otherwise need to reload the token_ref from
# backing store.
wsgi.validate_token_bind(context, token_ref)
except exception.TokenNotFound:
LOG.warning(_LW('RBAC: Invalid token'))
raise exception.Unauthorized()
auth_context = authorization.token_to_auth_context(token_ref)
return auth_context
return context['environment'].get(authorization.AUTH_CONTEXT_ENV, {})
def protected(callback=None):
@ -123,6 +98,8 @@ def protected(callback=None):
def wrapper(f):
@functools.wraps(f)
def inner(self, request, *args, **kwargs):
request.assert_authenticated()
if request.context.is_admin:
LOG.warning(_LW('RBAC: Bypassing authorization'))
elif callback is not None:
@ -205,6 +182,8 @@ def filterprotected(*filters, **callback):
def _filterprotected(f):
@functools.wraps(f)
def wrapper(self, request, **kwargs):
request.assert_authenticated()
if not request.context.is_admin:
# The target dict for the policy check will include:
#

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
import webob
from webob.descriptors import environ_getter
@ -17,13 +19,14 @@ from keystone.common import authorization
from keystone.common import context
import keystone.conf
from keystone import exception
from keystone.i18n import _
from keystone.i18n import _, _LW
# Environment variable used to pass the request context
CONTEXT_ENV = 'openstack.context'
CONF = keystone.conf.CONF
LOG = logging.getLogger(__name__)
class Request(webob.Request):
@ -73,6 +76,20 @@ class Request(webob.Request):
def auth_context(self):
return self.environ.get(authorization.AUTH_CONTEXT_ENV, {})
def assert_authenticated(self):
"""Ensure that the current request has been authenticated."""
if not self.context:
LOG.warning(_LW('An authenticated call was made and there is '
'no request.context. This means the '
'auth_context middleware is not in place. You '
'must have this middleware in your pipeline '
'to perform authenticated calls'))
raise exception.Unauthorized()
if not self.context.authenticated:
# auth_context didn't decode anything we can use
raise exception.Unauthorized()
auth_type = environ_getter('AUTH_TYPE', None)
remote_domain = environ_getter('REMOTE_DOMAIN', None)
context = environ_getter(context.REQUEST_CONTEXT_ENV, None)

View File

@ -154,6 +154,7 @@ class AuthContextMiddleware(auth_token.BaseAuthProtocol):
# The request context stores itself in thread-local memory for logging.
request_context = context.RequestContext(
request_id=request.environ.get('openstack.request_id'),
authenticated=False,
overwrite=True)
request.environ[context.REQUEST_CONTEXT_ENV] = request_context
@ -203,6 +204,9 @@ class AuthContextMiddleware(auth_token.BaseAuthProtocol):
'context will be set.')
return
# set authenticated to flag to keystone that a token has been validated
request_context.authenticated = True
# The attributes of request_context are put into the logs. This is a
# common pattern for all the OpenStack services. In all the other
# projects these are IDs, so set the attributes to IDs here rather than

View File

@ -594,7 +594,8 @@ class TestCase(BaseTestCase):
if not environ.get(context.REQUEST_CONTEXT_ENV):
environ[context.REQUEST_CONTEXT_ENV] = context.RequestContext(
is_admin=is_admin)
is_admin=is_admin,
authenticated=kwargs.pop('authenticated', True))
req = request.Request.blank(path=path, **kwargs)
req.context_dict['is_admin'] = is_admin

View File

@ -4699,23 +4699,6 @@ class TestTrustChain(test_v3.RestfulTestCase):
expected_status=http_client.FORBIDDEN)
class TestAPIProtectionWithoutAuthContextMiddleware(test_v3.RestfulTestCase):
def test_api_protection_with_no_auth_context_in_env(self):
auth_data = self.build_authentication_request(
user_id=self.default_domain_user['id'],
password=self.default_domain_user['password'],
project_id=self.project['id'])
token = self.get_requested_token(auth_data)
auth_controller = auth.controllers.Auth()
# all we care is that auth context is not in the environment and
# 'token_id' is used to build the auth context instead
request = self.make_request()
request.context_dict['subject_token_id'] = token
request.context_dict['token_id'] = token
r = auth_controller.validate_token(request)
self.assertEqual(http_client.OK, r.status_code)
class TestAuthContext(unit.TestCase):
def setUp(self):
super(TestAuthContext, self).setUp()