providers/oauth2: set amr values based on login event

closes #4070

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-11-25 11:21:59 +01:00
parent 1fa9b3a996
commit a9909fcf6d
3 changed files with 32 additions and 11 deletions

View File

@ -11,7 +11,6 @@ from authentik.core.middleware import CTX_AUTH_VIA
from authentik.core.models import Token, TokenIntents, User from authentik.core.models import Token, TokenIntents, User
from authentik.outposts.models import Outpost from authentik.outposts.models import Outpost
from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API
from authentik.providers.oauth2.models import RefreshToken
LOGGER = get_logger() LOGGER = get_logger()
@ -33,6 +32,8 @@ def validate_auth(header: bytes) -> Optional[str]:
def bearer_auth(raw_header: bytes) -> Optional[User]: def bearer_auth(raw_header: bytes) -> Optional[User]:
"""raw_header in the Format of `Bearer ....`""" """raw_header in the Format of `Bearer ....`"""
from authentik.providers.oauth2.models import RefreshToken
auth_credentials = validate_auth(raw_header) auth_credentials = validate_auth(raw_header)
if not auth_credentials: if not auth_credentials:
return None return None

View File

@ -31,3 +31,9 @@ SCOPE_GITHUB_USER_EMAIL = "user:email"
SCOPE_GITHUB_ORG_READ = "read:org" SCOPE_GITHUB_ORG_READ = "read:org"
ACR_AUTHENTIK_DEFAULT = "goauthentik.io/providers/oauth2/default" 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"

View File

@ -20,14 +20,21 @@ from rest_framework.serializers import Serializer
from authentik.core.models import ExpiringModel, PropertyMapping, Provider, User from authentik.core.models import ExpiringModel, PropertyMapping, Provider, User
from authentik.crypto.models import CertificateKeyPair from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction from authentik.events.models import Event
from authentik.events.utils import get_user from authentik.events.signals import SESSION_LOGIN_EVENT
from authentik.lib.generators import generate_code_fixed_length, generate_id, generate_key from authentik.lib.generators import generate_code_fixed_length, generate_id, generate_key
from authentik.lib.models import SerializerModel from authentik.lib.models import SerializerModel
from authentik.lib.utils.time import timedelta_string_validator from authentik.lib.utils.time import timedelta_string_validator
from authentik.providers.oauth2.apps import AuthentikProviderOAuth2Config 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.sources.oauth.models import OAuthSource
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
class ClientTypes(models.TextChoices): class ClientTypes(models.TextChoices):
@ -392,6 +399,7 @@ class IDToken:
iat: Optional[int] = None iat: Optional[int] = None
auth_time: Optional[int] = None auth_time: Optional[int] = None
acr: Optional[str] = ACR_AUTHENTIK_DEFAULT acr: Optional[str] = ACR_AUTHENTIK_DEFAULT
amr: Optional[list[str]] = None
c_hash: Optional[str] = None c_hash: Optional[str] = None
nonce: Optional[str] = None nonce: Optional[str] = None
@ -485,21 +493,27 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
f"selected: {self.provider.sub_mode}" f"selected: {self.provider.sub_mode}"
) )
) )
amr = []
# Convert datetimes into timestamps. # Convert datetimes into timestamps.
now = datetime.now() now = datetime.now()
iat_time = int(now.timestamp()) iat_time = int(now.timestamp())
exp_time = int(self.expires.timestamp()) exp_time = int(self.expires.timestamp())
# We use the timestamp of the user's last successful login (EventAction.LOGIN) for auth_time # 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 # Fallback in case we can't find any login events
auth_time = now 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 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()) auth_timestamp = int(auth_time.timestamp())