start fixing tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens Langhammer 2023-02-21 21:52:30 +01:00
parent 1889e82309
commit ff996f798f
No known key found for this signature in database
10 changed files with 88 additions and 41 deletions

View File

@ -10,7 +10,6 @@ from django.db.models.functions import ExtractHour
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.db.transaction import atomic from django.db.transaction import atomic
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from django.urls import reverse_lazy
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.timezone import now from django.utils.timezone import now
@ -72,6 +71,8 @@ from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import FlowToken from authentik.flows.models import FlowToken
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner
from authentik.flows.views.executor import QS_KEY_TOKEN from authentik.flows.views.executor import QS_KEY_TOKEN
from authentik.interfaces.models import InterfaceType
from authentik.interfaces.views import reverse_interface
from authentik.stages.email.models import EmailStage from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage from authentik.stages.email.utils import TemplateEmailMessage
@ -350,8 +351,12 @@ class UserViewSet(UsedByMixin, ModelViewSet):
) )
querystring = urlencode({QS_KEY_TOKEN: token.key}) querystring = urlencode({QS_KEY_TOKEN: token.key})
link = self.request.build_absolute_uri( link = self.request.build_absolute_uri(
reverse_lazy("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) reverse_interface(
+ f"?{querystring}" self.request,
InterfaceType.FLOW,
flow_slug=flow.slug,
),
+f"?{querystring}",
) )
return link, token return link, token

View File

@ -60,5 +60,5 @@ class TestImpersonation(TestCase):
response = self.client.get(reverse("authentik_core:impersonate-end")) response = self.client.get(reverse("authentik_core:impersonate-end"))
self.assertRedirects( self.assertRedirects(
response, reverse("authentik_interfaces:if", kwargs={"if_name", "user"}) response, reverse("authentik_interfaces:if", kwargs={"if_name": "user"})
) )

View File

@ -25,6 +25,8 @@ from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.flows.planner import CACHE_PREFIX, PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key from authentik.flows.planner import CACHE_PREFIX, PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
from authentik.flows.views.executor import SESSION_KEY_HISTORY, SESSION_KEY_PLAN from authentik.flows.views.executor import SESSION_KEY_HISTORY, SESSION_KEY_PLAN
from authentik.interfaces.models import InterfaceType
from authentik.interfaces.views import reverse_interface
from authentik.lib.utils.file import ( from authentik.lib.utils.file import (
FilePathSerializer, FilePathSerializer,
FileUploadSerializer, FileUploadSerializer,
@ -294,7 +296,10 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
return Response( return Response(
{ {
"link": request._request.build_absolute_uri( "link": request._request.build_absolute_uri(
reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) reverse_interface(
InterfaceType.FLOW,
flow_slug=flow.slug,
),
) )
} }
) )

View File

@ -7,6 +7,8 @@ from authentik.core.tests.utils import create_test_flow
from authentik.flows.models import Flow, FlowDesignation from authentik.flows.models import Flow, FlowDesignation
from authentik.flows.planner import FlowPlan from authentik.flows.planner import FlowPlan
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_PLAN from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_PLAN
from authentik.interfaces.models import InterfaceType
from authentik.interfaces.tests import reverse_interface
from authentik.lib.generators import generate_id from authentik.lib.generators import generate_id
from authentik.providers.oauth2.models import OAuth2Provider from authentik.providers.oauth2.models import OAuth2Provider
@ -21,7 +23,10 @@ class TestHelperView(TestCase):
response = self.client.get( response = self.client.get(
reverse("authentik_flows:default-invalidation"), reverse("authentik_flows:default-invalidation"),
) )
expected_url = reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) expected_url = reverse_interface(
InterfaceType.FLOW,
flow_slug=flow.slug,
)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, expected_url) self.assertEqual(response.url, expected_url)
@ -72,6 +77,9 @@ class TestHelperView(TestCase):
response = self.client.get( response = self.client.get(
reverse("authentik_flows:default-invalidation"), reverse("authentik_flows:default-invalidation"),
) )
expected_url = reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) expected_url = reverse_interface(
InterfaceType.FLOW,
flow_slug=flow.slug,
)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, expected_url) self.assertEqual(response.url, expected_url)

View File

@ -25,3 +25,4 @@ class InterfaceViewSet(UsedByMixin, ModelViewSet):
queryset = Interface.objects.all() queryset = Interface.objects.all()
serializer_class = InterfaceSerializer serializer_class = InterfaceSerializer
filterset_fields = ["url_name", "type", "template"] filterset_fields = ["url_name", "type", "template"]
search_fields = ["url_name", "type", "template"]

View File

@ -0,0 +1,12 @@
"""Interface tests"""
from django.test import RequestFactory
from authentik.interfaces.models import InterfaceType
from authentik.interfaces.views import reverse_interface as full_reverse_interface
def reverse_interface(interface_type: InterfaceType, **kwargs):
"""reverse_interface wrapper for tests"""
factory = RequestFactory()
request = factory.get("/")
return full_reverse_interface(request, interface_type, **kwargs)

View File

@ -1,11 +1,13 @@
"""Interface views""" """Interface views"""
from json import dumps from json import dumps
from typing import Any, Optional from typing import Any, Optional
from urllib.parse import urlencode
from django.http import Http404, HttpRequest, HttpResponse from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404, redirect
from django.template import Template, TemplateSyntaxError, engines from django.template import Template, TemplateSyntaxError, engines
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views import View from django.views import View
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
@ -40,30 +42,37 @@ def redirect_to_default_interface(request: HttpRequest, interface_type: Interfac
return RedirectToInterface.as_view(type=interface_type)(request, **kwargs) return RedirectToInterface.as_view(type=interface_type)(request, **kwargs)
def reverse_interface(request: HttpRequest, interface_type: InterfaceType, **kwargs):
"""Reverse URL to configured default interface"""
tenant: Tenant = request.tenant
interface: Interface = None
if interface_type == InterfaceType.USER:
interface = tenant.interface_user
if interface_type == InterfaceType.ADMIN:
interface = tenant.interface_admin
if interface_type == InterfaceType.FLOW:
interface = tenant.interface_flow
if not interface:
raise Http404()
kwargs["if_name"] = interface.url_name
return reverse(
"authentik_interfaces:if",
kwargs=kwargs,
)
class RedirectToInterface(View): class RedirectToInterface(View):
"""Redirect to tenant's configured view for specified type""" """Redirect to tenant's configured view for specified type"""
type: Optional[InterfaceType] = None type: Optional[InterfaceType] = None
def dispatch(self, request: HttpRequest, **kwargs: Any) -> HttpResponse: def dispatch(self, request: HttpRequest, **kwargs: Any) -> HttpResponse:
tenant: Tenant = request.tenant target = reverse_interface(request, self.type, **kwargs)
interface: Interface = None if self.request.GET:
target += "?" + urlencode(self.request.GET.items())
if self.type == InterfaceType.USER: return redirect(target)
interface = tenant.interface_user
if self.type == InterfaceType.ADMIN:
interface = tenant.interface_admin
if self.type == InterfaceType.FLOW:
interface = tenant.interface_flow
if not interface:
raise Http404()
return redirect_with_qs(
"authentik_interfaces:if",
self.request.GET,
if_name=interface.url_name,
**kwargs,
)
@method_decorator(ensure_csrf_cookie, name="dispatch") @method_decorator(ensure_csrf_cookie, name="dispatch")

View File

@ -16,6 +16,8 @@ from authentik.flows.models import FlowToken
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import QS_KEY_TOKEN from authentik.flows.views.executor import QS_KEY_TOKEN
from authentik.interfaces.models import InterfaceType
from authentik.interfaces.views import reverse_interface
from authentik.stages.email.models import EmailStage from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage from authentik.stages.email.utils import TemplateEmailMessage
@ -47,9 +49,10 @@ class EmailStageView(ChallengeStageView):
def get_full_url(self, **kwargs) -> str: def get_full_url(self, **kwargs) -> str:
"""Get full URL to be used in template""" """Get full URL to be used in template"""
base_url = reverse( base_url = reverse_interface(
"authentik_core:if-flow", self.request,
kwargs={"flow_slug": self.executor.flow.slug}, InterfaceType.FLOW,
flow_slug=self.executor.flow.slug,
) )
relative_url = f"{base_url}?{urlencode(kwargs)}" relative_url = f"{base_url}?{urlencode(kwargs)}"
return self.request.build_absolute_uri(relative_url) return self.request.build_absolute_uri(relative_url)

View File

@ -5,6 +5,8 @@ from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.challenge import ChallengeTypes from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import FlowDesignation, FlowStageBinding from authentik.flows.models import FlowDesignation, FlowStageBinding
from authentik.flows.tests import FlowTestCase from authentik.flows.tests import FlowTestCase
from authentik.interfaces.models import InterfaceType
from authentik.interfaces.tests import reverse_interface
from authentik.sources.oauth.models import OAuthSource from authentik.sources.oauth.models import OAuthSource
from authentik.stages.identification.models import IdentificationStage, UserFields from authentik.stages.identification.models import IdentificationStage, UserFields
from authentik.stages.password import BACKEND_INBUILT from authentik.stages.password import BACKEND_INBUILT
@ -166,9 +168,9 @@ class TestIdentificationStage(FlowTestCase):
component="ak-stage-identification", component="ak-stage-identification",
user_fields=["email"], user_fields=["email"],
password_fields=False, password_fields=False,
enroll_url=reverse( enroll_url=reverse_interface(
"authentik_core:if-flow", InterfaceType.FLOW,
kwargs={"flow_slug": flow.slug}, flow_slug=flow.slug,
), ),
show_source_labels=False, show_source_labels=False,
primary_action="Log in", primary_action="Log in",
@ -204,9 +206,9 @@ class TestIdentificationStage(FlowTestCase):
component="ak-stage-identification", component="ak-stage-identification",
user_fields=["email"], user_fields=["email"],
password_fields=False, password_fields=False,
recovery_url=reverse( recovery_url=reverse_interface(
"authentik_core:if-flow", InterfaceType.FLOW,
kwargs={"flow_slug": flow.slug}, flow_slug=flow.slug,
), ),
show_source_labels=False, show_source_labels=False,
primary_action="Log in", primary_action="Log in",

View File

@ -5,7 +5,6 @@ from django.contrib.auth import _clean_credentials
from django.contrib.auth.backends import BaseBackend from django.contrib.auth.backends import BaseBackend
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.fields import CharField from rest_framework.fields import CharField
@ -23,6 +22,8 @@ from authentik.flows.challenge import (
from authentik.flows.models import Flow, FlowDesignation, Stage from authentik.flows.models import Flow, FlowDesignation, Stage
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.interfaces.models import InterfaceType
from authentik.interfaces.views import reverse_interface
from authentik.lib.utils.reflection import path_to_class from authentik.lib.utils.reflection import path_to_class
from authentik.stages.password.models import PasswordStage from authentik.stages.password.models import PasswordStage
@ -95,11 +96,12 @@ class PasswordStageView(ChallengeStageView):
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
} }
) )
recovery_flow = Flow.objects.filter(designation=FlowDesignation.RECOVERY) recovery_flow = Flow.objects.filter(designation=FlowDesignation.RECOVERY).first()
if recovery_flow.exists(): if recovery_flow:
recover_url = reverse( recover_url = reverse_interface(
"authentik_core:if-flow", self.request,
kwargs={"flow_slug": recovery_flow.first().slug}, InterfaceType.FLOW,
flow_slug=recovery_flow.slug,
) )
challenge.initial_data["recovery_url"] = self.request.build_absolute_uri(recover_url) challenge.initial_data["recovery_url"] = self.request.build_absolute_uri(recover_url)
return challenge return challenge