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:
		@ -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 New Issue
	
	Block a user