providers/oauth2: implement max_age param

This commit is contained in:
Jens Langhammer 2020-12-26 20:05:31 +01:00
parent 29edbb0357
commit 43bb29e16a
2 changed files with 32 additions and 4 deletions

View file

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

View file

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