diff --git a/keystone/api/_shared/authentication.py b/keystone/api/_shared/authentication.py index e1839566f2..7b1311087c 100644 --- a/keystone/api/_shared/authentication.py +++ b/keystone/api/_shared/authentication.py @@ -183,6 +183,13 @@ def authenticate(auth_info, auth_context): resp_method_names = resp.response_data.pop('method_names', []) auth_context['method_names'].extend(resp_method_names) auth_context.update(resp.response_data or {}) + # NOTE(gtema): When trying to get token from + # application_credential based token we need to restore + # application_credential_id to prevent escaping its bounds. + if "application_credential_id" in resp: + auth_context["application_credential_id"] = resp[ + "application_credential_id" + ] elif resp.response_body: auth_response['methods'].append(method_name) auth_response[method_name] = resp.response_body @@ -231,7 +238,14 @@ def authenticate_for_token(auth=None): app_cred_id = None if 'application_credential' in method_names: token_auth = auth_info.auth['identity'] - app_cred_id = token_auth['application_credential']['id'] + if "application_credential" in token_auth: + app_cred_id = token_auth['application_credential']['id'] + elif "application_credential_id" in auth_context: + app_cred_id = auth_context["application_credential_id"] + else: + raise exception.MissingApplicationCredentialId( + user_id=auth_context['user_id'] + ) # Do MFA Rule Validation for the user if not core.UserMFARulesValidator.check_auth_methods_against_rules( diff --git a/keystone/auth/plugins/token.py b/keystone/auth/plugins/token.py index 31675e1952..3347a1eb68 100644 --- a/keystone/auth/plugins/token.py +++ b/keystone/auth/plugins/token.py @@ -14,6 +14,7 @@ import flask from oslo_log import log +import typing as ty from keystone.auth.plugins import base from keystone.auth.plugins import mapped @@ -21,6 +22,7 @@ from keystone.common import provider_api import keystone.conf from keystone import exception from keystone.i18n import _ +from keystone.models.token_model import TokenModel LOG = log.getLogger(__name__) @@ -49,13 +51,12 @@ class Token(base.AuthMethodHandler): # for re-scoping and we want to maintain the values. Most # AuthMethodHandlers do no such thing and this is not required. response_data.setdefault('method_names', []).extend(token.methods) - return base.AuthHandlerResponse( status=True, response_body=None, response_data=response_data ) -def token_authenticate(token): +def token_authenticate(token: TokenModel) -> dict[str, ty.Any]: response_data = {} try: # Do not allow tokens used for delegation to @@ -88,6 +89,20 @@ def token_authenticate(token): 'or domain-scoped token is not allowed.' ) ) + elif token.application_credential: + # NOTE(gtema): when getting token from token (initially issued by + # application credential) it is necessary to ensure scope is not + # requested. + if project_scoped or domain_scoped: + raise exception.ForbiddenAction( + action=_( + "Using an application credential token to create a " + "project-scoped or domain-scoped token is not allowed." + ) + ) + response_data["application_credential_id"] = ( + token.application_credential["id"] + ) if not CONF.token.allow_rescope_scoped_token: # Do not allow conversion from scoped tokens.