providers/oauth2: implement max_age param
This commit is contained in:
parent
29edbb0357
commit
43bb29e16a
|
@ -4,6 +4,7 @@ GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code"
|
||||||
GRANT_TYPE_REFRESH_TOKEN = "refresh_token" # nosec
|
GRANT_TYPE_REFRESH_TOKEN = "refresh_token" # nosec
|
||||||
PROMPT_NONE = "none"
|
PROMPT_NONE = "none"
|
||||||
PROMPT_CONSNET = "consent"
|
PROMPT_CONSNET = "consent"
|
||||||
|
PROMPT_LOGIN = "login"
|
||||||
SCOPE_OPENID = "openid"
|
SCOPE_OPENID = "openid"
|
||||||
SCOPE_OPENID_PROFILE = "profile"
|
SCOPE_OPENID_PROFILE = "profile"
|
||||||
SCOPE_OPENID_EMAIL = "email"
|
SCOPE_OPENID_EMAIL = "email"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""authentik OAuth2 Authorization views"""
|
"""authentik OAuth2 Authorization views"""
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import timedelta
|
||||||
from typing import List, Optional, Set
|
from typing import List, Optional, Set
|
||||||
from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit
|
from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
@ -12,6 +13,7 @@ from structlog import get_logger
|
||||||
|
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
|
from authentik.events.utils import get_user
|
||||||
from authentik.flows.models import in_memory_stage
|
from authentik.flows.models import in_memory_stage
|
||||||
from authentik.flows.planner import (
|
from authentik.flows.planner import (
|
||||||
PLAN_CONTEXT_APPLICATION,
|
PLAN_CONTEXT_APPLICATION,
|
||||||
|
@ -27,6 +29,7 @@ from authentik.lib.views import bad_request_message
|
||||||
from authentik.policies.views import PolicyAccessView, RequestValidationError
|
from authentik.policies.views import PolicyAccessView, RequestValidationError
|
||||||
from authentik.providers.oauth2.constants import (
|
from authentik.providers.oauth2.constants import (
|
||||||
PROMPT_CONSNET,
|
PROMPT_CONSNET,
|
||||||
|
PROMPT_LOGIN,
|
||||||
PROMPT_NONE,
|
PROMPT_NONE,
|
||||||
SCOPE_OPENID,
|
SCOPE_OPENID,
|
||||||
)
|
)
|
||||||
|
@ -54,7 +57,7 @@ LOGGER = get_logger()
|
||||||
PLAN_CONTEXT_PARAMS = "params"
|
PLAN_CONTEXT_PARAMS = "params"
|
||||||
PLAN_CONTEXT_SCOPE_DESCRIPTIONS = "scope_descriptions"
|
PLAN_CONTEXT_SCOPE_DESCRIPTIONS = "scope_descriptions"
|
||||||
|
|
||||||
ALLOWED_PROMPT_PARAMS = {PROMPT_NONE, PROMPT_CONSNET}
|
ALLOWED_PROMPT_PARAMS = {PROMPT_NONE, PROMPT_CONSNET, PROMPT_LOGIN}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -72,6 +75,8 @@ class OAuthAuthorizationParams:
|
||||||
|
|
||||||
provider: OAuth2Provider = field(default_factory=OAuth2Provider)
|
provider: OAuth2Provider = field(default_factory=OAuth2Provider)
|
||||||
|
|
||||||
|
max_age: Optional[int] = None
|
||||||
|
|
||||||
code_challenge: Optional[str] = None
|
code_challenge: Optional[str] = None
|
||||||
code_challenge_method: Optional[str] = None
|
code_challenge_method: Optional[str] = None
|
||||||
|
|
||||||
|
@ -125,6 +130,7 @@ class OAuthAuthorizationParams:
|
||||||
prompt=ALLOWED_PROMPT_PARAMS.intersection(
|
prompt=ALLOWED_PROMPT_PARAMS.intersection(
|
||||||
set(query_dict.get("prompt", "").split())
|
set(query_dict.get("prompt", "").split())
|
||||||
),
|
),
|
||||||
|
max_age=query_dict.get("max_age"),
|
||||||
code_challenge=query_dict.get("code_challenge"),
|
code_challenge=query_dict.get("code_challenge"),
|
||||||
code_challenge_method=query_dict.get("code_challenge_method"),
|
code_challenge_method=query_dict.get("code_challenge_method"),
|
||||||
)
|
)
|
||||||
|
@ -183,6 +189,10 @@ class OAuthAuthorizationParams:
|
||||||
self.redirect_uri, "invalid_request", self.grant_type
|
self.redirect_uri, "invalid_request", self.grant_type
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# max_age directly from the Querystring will be a string
|
||||||
|
if self.max_age:
|
||||||
|
self.max_age = int(self.max_age)
|
||||||
|
|
||||||
def create_code(self, request: HttpRequest) -> AuthorizationCode:
|
def create_code(self, request: HttpRequest) -> AuthorizationCode:
|
||||||
"""Create an AuthorizationCode object for the request"""
|
"""Create an AuthorizationCode object for the request"""
|
||||||
code = AuthorizationCode()
|
code = AuthorizationCode()
|
||||||
|
@ -350,8 +360,9 @@ class AuthorizationFlowInitView(PolicyAccessView):
|
||||||
error = AuthorizeError(
|
error = AuthorizeError(
|
||||||
self.params.redirect_uri, "login_required", self.params.grant_type
|
self.params.redirect_uri, "login_required", self.params.grant_type
|
||||||
)
|
)
|
||||||
raise RequestValidationError(redirect(error.create_uri(
|
raise RequestValidationError(
|
||||||
self.params.redirect_uri, self.params.state)))
|
redirect(error.create_uri(self.params.redirect_uri, self.params.state))
|
||||||
|
)
|
||||||
|
|
||||||
def resolve_provider_application(self):
|
def resolve_provider_application(self):
|
||||||
client_id = self.request.GET.get("client_id")
|
client_id = self.request.GET.get("client_id")
|
||||||
|
@ -360,7 +371,23 @@ class AuthorizationFlowInitView(PolicyAccessView):
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
"""Check access to application, start FlowPLanner, return to flow executor shell"""
|
"""Start FlowPLanner, return to flow executor shell"""
|
||||||
|
# After we've checked permissions, and the user has access, check if we need
|
||||||
|
# to re-authenticate the user
|
||||||
|
if self.params.max_age:
|
||||||
|
current_age: timedelta = (
|
||||||
|
timezone.now()
|
||||||
|
- Event.objects.filter(
|
||||||
|
action=EventAction.LOGIN, user=get_user(self.request.user)
|
||||||
|
)
|
||||||
|
.latest("created")
|
||||||
|
.created
|
||||||
|
)
|
||||||
|
if current_age.total_seconds() > self.params.max_age:
|
||||||
|
return self.handle_no_permission()
|
||||||
|
# If prompt=login, we need to re-authenticate the user regardless
|
||||||
|
if PROMPT_LOGIN in self.params.prompt:
|
||||||
|
return self.handle_no_permission()
|
||||||
# Regardless, we start the planner and return to it
|
# Regardless, we start the planner and return to it
|
||||||
planner = FlowPlanner(self.provider.authorization_flow)
|
planner = FlowPlanner(self.provider.authorization_flow)
|
||||||
# planner.use_cache = False
|
# planner.use_cache = False
|
||||||
|
|
Reference in New Issue