From e4841d54a1c4e634e9c198c111754361dde01476 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 13 Dec 2021 23:56:01 +0100 Subject: [PATCH] *: migrate ui_* properties to functions to allow context being passed Signed-off-by: Jens Langhammer --- authentik/core/api/sources.py | 4 +-- authentik/core/models.py | 4 +-- authentik/core/tests/test_models.py | 9 ++++-- authentik/flows/api/stages.py | 2 +- authentik/flows/models.py | 1 - authentik/flows/tests/test_api.py | 2 +- authentik/flows/tests/test_stage_model.py | 2 +- authentik/sources/oauth/models.py | 13 ++++++--- authentik/sources/oauth/types/manager.py | 28 +++++++++---------- authentik/sources/plex/models.py | 5 ++-- authentik/sources/saml/models.py | 3 +- authentik/stages/authenticator_duo/models.py | 1 - authentik/stages/authenticator_sms/models.py | 1 - .../stages/authenticator_static/models.py | 1 - authentik/stages/authenticator_totp/models.py | 1 - .../stages/authenticator_webauthn/models.py | 1 - authentik/stages/identification/stage.py | 2 +- authentik/stages/password/models.py | 1 - 18 files changed, 38 insertions(+), 43 deletions(-) diff --git a/authentik/core/api/sources.py b/authentik/core/api/sources.py index 9d56e7be84..63c028bceb 100644 --- a/authentik/core/api/sources.py +++ b/authentik/core/api/sources.py @@ -104,14 +104,14 @@ class SourceViewSet( ) matching_sources: list[UserSettingSerializer] = [] for source in _all_sources: - user_settings = source.ui_user_settings + user_settings = source.ui_user_settings() if not user_settings: continue policy_engine = PolicyEngine(source, request.user, request) policy_engine.build() if not policy_engine.passing: continue - source_settings = source.ui_user_settings + source_settings = source.ui_user_settings() source_settings.initial_data["object_uid"] = source.slug if not source_settings.is_valid(): LOGGER.warning(source_settings.errors) diff --git a/authentik/core/models.py b/authentik/core/models.py index ad07d3c12e..136db1c149 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -359,13 +359,11 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel): """Return component used to edit this object""" raise NotImplementedError - @property - def ui_login_button(self) -> Optional[UILoginButton]: + def ui_login_button(self, request: HttpRequest) -> Optional[UILoginButton]: """If source uses a http-based flow, return UI Information about the login button. If source doesn't use http-based flow, return None.""" return None - @property def ui_user_settings(self) -> Optional[UserSettingSerializer]: """Entrypoint to integrate with User settings. Can either return None if no user settings are available, or UserSettingSerializer.""" diff --git a/authentik/core/tests/test_models.py b/authentik/core/tests/test_models.py index 9dc17d7244..1fa924f7b5 100644 --- a/authentik/core/tests/test_models.py +++ b/authentik/core/tests/test_models.py @@ -2,7 +2,7 @@ from time import sleep from typing import Callable, Type -from django.test import TestCase +from django.test import RequestFactory, TestCase from django.utils.timezone import now from guardian.shortcuts import get_anonymous_user @@ -30,6 +30,9 @@ class TestModels(TestCase): def source_tester_factory(test_model: Type[Stage]) -> Callable: """Test source""" + factory = RequestFactory() + request = factory.get("/") + def tester(self: TestModels): model_class = None if test_model._meta.abstract: @@ -38,8 +41,8 @@ def source_tester_factory(test_model: Type[Stage]) -> Callable: model_class = test_model() model_class.slug = "test" self.assertIsNotNone(model_class.component) - _ = model_class.ui_login_button - _ = model_class.ui_user_settings + _ = model_class.ui_login_button(request) + _ = model_class.ui_user_settings() return tester diff --git a/authentik/flows/api/stages.py b/authentik/flows/api/stages.py index 59cacb1ca6..d7a3948594 100644 --- a/authentik/flows/api/stages.py +++ b/authentik/flows/api/stages.py @@ -90,7 +90,7 @@ class StageViewSet( stages += list(configurable_stage.objects.all().order_by("name")) matching_stages: list[dict] = [] for stage in stages: - user_settings = stage.ui_user_settings + user_settings = stage.ui_user_settings() if not user_settings: continue user_settings.initial_data["object_uid"] = str(stage.pk) diff --git a/authentik/flows/models.py b/authentik/flows/models.py index dcfb715f03..fa466fb361 100644 --- a/authentik/flows/models.py +++ b/authentik/flows/models.py @@ -75,7 +75,6 @@ class Stage(SerializerModel): """Return component used to edit this object""" raise NotImplementedError - @property def ui_user_settings(self) -> Optional[UserSettingSerializer]: """Entrypoint to integrate with User settings. Can either return None if no user settings are available, or a challenge.""" diff --git a/authentik/flows/tests/test_api.py b/authentik/flows/tests/test_api.py index b2ee2d734a..4b1d173ba6 100644 --- a/authentik/flows/tests/test_api.py +++ b/authentik/flows/tests/test_api.py @@ -32,7 +32,7 @@ class TestFlowsAPI(APITestCase): def test_models(self): """Test that ui_user_settings returns none""" - self.assertIsNone(Stage().ui_user_settings) + self.assertIsNone(Stage().ui_user_settings()) def test_api_serializer(self): """Test that stage serializer returns the correct type""" diff --git a/authentik/flows/tests/test_stage_model.py b/authentik/flows/tests/test_stage_model.py index feb3af6908..807442de34 100644 --- a/authentik/flows/tests/test_stage_model.py +++ b/authentik/flows/tests/test_stage_model.py @@ -23,7 +23,7 @@ def model_tester_factory(test_model: Type[Stage]) -> Callable: model_class = test_model() self.assertTrue(issubclass(model_class.type, StageView)) self.assertIsNotNone(test_model.component) - _ = model_class.ui_user_settings + _ = model_class.ui_user_settings() return tester diff --git a/authentik/sources/oauth/models.py b/authentik/sources/oauth/models.py index 8da0f3646c..3d44f9e5e1 100644 --- a/authentik/sources/oauth/models.py +++ b/authentik/sources/oauth/models.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Optional, Type from django.db import models +from django.http.request import HttpRequest from django.urls import reverse from django.utils.translation import gettext_lazy as _ from rest_framework.serializers import Serializer @@ -63,11 +64,15 @@ class OAuthSource(Source): return OAuthSourceSerializer - @property - def ui_login_button(self) -> UILoginButton: - return self.type().ui_login_button() + def ui_login_button(self, request: HttpRequest) -> UILoginButton: + provider_type = self.type + provider = provider_type() + return UILoginButton( + name=self.name, + icon_url=provider.icon_url(), + challenge=provider.login_challenge(self, request), + ) - @property def ui_user_settings(self) -> Optional[UserSettingSerializer]: return UserSettingSerializer( data={ diff --git a/authentik/sources/oauth/types/manager.py b/authentik/sources/oauth/types/manager.py index a11f79ff79..b701712077 100644 --- a/authentik/sources/oauth/types/manager.py +++ b/authentik/sources/oauth/types/manager.py @@ -2,12 +2,13 @@ from enum import Enum from typing import Callable, Optional, Type +from django.http.request import HttpRequest from django.templatetags.static import static from django.urls.base import reverse from structlog.stdlib import get_logger -from authentik.core.types import UILoginButton -from authentik.flows.challenge import ChallengeTypes, RedirectChallenge +from authentik.flows.challenge import Challenge, ChallengeTypes, RedirectChallenge +from authentik.sources.oauth.models import OAuthSource from authentik.sources.oauth.views.callback import OAuthCallback from authentik.sources.oauth.views.redirect import OAuthRedirect @@ -40,20 +41,17 @@ class SourceType: """Get Icon URL for login""" return static(f"authentik/sources/{self.slug}.svg") - def ui_login_button(self) -> UILoginButton: + # pylint: disable=unused-argument + def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge: """Allow types to return custom challenges""" - return UILoginButton( - challenge=RedirectChallenge( - instance={ - "type": ChallengeTypes.REDIRECT.value, - "to": reverse( - "authentik_sources_oauth:oauth-client-login", - kwargs={"source_slug": self.slug}, - ), - } - ), - icon_url=self.icon_url(), - name=self.name, + return RedirectChallenge( + instance={ + "type": ChallengeTypes.REDIRECT.value, + "to": reverse( + "authentik_sources_oauth:oauth-client-login", + kwargs={"source_slug": self.slug}, + ), + } ) diff --git a/authentik/sources/plex/models.py b/authentik/sources/plex/models.py index 6ccb0293ac..c16fd28dde 100644 --- a/authentik/sources/plex/models.py +++ b/authentik/sources/plex/models.py @@ -3,6 +3,7 @@ from typing import Optional from django.contrib.postgres.fields import ArrayField from django.db import models +from django.http.request import HttpRequest from django.templatetags.static import static from django.utils.translation import gettext_lazy as _ from rest_framework.fields import CharField @@ -62,8 +63,7 @@ class PlexSource(Source): return PlexSourceSerializer - @property - def ui_login_button(self) -> UILoginButton: + def ui_login_button(self, request: HttpRequest) -> UILoginButton: return UILoginButton( challenge=PlexAuthenticationChallenge( { @@ -77,7 +77,6 @@ class PlexSource(Source): name=self.name, ) - @property def ui_user_settings(self) -> Optional[UserSettingSerializer]: return UserSettingSerializer( data={ diff --git a/authentik/sources/saml/models.py b/authentik/sources/saml/models.py index 4127667575..882050fc3b 100644 --- a/authentik/sources/saml/models.py +++ b/authentik/sources/saml/models.py @@ -167,8 +167,7 @@ class SAMLSource(Source): reverse(f"authentik_sources_saml:{view}", kwargs={"source_slug": self.slug}) ) - @property - def ui_login_button(self) -> UILoginButton: + def ui_login_button(self, request: HttpRequest) -> UILoginButton: return UILoginButton( challenge=RedirectChallenge( instance={ diff --git a/authentik/stages/authenticator_duo/models.py b/authentik/stages/authenticator_duo/models.py index 4e9db04781..e69e7366b5 100644 --- a/authentik/stages/authenticator_duo/models.py +++ b/authentik/stages/authenticator_duo/models.py @@ -48,7 +48,6 @@ class AuthenticatorDuoStage(ConfigurableStage, Stage): def component(self) -> str: return "ak-stage-authenticator-duo-form" - @property def ui_user_settings(self) -> Optional[UserSettingSerializer]: return UserSettingSerializer( data={ diff --git a/authentik/stages/authenticator_sms/models.py b/authentik/stages/authenticator_sms/models.py index 1fb9dbbdf6..1deaa89daf 100644 --- a/authentik/stages/authenticator_sms/models.py +++ b/authentik/stages/authenticator_sms/models.py @@ -141,7 +141,6 @@ class AuthenticatorSMSStage(ConfigurableStage, Stage): def component(self) -> str: return "ak-stage-authenticator-sms-form" - @property def ui_user_settings(self) -> Optional[UserSettingSerializer]: return UserSettingSerializer( data={ diff --git a/authentik/stages/authenticator_static/models.py b/authentik/stages/authenticator_static/models.py index 5b26ca8aa4..ad7f0f65ed 100644 --- a/authentik/stages/authenticator_static/models.py +++ b/authentik/stages/authenticator_static/models.py @@ -31,7 +31,6 @@ class AuthenticatorStaticStage(ConfigurableStage, Stage): def component(self) -> str: return "ak-stage-authenticator-static-form" - @property def ui_user_settings(self) -> Optional[UserSettingSerializer]: return UserSettingSerializer( data={ diff --git a/authentik/stages/authenticator_totp/models.py b/authentik/stages/authenticator_totp/models.py index 9b36fe3031..139a66f0be 100644 --- a/authentik/stages/authenticator_totp/models.py +++ b/authentik/stages/authenticator_totp/models.py @@ -38,7 +38,6 @@ class AuthenticatorTOTPStage(ConfigurableStage, Stage): def component(self) -> str: return "ak-stage-authenticator-totp-form" - @property def ui_user_settings(self) -> Optional[UserSettingSerializer]: return UserSettingSerializer( data={ diff --git a/authentik/stages/authenticator_webauthn/models.py b/authentik/stages/authenticator_webauthn/models.py index a28b52c8bb..708270f4ac 100644 --- a/authentik/stages/authenticator_webauthn/models.py +++ b/authentik/stages/authenticator_webauthn/models.py @@ -34,7 +34,6 @@ class AuthenticateWebAuthnStage(ConfigurableStage, Stage): def component(self) -> str: return "ak-stage-authenticator-webauthn-form" - @property def ui_user_settings(self) -> Optional[UserSettingSerializer]: return UserSettingSerializer( data={ diff --git a/authentik/stages/identification/stage.py b/authentik/stages/identification/stage.py index 925a936e07..e547408e3c 100644 --- a/authentik/stages/identification/stage.py +++ b/authentik/stages/identification/stage.py @@ -191,7 +191,7 @@ class IdentificationStageView(ChallengeStageView): current_stage.sources.filter(enabled=True).order_by("name").select_subclasses() ) for source in sources: - ui_login_button = source.ui_login_button + ui_login_button = source.ui_login_button(self.request) if ui_login_button: button = asdict(ui_login_button) button["challenge"] = ui_login_button.challenge.data diff --git a/authentik/stages/password/models.py b/authentik/stages/password/models.py index 5369052a25..4ee783c912 100644 --- a/authentik/stages/password/models.py +++ b/authentik/stages/password/models.py @@ -63,7 +63,6 @@ class PasswordStage(ConfigurableStage, Stage): def component(self) -> str: return "ak-stage-password-form" - @property def ui_user_settings(self) -> Optional[UserSettingSerializer]: if not self.configure_flow: return None