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.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

View file

@ -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"

View file

@ -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())