providers/oauth2: add proper support for non-http schemes as redirect URIs
closes #772 Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
5112ef9331
commit
d616bdd5d6
|
@ -166,7 +166,7 @@ class TestViewsAuthorize(TestCase):
|
|||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=flow,
|
||||
redirect_uris="http://localhost",
|
||||
redirect_uris="foo://localhost",
|
||||
)
|
||||
Application.objects.create(name="app", slug="app", provider=provider)
|
||||
state = generate_client_id()
|
||||
|
@ -179,7 +179,7 @@ class TestViewsAuthorize(TestCase):
|
|||
"response_type": "code",
|
||||
"client_id": "test",
|
||||
"state": state,
|
||||
"redirect_uri": "http://localhost",
|
||||
"redirect_uri": "foo://localhost",
|
||||
},
|
||||
)
|
||||
response = self.client.get(
|
||||
|
@ -190,7 +190,7 @@ class TestViewsAuthorize(TestCase):
|
|||
force_str(response.content),
|
||||
{
|
||||
"type": ChallengeTypes.REDIRECT.value,
|
||||
"to": f"http://localhost?code={code.code}&state={state}",
|
||||
"to": f"foo://localhost?code={code.code}&state={state}",
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
import re
|
||||
from base64 import b64decode
|
||||
from binascii import Error
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.utils.cache import patch_vary_headers
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
|
@ -161,3 +162,18 @@ def protected_resource_view(scopes: list[str]):
|
|||
return view_wrapper
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class HttpResponseRedirectScheme(HttpResponseRedirect):
|
||||
"""HTTP Response to redirect, can be to a non-http scheme"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
redirect_to: str,
|
||||
*args: Any,
|
||||
allowed_schemes: Optional[list[str]] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
self.allowed_schemes = allowed_schemes or ["http", "https", "ftp"]
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
super().__init__(redirect_to, *args, **kwargs)
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
from dataclasses import dataclass, field
|
||||
from datetime import timedelta
|
||||
from typing import Optional
|
||||
from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit
|
||||
from urllib.parse import parse_qs, urlencode, urlparse, urlsplit, urlunsplit
|
||||
from uuid import uuid4
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http.response import Http404, HttpResponseBadRequest, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
from structlog.stdlib import get_logger
|
||||
|
@ -46,6 +46,7 @@ from authentik.providers.oauth2.models import (
|
|||
OAuth2Provider,
|
||||
ResponseTypes,
|
||||
)
|
||||
from authentik.providers.oauth2.utils import HttpResponseRedirectScheme
|
||||
from authentik.providers.oauth2.views.userinfo import UserInfoView
|
||||
from authentik.stages.consent.models import ConsentMode, ConsentStage
|
||||
from authentik.stages.consent.stage import (
|
||||
|
@ -233,6 +234,11 @@ class OAuthFulfillmentStage(StageView):
|
|||
params: OAuthAuthorizationParams
|
||||
provider: OAuth2Provider
|
||||
|
||||
def redirect(self, uri: str) -> HttpResponse:
|
||||
"""Redirect using HttpResponseRedirectScheme, compatible with non-http schemes"""
|
||||
parsed = urlparse(uri)
|
||||
return HttpResponseRedirectScheme(uri, allowed_schemes=[parsed.scheme])
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""final Stage of an OAuth2 Flow"""
|
||||
|
@ -261,7 +267,7 @@ class OAuthFulfillmentStage(StageView):
|
|||
flow=self.executor.plan.flow_pk,
|
||||
scopes=", ".join(self.params.scope),
|
||||
).from_http(self.request)
|
||||
return redirect(self.create_response_uri())
|
||||
return self.redirect(self.create_response_uri())
|
||||
except (ClientIdError, RedirectUriError) as error:
|
||||
error.to_event(application=application).from_http(request)
|
||||
self.executor.stage_invalid()
|
||||
|
@ -270,7 +276,7 @@ class OAuthFulfillmentStage(StageView):
|
|||
except AuthorizeError as error:
|
||||
error.to_event(application=application).from_http(request)
|
||||
self.executor.stage_invalid()
|
||||
return redirect(error.create_uri())
|
||||
return self.redirect(error.create_uri())
|
||||
|
||||
def create_response_uri(self) -> str:
|
||||
"""Create a final Response URI the user is redirected to."""
|
||||
|
|
Reference in a new issue