From a9909fcf6ddb28e492d17a2321222eac5f3a1ce3 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Fri, 25 Nov 2022 11:21:59 +0100 Subject: [PATCH] providers/oauth2: set amr values based on login event closes #4070 Signed-off-by: Jens Langhammer --- authentik/api/authentication.py | 3 ++- authentik/providers/oauth2/constants.py | 6 +++++ authentik/providers/oauth2/models.py | 34 +++++++++++++++++-------- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/authentik/api/authentication.py b/authentik/api/authentication.py index 6a4b297be..1077812f9 100644 --- a/authentik/api/authentication.py +++ b/authentik/api/authentication.py @@ -11,7 +11,6 @@ from authentik.core.middleware import CTX_AUTH_VIA from authentik.core.models import Token, TokenIntents, User from authentik.outposts.models import Outpost from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API -from authentik.providers.oauth2.models import RefreshToken LOGGER = get_logger() @@ -33,6 +32,8 @@ def validate_auth(header: bytes) -> Optional[str]: def bearer_auth(raw_header: bytes) -> Optional[User]: """raw_header in the Format of `Bearer ....`""" + from authentik.providers.oauth2.models import RefreshToken + auth_credentials = validate_auth(raw_header) if not auth_credentials: return None diff --git a/authentik/providers/oauth2/constants.py b/authentik/providers/oauth2/constants.py index fb45587cf..2d88ef8b6 100644 --- a/authentik/providers/oauth2/constants.py +++ b/authentik/providers/oauth2/constants.py @@ -31,3 +31,9 @@ SCOPE_GITHUB_USER_EMAIL = "user:email" SCOPE_GITHUB_ORG_READ = "read:org" ACR_AUTHENTIK_DEFAULT = "goauthentik.io/providers/oauth2/default" + +# https://datatracker.ietf.org/doc/html/draft-ietf-oauth-amr-values-06#section-2 +AMR_PASSWORD = "pwd" # nosec +AMR_MFA = "mfa" +AMR_OTP = "otp" +AMR_WEBAUTHN = "user" diff --git a/authentik/providers/oauth2/models.py b/authentik/providers/oauth2/models.py index 9d22aac6b..128a32a23 100644 --- a/authentik/providers/oauth2/models.py +++ b/authentik/providers/oauth2/models.py @@ -20,14 +20,21 @@ from rest_framework.serializers import Serializer from authentik.core.models import ExpiringModel, PropertyMapping, Provider, User from authentik.crypto.models import CertificateKeyPair -from authentik.events.models import Event, EventAction -from authentik.events.utils import get_user +from authentik.events.models import Event +from authentik.events.signals import SESSION_LOGIN_EVENT from authentik.lib.generators import generate_code_fixed_length, generate_id, generate_key from authentik.lib.models import SerializerModel from authentik.lib.utils.time import timedelta_string_validator from authentik.providers.oauth2.apps import AuthentikProviderOAuth2Config -from authentik.providers.oauth2.constants import ACR_AUTHENTIK_DEFAULT +from authentik.providers.oauth2.constants import ( + ACR_AUTHENTIK_DEFAULT, + AMR_MFA, + AMR_OTP, + AMR_PASSWORD, + AMR_WEBAUTHN, +) from authentik.sources.oauth.models import OAuthSource +from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS class ClientTypes(models.TextChoices): @@ -392,6 +399,7 @@ class IDToken: iat: Optional[int] = None auth_time: Optional[int] = None acr: Optional[str] = ACR_AUTHENTIK_DEFAULT + amr: Optional[list[str]] = None c_hash: Optional[str] = None nonce: Optional[str] = None @@ -485,21 +493,27 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel): f"selected: {self.provider.sub_mode}" ) ) - + amr = [] # Convert datetimes into timestamps. now = datetime.now() iat_time = int(now.timestamp()) exp_time = int(self.expires.timestamp()) # We use the timestamp of the user's last successful login (EventAction.LOGIN) for auth_time - auth_event = ( - Event.objects.filter(action=EventAction.LOGIN, user=get_user(user)) - .order_by("-created") - .first() - ) # Fallback in case we can't find any login events auth_time = now - if auth_event: + if SESSION_LOGIN_EVENT in request.session: + auth_event: Event = request.session[SESSION_LOGIN_EVENT] auth_time = auth_event.created + # Also check which method was used for authentication + method = auth_event.context.get(PLAN_CONTEXT_METHOD, "") + method_args = auth_event.context.get(PLAN_CONTEXT_METHOD_ARGS, {}) + if method == "password": + amr.append(AMR_PASSWORD) + if method == "auth_webauthn_pwl": + amr.append(AMR_WEBAUTHN) + if "mfa_devices" in method_args: + if len(amr) > 0: + amr.append(AMR_MFA) auth_timestamp = int(auth_time.timestamp())