Merge "Auth Method Handlers now return a response object always"

This commit is contained in:
Jenkins 2017-01-31 14:04:20 +00:00 committed by Gerrit Code Review
commit d4a1bbda0b
9 changed files with 57 additions and 16 deletions

@ -433,8 +433,10 @@ class Auth(controller.V3Controller):
self._check_and_set_default_scoping(auth_info, auth_context)
(domain_id, project_id, trust, unscoped) = auth_info.get_scope()
method_names = auth_info.get_method_names()
method_names += auth_context.get('method_names', [])
# NOTE(notmorgan): only methods that actually run and succeed will
# be in the auth_context['method_names'] list. Do not blindly take
# the values from auth_info, look at the authoritative values.
method_names = auth_context.get('method_names', [])
# make sure the list is unique
method_names = list(set(method_names))
expires_at = auth_context.get('expires_at')
@ -527,6 +529,7 @@ class Auth(controller.V3Controller):
# of the attributes set by the plugins. This check to ensure
# `auth_context` is an instance of AuthContext is extra insurance and
# will prevent regressions.
if not isinstance(auth_context, AuthContext):
LOG.error(
_LE('`auth_context` passed to the Auth controller '
@ -542,9 +545,15 @@ class Auth(controller.V3Controller):
if request.remote_user:
try:
external = get_auth_method('external')
external.authenticate(request,
auth_info,
auth_context)
resp = external.authenticate(request,
auth_info,
auth_context)
if resp and resp.status:
# NOTE(notmorgan): ``external`` plugin cannot be multi-step
# it is either a plain success/fail.
auth_context.setdefault(
'method_names', []).insert(0, 'external')
except exception.AuthMethodNotSupported:
# This will happen there is no 'external' plugin registered
# and the container is performing authentication.
@ -567,8 +576,12 @@ class Auth(controller.V3Controller):
auth_info.get_method_data(method_name),
auth_context)
if resp:
auth_response['methods'].append(method_name)
auth_response[method_name] = resp
if resp.status:
auth_context.setdefault(
'method_names', []).insert(0, method_name)
elif resp.response_body:
auth_response['methods'].append(method_name)
auth_response[method_name] = resp.response_body
if auth_response["methods"]:
# authentication continuation required

@ -13,12 +13,17 @@
# under the License.
import abc
import collections
import six
from keystone import exception
AuthHandlerResponse = collections.namedtuple('AuthHandlerResponse',
'status, response_body')
@six.add_metaclass(abc.ABCMeta)
class AuthMethodHandler(object):
"""Abstract base class for an authentication plugin."""
@ -45,7 +50,8 @@ class AuthMethodHandler(object):
``method_name`` is used to convey any additional authentication methods
in case authentication is for re-scoping. For example, if the
authentication is for re-scoping, plugin must append the previous
method names into ``method_names``. Also, plugin may add any additional
method names into ``method_names``; NOTE: This behavior is exclusive
to the re-scope type action. Also, plugin may add any additional
information into ``extras``. Anything in ``extras`` will be conveyed in
the token's ``extras`` attribute. Here's an example of ``auth_context``
on successful authentication::
@ -88,10 +94,11 @@ class AuthMethodHandler(object):
}
}
:returns: None if authentication is successful.
Authentication payload in the form of a dictionary for the
next authentication step if this is a multi step
authentication.
:returns: AuthHandlerResponse with status set to ``True`` if auth was
successful. If `status` is ``False`` and this is a multi-step
auth, the ``response_body`` can be in a form of a dict for
the next step in authentication.
:raises keystone.exception.Unauthorized: for authentication failure
"""
raise exception.Unauthorized()

@ -52,6 +52,8 @@ class Base(base.AuthMethodHandler):
if 'kerberos' in CONF.token.bind and auth_type == 'negotiate':
auth_context['bind']['kerberos'] = user_ref['name']
return base.AuthHandlerResponse(status=True, response_body=None)
@abc.abstractmethod
def _authenticate(self, request):
"""Look up the user in the identity backend.

@ -73,6 +73,8 @@ class Mapped(base.AuthMethodHandler):
self.assignment_api,
self.role_api)
return base.AuthHandlerResponse(status=True, response_body=None)
def handle_scoped_token(request, auth_context, token_ref,
federation_api, identity_api):

@ -62,3 +62,5 @@ class OAuth(base.AuthMethodHandler):
auth_context['user_id'] = acc_token['authorizing_user_id']
auth_context['access_token_id'] = access_token_id
auth_context['project_id'] = acc_token['project_id']
return base.AuthHandlerResponse(status=True, response_body=None)

@ -40,3 +40,5 @@ class Password(base.AuthMethodHandler):
raise exception.Unauthorized(msg)
auth_context['user_id'] = user_info.user_id
return base.AuthHandlerResponse(status=True, response_body=None)

@ -51,6 +51,8 @@ class Token(base.AuthMethodHandler):
else:
token_authenticate(request, auth_context, token_ref)
return base.AuthHandlerResponse(status=True, response_body=None)
def token_authenticate(request, user_context, token_ref):
try:
@ -98,7 +100,11 @@ def token_authenticate(request, user_context, token_ref):
# TODO(morganfainberg: determine if token 'extras' can be removed
# from the user_context
user_context['extras'].update(token_ref.get('extras', {}))
user_context['method_names'].extend(token_ref.methods)
# NOTE(notmorgan): The Token auth method is *very* special and sets the
# previous values to the method_names. This is because it can be used
# for re-scoping and we want to maintain the values. Most
# AuthMethodHandlers do no such thing and this is not required.
user_context.setdefault('method_names', []).extend(token_ref.methods)
except AssertionError as e:
LOG.error(six.text_type(e))

@ -97,3 +97,5 @@ class TOTP(base.AuthMethodHandler):
raise exception.Unauthorized(msg)
auth_context['user_id'] = user_info.user_id
return base.AuthHandlerResponse(status=True, response_body=None)

@ -34,13 +34,18 @@ DEMO_USER_ID = uuid.uuid4().hex
class SimpleChallengeResponse(base.AuthMethodHandler):
def authenticate(self, context, auth_payload, user_context):
def authenticate(self, context, auth_payload, auth_context):
if 'response' in auth_payload:
if auth_payload['response'] != EXPECTED_RESPONSE:
raise exception.Unauthorized('Wrong answer')
user_context['user_id'] = DEMO_USER_ID
auth_context['user_id'] = DEMO_USER_ID
return base.AuthHandlerResponse(status=True, response_body=None)
else:
return {"challenge": "What's the name of your high school?"}
return base.AuthHandlerResponse(
status=False,
response_body={
"challenge": "What's the name of your high school?"})
class TestAuthPlugin(unit.SQLDriverOverrides, unit.TestCase):