AuthContextMiddleware admin token handling

The AuthContextMiddleware shouldn't be re-implementing the
AdminTokenAuthMiddleware but using the request environment
context setting that the admin token middleware sets up.

This makes it so that admin token handling is in one place
rather than duplicating it and allows for an alternative
implementation of the admin token middleware.

The old behavior is left in place as deprecated to be removed
in a future release.

ReleaseNotesImpact
 - The paste.ini file is changed to put the admin token
   middleware first so that the auth context middleware can use
   the results. It's deprecated to have the admin token
   middleware after the auth context middleware.

Closes-Bug: 1473553
Change-Id: I658213699ac4af0abd08f893d9cf18ef0af5827d
This commit is contained in:
Brant Knudson 2015-07-06 19:51:48 -05:00
parent 5286b4a297
commit c29ff68a6f
3 changed files with 90 additions and 28 deletions

View File

@ -13,6 +13,7 @@
from oslo_config import cfg
from oslo_context import context as oslo_context
from oslo_log import log
from oslo_log import versionutils
from keystone.common import authorization
from keystone.common import tokenless_auth
@ -34,14 +35,55 @@ __all__ = ('AuthContextMiddleware',)
class AuthContextMiddleware(wsgi.Middleware):
"""Build the authentication context from the request auth token."""
def _build_auth_context(self, request, token_id):
def _build_auth_context(self, request):
# NOTE(gyee): token takes precedence over SSL client certificates.
# This will preserve backward compatibility with the existing
# behavior. Tokenless authorization with X.509 SSL client
# certificate is effectively disabled if no trusted issuers are
# provided.
token_id = None
if core.AUTH_TOKEN_HEADER in request.headers:
token_id = request.headers[core.AUTH_TOKEN_HEADER].strip()
is_admin = request.environ.get(core.CONTEXT_ENV, {}).get('is_admin',
False)
if is_admin:
# NOTE(gyee): no need to proceed any further as we already know
# this is an admin request.
auth_context = {}
return auth_context, token_id, is_admin
if token_id:
# In this case the client sent in a token.
auth_context, is_admin = self._build_token_auth_context(
request, token_id)
return auth_context, token_id, is_admin
# No token, maybe the client presented an X.509 certificate.
if self._validate_trusted_issuer(request.environ):
auth_context = self._build_tokenless_auth_context(
request.environ)
return auth_context, None, False
LOG.debug('There is either no auth token in the request or '
'the certificate issuer is not trusted. No auth '
'context will be set.')
return None, None, False
def _build_token_auth_context(self, request, token_id):
if token_id == CONF.admin_token:
# NOTE(gyee): no need to proceed any further as the special admin
# token is being handled by AdminTokenAuthMiddleware. This code
# will not be impacted even if AdminTokenAuthMiddleware is removed
# from the pipeline as "is_admin" is default to "False". This code
# is independent of AdminTokenAuthMiddleware.
return {}
versionutils.report_deprecated_feature(
LOG,
_LW('Auth context checking for the admin token is deprecated '
'as of the Mitaka release and will be removed in the O '
'release. Update keystone-paste.ini so that '
'admin_token_auth is before build_auth_context in the '
'paste pipelines.'))
return {}, True
context = {'token_id': token_id}
context['environment'] = request.environ
@ -53,7 +95,7 @@ class AuthContextMiddleware(wsgi.Middleware):
# TODO(gyee): validate_token_bind should really be its own
# middleware
wsgi.validate_token_bind(context, token_ref)
return authorization.token_to_auth_context(token_ref)
return authorization.token_to_auth_context(token_ref), False
except exception.TokenNotFound:
LOG.warning(_LW('RBAC: Invalid token'))
raise exception.Unauthorized()
@ -154,23 +196,13 @@ class AuthContextMiddleware(wsgi.Middleware):
LOG.warning(msg)
return
# NOTE(gyee): token takes precedence over SSL client certificates.
# This will preserve backward compatibility with the existing
# behavior. Tokenless authorization with X.509 SSL client
# certificate is effectively disabled if no trusted issuers are
# provided.
if core.AUTH_TOKEN_HEADER in request.headers:
token_id = request.headers[core.AUTH_TOKEN_HEADER].strip()
request_context.auth_token = token_id
auth_context, token_id, is_admin = self._build_auth_context(request)
auth_context = self._build_auth_context(request, token_id)
elif self._validate_trusted_issuer(request.environ):
auth_context = self._build_tokenless_auth_context(
request.environ)
else:
LOG.debug('There is either no auth token in the request or '
'the certificate issuer is not trusted. No auth '
'context will be set.')
request_context.auth_token = token_id
request_context.is_admin = is_admin
if auth_context is None:
# The client didn't send any auth info, so don't set auth context.
return
# The attributes of request_context are put into the logs. This is a
@ -182,7 +214,6 @@ class AuthContextMiddleware(wsgi.Middleware):
request_context.domain = auth_context.get('domain_id')
request_context.user_domain = auth_context.get('user_domain_id')
request_context.project_domain = auth_context.get('project_domain_id')
request_context.is_admin = request.environ.get('is_admin', False)
LOG.debug('RBAC: auth_context: %s', auth_context)
request.environ[authorization.AUTH_CONTEXT_ENV] = auth_context

View File

@ -14,6 +14,7 @@
import uuid
import mock
from oslo_config import cfg
import oslo_context.context
from oslo_serialization import jsonutils
@ -28,6 +29,7 @@ from keystone.common import cache
from keystone.common.validation import validators
from keystone import exception
from keystone import middleware
from keystone.middleware import auth as middleware_auth
from keystone.tests.common import auth as common_auth
from keystone.tests import unit
from keystone.tests.unit import rest
@ -1345,12 +1347,27 @@ class AuthContextMiddlewareAdminTokenTestCase(RestfulTestCase):
self.assertEqual('body', resp.text) # just to make sure it worked
return resp.request
def test_admin_token_auth_context(self):
# test to make sure AuthContextMiddleware does not attempt to build
# auth context if the incoming auth token is the special admin token
def test_admin_auth_context(self):
# test to make sure AuthContextMiddleware does not attempt to build the
# auth context if the admin_token middleware indicates it's admin
# already.
token_id = uuid.uuid4().hex # token doesn't matter.
# the admin_token middleware sets is_admin in the context.
extra_environ = {middleware.CONTEXT_ENV: {'is_admin': True}}
req = self._middleware_request(token_id, extra_environ)
auth_context = req.environ.get(authorization.AUTH_CONTEXT_ENV)
self.assertDictEqual({}, auth_context)
@mock.patch.object(middleware_auth.versionutils,
'report_deprecated_feature')
def test_admin_token_auth_context_deprecated(self, mock_report_deprecated):
# For backwards compatibility AuthContextMiddleware will check that the
# admin token (as configured in the CONF file) is present and not
# attempt to build the auth context. This is deprecated.
req = self._middleware_request(CONF.admin_token)
auth_context = req.environ.get(authorization.AUTH_CONTEXT_ENV)
self.assertDictEqual({}, auth_context)
self.assertEqual(1, mock_report_deprecated.call_count)
# NOTE(gyee): test AuthContextMiddleware here instead of test_middleware.py

View File

@ -0,0 +1,14 @@
---
upgrade:
- >
[`bug 1473553 <https://bugs.launchpad.net/keystone/+bug/1473553>`_]
The ``keystone-paste.ini`` must be updated to put the ``admin_token_auth``
middleware before ``build_auth_context``. See the sample
``keystone-paste.ini`` for the correct ``pipeline`` value. Having
``admin_token_auth`` after ``build_auth_context`` is deprecated and will
not be supported in a future release.
deprecations:
- >
[`blueprint deprecated-as-of-mitaka <https://blueprints.launchpad.net/keystone/+spec/deprecated-as-of-mitaka>`_]
The ``admin_token_auth`` filter must now be placed before the
``build_auth_context`` filter in ``keystone-paste.ini``.