From 1c0c4c82efd5f27e16b4ea40da34e9d145ee72a2 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Fri, 30 May 2025 12:47:29 +0200 Subject: [PATCH] Fix getting token from application credentials token When user tries to get token from token that was initially issued using application credentials it is necessary to restore the initial application credential is to enforce it's scope, roles and access rules. Closes-bug: #2111836 Change-Id: Ie94f7e18106b50087284bd8c81b50aa50ab104cb Signed-off-by: Artem Goncharov (cherry picked from commit 2323c474f857b64b5e6159dab03e64580817c65c) --- keystone/api/_shared/authentication.py | 16 +++++++++++++++- keystone/auth/plugins/token.py | 19 +++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) 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.