diff --git a/passbook/admin/templates/administration/source/list.html b/passbook/admin/templates/administration/source/list.html
index b2de358b2..e0e5c9e36 100644
--- a/passbook/admin/templates/administration/source/list.html
+++ b/passbook/admin/templates/administration/source/list.html
@@ -36,7 +36,7 @@
{{ source.name }} |
{{ source|fieldtype }} |
- {{ source.additional_info|safe }} |
+ {{ source.ui_additional_info|safe|default:"" }} |
{% trans 'Edit' %}
diff --git a/passbook/core/models.py b/passbook/core/models.py
index 1d718a6f7..32bda701b 100644
--- a/passbook/core/models.py
+++ b/passbook/core/models.py
@@ -21,6 +21,7 @@ from jinja2.nativetypes import NativeEnvironment
from model_utils.managers import InheritanceManager
from structlog import get_logger
+from passbook.core.types import UIUserSettings, UILoginButton
from passbook.core.exceptions import PropertyMappingExpressionException
from passbook.core.signals import password_changed
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
@@ -102,19 +103,6 @@ class PolicyModel(UUIDModel, CreatedUpdatedModel):
policies = models.ManyToManyField("Policy", blank=True)
-class UserSettings:
- """Dataclass for Factor and Source's user_settings"""
-
- name: str
- icon: str
- view_name: str
-
- def __init__(self, name: str, icon: str, view_name: str):
- self.name = name
- self.icon = icon
- self.view_name = view_name
-
-
class Factor(ExportModelOperationsMixin("factor"), PolicyModel):
"""Authentication factor, multiple instances of the same Factor can be used"""
@@ -127,9 +115,10 @@ class Factor(ExportModelOperationsMixin("factor"), PolicyModel):
type = ""
form = ""
- def user_settings(self) -> Optional[UserSettings]:
+ @property
+ def ui_user_settings(self) -> Optional[UIUserSettings]:
"""Entrypoint to integrate with User settings. Can either return None if no
- user settings are available, or an instanace of UserSettings."""
+ user settings are available, or an instanace of UIUserSettings."""
return None
def __str__(self):
@@ -181,19 +170,20 @@ class Source(ExportModelOperationsMixin("source"), PolicyModel):
objects = InheritanceManager()
@property
- def login_button(self):
- """Return a tuple of URL, Icon name and Name
- if Source should get a link on the login page"""
+ def ui_login_button(self) -> 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 additional_info(self):
+ def ui_additional_info(self) -> Optional[str]:
"""Return additional Info, such as a callback URL. Show in the administration interface."""
return None
- def user_settings(self) -> Optional[UserSettings]:
+ @property
+ def ui_user_settings(self) -> Optional[UIUserSettings]:
"""Entrypoint to integrate with User settings. Can either return None if no
- user settings are available, or an instanace of UserSettings."""
+ user settings are available, or an instanace of UIUserSettings."""
return None
def __str__(self):
diff --git a/passbook/core/templatetags/passbook_user_settings.py b/passbook/core/templatetags/passbook_user_settings.py
index 82c7ec60c..39773799e 100644
--- a/passbook/core/templatetags/passbook_user_settings.py
+++ b/passbook/core/templatetags/passbook_user_settings.py
@@ -1,46 +1,53 @@
"""passbook user settings template tags"""
-from typing import List
+from typing import List, Iterable
from django import template
from django.template.context import RequestContext
-from passbook.core.models import Factor, Source, UserSettings
+from passbook.core.types import UIUserSettings
+from passbook.core.models import Factor, Source
from passbook.policies.engine import PolicyEngine
register = template.Library()
@register.simple_tag(takes_context=True)
-def user_factors(context: RequestContext) -> List[UserSettings]:
+def user_factors(context: RequestContext) -> List[UIUserSettings]:
"""Return list of all factors which apply to user"""
user = context.get("request").user
- _all_factors = (
+ _all_factors: Iterable[Factor] = (
Factor.objects.filter(enabled=True).order_by("order").select_subclasses()
)
- matching_factors: List[UserSettings] = []
+ matching_factors: List[UIUserSettings] = []
for factor in _all_factors:
- user_settings = factor.user_settings()
+ user_settings = factor.ui_user_settings
+ if not user_settings:
+ continue
policy_engine = PolicyEngine(
factor.policies.all(), user, context.get("request")
)
policy_engine.build()
- if policy_engine.passing and user_settings:
+ if policy_engine.passing:
matching_factors.append(user_settings)
return matching_factors
@register.simple_tag(takes_context=True)
-def user_sources(context: RequestContext) -> List[UserSettings]:
+def user_sources(context: RequestContext) -> List[UIUserSettings]:
"""Return a list of all sources which are enabled for the user"""
user = context.get("request").user
- _all_sources = Source.objects.filter(enabled=True).select_subclasses()
- matching_sources: List[UserSettings] = []
+ _all_sources: Iterable[Source] = (
+ Source.objects.filter(enabled=True).select_subclasses()
+ )
+ matching_sources: List[UIUserSettings] = []
for factor in _all_sources:
- user_settings = factor.user_settings()
+ user_settings = factor.ui_user_settings
+ if not user_settings:
+ continue
policy_engine = PolicyEngine(
factor.policies.all(), user, context.get("request")
)
policy_engine.build()
- if policy_engine.passing and user_settings:
+ if policy_engine.passing:
matching_sources.append(user_settings)
return matching_sources
diff --git a/passbook/core/types.py b/passbook/core/types.py
new file mode 100644
index 000000000..17937b527
--- /dev/null
+++ b/passbook/core/types.py
@@ -0,0 +1,29 @@
+"""passbook core dataclasses"""
+from typing import Optional
+from dataclasses import dataclass
+
+
+@dataclass
+class UIUserSettings:
+ """Dataclass for Factor and Source's user_settings"""
+
+ name: str
+ icon: str
+ view_name: str
+
+
+@dataclass
+class UILoginButton:
+ """Dataclass for Source's ui_ui_login_button"""
+
+ # Name, ran through i18n
+ name: str
+
+ # URL Which Button points to
+ url: str
+
+ # Icon name, ran through django's static
+ icon_path: Optional[str] = None
+
+ # Icon URL, used as-is
+ icon_url: Optional[str] = None
diff --git a/passbook/core/views/authentication.py b/passbook/core/views/authentication.py
index f85aaa019..05bee8ba1 100644
--- a/passbook/core/views/authentication.py
+++ b/passbook/core/views/authentication.py
@@ -47,9 +47,9 @@ class LoginView(UserPassesTestMixin, FormView):
kwargs["sources"] = []
sources = Source.objects.filter(enabled=True).select_subclasses()
for source in sources:
- login_button = source.login_button
- if login_button:
- kwargs["sources"].append(login_button)
+ ui_login_button = source.ui_login_button
+ if ui_login_button:
+ kwargs["sources"].append(ui_login_button)
if kwargs["sources"]:
self.template_name = "login/with_sources.html"
return super().get_context_data(**kwargs)
diff --git a/passbook/factors/otp/models.py b/passbook/factors/otp/models.py
index daf9fb4a2..8e751e625 100644
--- a/passbook/factors/otp/models.py
+++ b/passbook/factors/otp/models.py
@@ -1,9 +1,9 @@
"""OTP Factor"""
-
from django.db import models
from django.utils.translation import gettext as _
-from passbook.core.models import Factor, UserSettings
+from passbook.core.types import UIUserSettings
+from passbook.core.models import Factor
class OTPFactor(Factor):
@@ -17,9 +17,12 @@ class OTPFactor(Factor):
type = "passbook.factors.otp.factors.OTPFactor"
form = "passbook.factors.otp.forms.OTPFactorForm"
- def user_settings(self) -> UserSettings:
- return UserSettings(
- _("OTP"), "pficon-locked", "passbook_factors_otp:otp-user-settings"
+ @property
+ def ui_user_settings(self) -> UIUserSettings:
+ return UIUserSettings(
+ name="OTP",
+ icon="pficon-locked",
+ view_name="passbook_factors_otp:otp-user-settings",
)
def __str__(self):
diff --git a/passbook/factors/password/models.py b/passbook/factors/password/models.py
index b5ed6bdf5..e9af9722a 100644
--- a/passbook/factors/password/models.py
+++ b/passbook/factors/password/models.py
@@ -3,7 +3,8 @@ from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.translation import gettext_lazy as _
-from passbook.core.models import Factor, Policy, User, UserSettings
+from passbook.core.types import UIUserSettings
+from passbook.core.models import Factor, Policy, User
class PasswordFactor(Factor):
@@ -18,9 +19,12 @@ class PasswordFactor(Factor):
type = "passbook.factors.password.factor.PasswordFactor"
form = "passbook.factors.password.forms.PasswordFactorForm"
- def user_settings(self):
- return UserSettings(
- _("Change Password"), "pficon-key", "passbook_core:user-change-password"
+ @property
+ def ui_user_settings(self) -> UIUserSettings:
+ return UIUserSettings(
+ name="Change Password",
+ icon="pficon-key",
+ view_name="passbook_core:user-change-password",
)
def password_passes(self, user: User) -> bool:
diff --git a/passbook/policies/expression/evaluator.py b/passbook/policies/expression/evaluator.py
index 042a4d9db..db14c7eba 100644
--- a/passbook/policies/expression/evaluator.py
+++ b/passbook/policies/expression/evaluator.py
@@ -76,7 +76,7 @@ class Evaluator:
src=expression_source,
req=request,
)
- return PolicyRequest(False)
+ return PolicyResult(False)
if isinstance(result, list) and len(result) == 2:
return PolicyResult(*result)
if result:
diff --git a/passbook/sources/oauth/models.py b/passbook/sources/oauth/models.py
index 40d070df6..1c1cabfd9 100644
--- a/passbook/sources/oauth/models.py
+++ b/passbook/sources/oauth/models.py
@@ -4,7 +4,8 @@ from django.db import models
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
-from passbook.core.models import Source, UserSettings, UserSourceConnection
+from passbook.core.types import UILoginButton, UIUserSettings
+from passbook.core.models import Source, UserSourceConnection
from passbook.sources.oauth.clients import get_client
@@ -28,30 +29,35 @@ class OAuthSource(Source):
form = "passbook.sources.oauth.forms.OAuthSourceForm"
@property
- def login_button(self):
- url = reverse_lazy(
- "passbook_sources_oauth:oauth-client-login",
- kwargs={"source_slug": self.slug},
+ def ui_login_button(self) -> UILoginButton:
+ return UILoginButton(
+ url=reverse_lazy(
+ "passbook_sources_oauth:oauth-client-login",
+ kwargs={"source_slug": self.slug},
+ ),
+ icon_path=f"{self.provider_type}.svg",
+ name=self.name,
)
- return url, self.provider_type, self.name
@property
- def additional_info(self):
- return "Callback URL: %s " % reverse_lazy(
+ def ui_additional_info(self) -> str:
+ url = reverse_lazy(
"passbook_sources_oauth:oauth-client-callback",
kwargs={"source_slug": self.slug},
)
+ return f"Callback URL: {url} "
- def user_settings(self) -> UserSettings:
+ @property
+ def ui_user_settings(self) -> UIUserSettings:
icon_type = self.provider_type
if icon_type == "azure ad":
icon_type = "windows"
- icon_class = "fa fa-%s" % icon_type
+ icon_class = f"fa fa-{icon_type}"
view_name = "passbook_sources_oauth:oauth-client-user"
- return UserSettings(
- self.name,
- icon_class,
- reverse((view_name), kwargs={"source_slug": self.slug}),
+ return UIUserSettings(
+ name=self.name,
+ icon=icon_class,
+ view_name=reverse((view_name), kwargs={"source_slug": self.slug}),
)
class Meta:
diff --git a/passbook/sources/saml/models.py b/passbook/sources/saml/models.py
index 273380382..36356fdfa 100644
--- a/passbook/sources/saml/models.py
+++ b/passbook/sources/saml/models.py
@@ -3,11 +3,12 @@ from django.db import models
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
+from passbook.core.types import UILoginButton
from passbook.core.models import Source
class SAMLSource(Source):
- """SAML2 Source"""
+ """SAML Source"""
entity_id = models.TextField(blank=True, default=None, verbose_name=_("Entity ID"))
idp_url = models.URLField(verbose_name=_("IDP URL"))
@@ -20,14 +21,17 @@ class SAMLSource(Source):
form = "passbook.sources.saml.forms.SAMLSourceForm"
@property
- def login_button(self):
- url = reverse_lazy(
- "passbook_sources_saml:login", kwargs={"source_slug": self.slug}
+ def ui_login_button(self) -> UILoginButton:
+ return UILoginButton(
+ name=self.name,
+ url=reverse_lazy(
+ "passbook_sources_saml:login", kwargs={"source_slug": self.slug}
+ ),
+ icon_path="",
)
- return url, "", self.name
@property
- def additional_info(self):
+ def ui_additional_info(self) -> str:
metadata_url = reverse_lazy(
"passbook_sources_saml:metadata", kwargs={"source_slug": self}
)
|