Squashed commit of the following:

commit 88029a4335
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Mon Jul 20 16:55:55 2020 +0200

    admin: update to work with new form

commit 4040eb9619
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Mon Jul 20 16:43:30 2020 +0200

    *: remove path-based import from all PropertyMappings

commit c9663a08da
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Mon Jul 20 16:33:34 2020 +0200

    flows: update work with new stages

commit a3d92ebc0a
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Mon Jul 20 16:23:30 2020 +0200

    stages/*: remove path-based import from all stages

commit 6fa825e372
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Mon Jul 20 16:03:55 2020 +0200

    providers/*: remove path-based import from all providers

commit 6aefd072c8
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Mon Jul 20 15:58:48 2020 +0200

    policies/*: remove path-based import from all policies

commit ac2dd3611f
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Mon Jul 20 15:11:27 2020 +0200

    sources/*: remove path-based import from all sources

commit 74e628ce9c
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Mon Jul 20 14:43:38 2020 +0200

    ui: allow overriding of verbose_name

commit d4ee18ee32
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Mon Jul 20 14:08:27 2020 +0200

    sources/oauth: migrate from discordapp.com to discord.com
This commit is contained in:
Jens Langhammer 2020-07-20 18:17:14 +02:00
parent 9ff3ee7c0c
commit 37a432267d
42 changed files with 379 additions and 98 deletions

View File

@ -103,9 +103,9 @@ class TestProviderOAuth(SeleniumTestCase):
USER().username, USER().username,
) )
self.assertEqual( self.assertEqual(
self.driver.find_element( self.driver.find_element(By.CSS_SELECTOR, "input[name=name]").get_attribute(
By.CSS_SELECTOR, "input[name=name]" "value"
).get_attribute("value"), ),
USER().username, USER().username,
) )
self.assertEqual( self.assertEqual(
@ -172,9 +172,9 @@ class TestProviderOAuth(SeleniumTestCase):
USER().username, USER().username,
) )
self.assertEqual( self.assertEqual(
self.driver.find_element( self.driver.find_element(By.CSS_SELECTOR, "input[name=name]").get_attribute(
By.CSS_SELECTOR, "input[name=name]" "value"
).get_attribute("value"), ),
USER().username, USER().username,
) )
self.assertEqual( self.assertEqual(

View File

@ -153,9 +153,9 @@ class TestProviderOIDC(SeleniumTestCase):
USER().name, USER().name,
) )
self.assertEqual( self.assertEqual(
self.driver.find_element( self.driver.find_element(By.CSS_SELECTOR, "input[name=name]").get_attribute(
By.CSS_SELECTOR, "input[name=name]" "value"
).get_attribute("value"), ),
USER().name, USER().name,
) )
self.assertEqual( self.assertEqual(
@ -232,9 +232,9 @@ class TestProviderOIDC(SeleniumTestCase):
USER().name, USER().name,
) )
self.assertEqual( self.assertEqual(
self.driver.find_element( self.driver.find_element(By.CSS_SELECTOR, "input[name=name]").get_attribute(
By.CSS_SELECTOR, "input[name=name]" "value"
).get_attribute("value"), ),
USER().name, USER().name,
) )
self.assertEqual( self.assertEqual(

View File

@ -6,7 +6,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.http import Http404 from django.http import Http404
from django.views.generic import DeleteView, ListView, UpdateView from django.views.generic import DeleteView, ListView, UpdateView
from passbook.lib.utils.reflection import all_subclasses, path_to_class from passbook.lib.utils.reflection import all_subclasses
from passbook.lib.views import CreateAssignPermView from passbook.lib.views import CreateAssignPermView
@ -40,7 +40,7 @@ class InheritanceCreateView(CreateAssignPermView):
) )
except StopIteration as exc: except StopIteration as exc:
raise Http404 from exc raise Http404 from exc
return path_to_class(model.form) return model.form(model)
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
@ -61,9 +61,7 @@ class InheritanceUpdateView(UpdateView):
return kwargs return kwargs
def get_form_class(self): def get_form_class(self):
form_class_path = self.get_object().form return self.get_object().form()
form_class = path_to_class(form_class_path)
return form_class
def get_object(self, queryset=None): def get_object(self, queryset=None):
return ( return (

View File

@ -1,12 +1,13 @@
"""passbook core models""" """passbook core models"""
from datetime import timedelta from datetime import timedelta
from typing import Any, Optional from typing import Any, Optional, Type
from uuid import uuid4 from uuid import uuid4
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from django.db import models from django.db import models
from django.db.models import Q, QuerySet from django.db.models import Q, QuerySet
from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -92,6 +93,10 @@ class Provider(models.Model):
objects = InheritanceManager() objects = InheritanceManager()
def form(self) -> Type[ModelForm]:
"""Return Form class used to edit this object"""
raise NotImplementedError
# This class defines no field for easier inheritance # This class defines no field for easier inheritance
def __str__(self): def __str__(self):
if hasattr(self, "name"): if hasattr(self, "name"):
@ -162,10 +167,12 @@ class Source(PolicyBindingModel):
related_name="source_enrollment", related_name="source_enrollment",
) )
form = "" # ModelForm-based class ued to create/edit instance
objects = InheritanceManager() objects = InheritanceManager()
def form(self) -> Type[ModelForm]:
"""Return Form class used to edit this object"""
raise NotImplementedError
@property @property
def ui_login_button(self) -> Optional[UILoginButton]: def ui_login_button(self) -> Optional[UILoginButton]:
"""If source uses a http-based flow, return UI Information about the login """If source uses a http-based flow, return UI Information about the login
@ -261,9 +268,12 @@ class PropertyMapping(models.Model):
name = models.TextField() name = models.TextField()
expression = models.TextField() expression = models.TextField()
form = ""
objects = InheritanceManager() objects = InheritanceManager()
def form(self) -> Type[ModelForm]:
"""Return Form class used to edit this object"""
raise NotImplementedError
def evaluate( def evaluate(
self, user: Optional[User], request: Optional[HttpRequest], **kwargs self, user: Optional[User], request: Optional[HttpRequest], **kwargs
) -> Any: ) -> Any:

View File

@ -3,13 +3,13 @@ from typing import TYPE_CHECKING, Optional, Type
from uuid import uuid4 from uuid import uuid4
from django.db import models from django.db import models
from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from model_utils.managers import InheritanceManager from model_utils.managers import InheritanceManager
from structlog import get_logger from structlog import get_logger
from passbook.core.types import UIUserSettings from passbook.core.types import UIUserSettings
from passbook.lib.utils.reflection import class_to_path
from passbook.policies.models import PolicyBindingModel from passbook.policies.models import PolicyBindingModel
if TYPE_CHECKING: if TYPE_CHECKING:
@ -47,8 +47,17 @@ class Stage(models.Model):
name = models.TextField() name = models.TextField()
objects = InheritanceManager() objects = InheritanceManager()
type = ""
form = "" def type(self) -> Type["StageView"]:
"""Return StageView class that implements logic for this stage"""
# This is a bit of a workaround, since we can't set class methods with setattr
if hasattr(self, "__in_memory_type"):
return getattr(self, "__in_memory_type")
raise NotImplementedError
def form(self) -> Type[ModelForm]:
"""Return Form class used to edit this object"""
raise NotImplementedError
@property @property
def ui_user_settings(self) -> Optional[UIUserSettings]: def ui_user_settings(self) -> Optional[UIUserSettings]:
@ -62,9 +71,11 @@ class Stage(models.Model):
def in_memory_stage(view: Type["StageView"]) -> Stage: def in_memory_stage(view: Type["StageView"]) -> Stage:
"""Creates an in-memory stage instance, based on a `_type` as view.""" """Creates an in-memory stage instance, based on a `_type` as view."""
class_path = class_to_path(view)
stage = Stage() stage = Stage()
stage.type = class_path # Because we can't pickle a locally generated function,
# we set the view as a separate property and reference a generic function
# that returns that member
setattr(stage, "__in_memory_type", view)
return stage return stage

View File

@ -21,7 +21,7 @@ from passbook.core.views.utils import PermissionDeniedView
from passbook.flows.exceptions import EmptyFlowException, FlowNonApplicableException from passbook.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from passbook.flows.models import Flow, FlowDesignation, Stage from passbook.flows.models import Flow, FlowDesignation, Stage
from passbook.flows.planner import FlowPlan, FlowPlanner from passbook.flows.planner import FlowPlan, FlowPlanner
from passbook.lib.utils.reflection import class_to_path, path_to_class from passbook.lib.utils.reflection import class_to_path
from passbook.lib.utils.urls import is_url_absolute, redirect_with_qs from passbook.lib.utils.urls import is_url_absolute, redirect_with_qs
from passbook.lib.views import bad_request_message from passbook.lib.views import bad_request_message
@ -94,7 +94,7 @@ class FlowExecutorView(View):
if not self.current_stage: if not self.current_stage:
LOGGER.debug("f(exec): no more stages, flow is done.") LOGGER.debug("f(exec): no more stages, flow is done.")
return self._flow_done() return self._flow_done()
stage_cls = path_to_class(self.current_stage.type) stage_cls = self.current_stage.type()
self.current_stage_view = stage_cls(self) self.current_stage_view = stage_cls(self)
self.current_stage_view.args = self.args self.current_stage_view.args = self.args
self.current_stage_view.kwargs = self.kwargs self.current_stage_view.kwargs = self.kwargs

View File

@ -36,7 +36,7 @@ def back(context: Context) -> str:
def fieldtype(field): def fieldtype(field):
"""Return classname""" """Return classname"""
if isinstance(field.__class__, Model) or issubclass(field.__class__, Model): if isinstance(field.__class__, Model) or issubclass(field.__class__, Model):
return field._meta.verbose_name return verbose_name(field)
return field.__class__.__name__ return field.__class__.__name__
@ -84,6 +84,9 @@ def verbose_name(obj) -> str:
"""Return Object's Verbose Name""" """Return Object's Verbose Name"""
if not obj: if not obj:
return "" return ""
if hasattr(obj, "verbose_name"):
print(obj.verbose_name)
return obj.verbose_name
return obj._meta.verbose_name return obj._meta.verbose_name
@ -92,7 +95,7 @@ def form_verbose_name(obj) -> str:
"""Return ModelForm's Object's Verbose Name""" """Return ModelForm's Object's Verbose Name"""
if not obj: if not obj:
return "" return ""
return obj._meta.model._meta.verbose_name return verbose_name(obj._meta.model)
@register.filter @register.filter

View File

@ -1,8 +1,10 @@
"""Dummy policy""" """Dummy policy"""
from random import SystemRandom from random import SystemRandom
from time import sleep from time import sleep
from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from structlog import get_logger from structlog import get_logger
@ -22,7 +24,10 @@ class DummyPolicy(Policy):
wait_min = models.IntegerField(default=5) wait_min = models.IntegerField(default=5)
wait_max = models.IntegerField(default=30) wait_max = models.IntegerField(default=30)
form = "passbook.policies.dummy.forms.DummyPolicyForm" def form(self) -> Type[ModelForm]:
from passbook.policies.dummy.forms import DummyPolicyForm
return DummyPolicyForm
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
"""Wait random time then return result""" """Wait random time then return result"""

View File

@ -1,7 +1,9 @@
"""passbook password_expiry_policy Models""" """passbook password_expiry_policy Models"""
from datetime import timedelta from datetime import timedelta
from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from structlog import get_logger from structlog import get_logger
@ -19,7 +21,10 @@ class PasswordExpiryPolicy(Policy):
deny_only = models.BooleanField(default=False) deny_only = models.BooleanField(default=False)
days = models.IntegerField() days = models.IntegerField()
form = "passbook.policies.expiry.forms.PasswordExpiryPolicyForm" def form(self) -> Type[ModelForm]:
from passbook.policies.expiry.forms import PasswordExpiryPolicyForm
return PasswordExpiryPolicyForm
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
"""If password change date is more than x days in the past, call set_unusable_password """If password change date is more than x days in the past, call set_unusable_password

View File

@ -1,5 +1,8 @@
"""passbook expression Policy Models""" """passbook expression Policy Models"""
from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from passbook.policies.expression.evaluator import PolicyEvaluator from passbook.policies.expression.evaluator import PolicyEvaluator
@ -12,7 +15,10 @@ class ExpressionPolicy(Policy):
expression = models.TextField() expression = models.TextField()
form = "passbook.policies.expression.forms.ExpressionPolicyForm" def form(self) -> Type[ModelForm]:
from passbook.policies.expression.forms import ExpressionPolicyForm
return ExpressionPolicyForm
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
"""Evaluate and render expression. Returns PolicyResult(false) on error.""" """Evaluate and render expression. Returns PolicyResult(false) on error."""

View File

@ -1,5 +1,8 @@
"""user field matcher models""" """user field matcher models"""
from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from passbook.core.models import Group from passbook.core.models import Group
@ -12,7 +15,10 @@ class GroupMembershipPolicy(Policy):
group = models.ForeignKey(Group, null=True, blank=True, on_delete=models.SET_NULL) group = models.ForeignKey(Group, null=True, blank=True, on_delete=models.SET_NULL)
form = "passbook.policies.group_membership.forms.GroupMembershipPolicyForm" def form(self) -> Type[ModelForm]:
from passbook.policies.group_membership.forms import GroupMembershipPolicyForm
return GroupMembershipPolicyForm
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
return PolicyResult(self.group.user_set.filter(pk=request.user.pk).exists()) return PolicyResult(self.group.user_set.filter(pk=request.user.pk).exists())

View File

@ -1,7 +1,9 @@
"""passbook HIBP Models""" """passbook HIBP Models"""
from hashlib import sha1 from hashlib import sha1
from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from requests import get from requests import get
from structlog import get_logger from structlog import get_logger
@ -25,7 +27,10 @@ class HaveIBeenPwendPolicy(Policy):
allowed_count = models.IntegerField(default=0) allowed_count = models.IntegerField(default=0)
form = "passbook.policies.hibp.forms.HaveIBeenPwnedPolicyForm" def form(self) -> Type[ModelForm]:
from passbook.policies.hibp.forms import HaveIBeenPwnedPolicyForm
return HaveIBeenPwnedPolicyForm
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
"""Check if password is in HIBP DB. Hashes given Password with SHA1, uses the first 5 """Check if password is in HIBP DB. Hashes given Password with SHA1, uses the first 5

View File

@ -1,7 +1,9 @@
"""Policy base models""" """Policy base models"""
from typing import Type
from uuid import uuid4 from uuid import uuid4
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from model_utils.managers import InheritanceManager from model_utils.managers import InheritanceManager
@ -73,6 +75,10 @@ class Policy(CreatedUpdatedModel):
objects = InheritanceAutoManager() objects = InheritanceAutoManager()
def form(self) -> Type[ModelForm]:
"""Return Form class used to edit this object"""
raise NotImplementedError
def __str__(self): def __str__(self):
return f"Policy {self.name}" return f"Policy {self.name}"

View File

@ -1,7 +1,9 @@
"""user field matcher models""" """user field matcher models"""
import re import re
from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from structlog import get_logger from structlog import get_logger
@ -28,7 +30,10 @@ class PasswordPolicy(Policy):
symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ") symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ")
error_message = models.TextField() error_message = models.TextField()
form = "passbook.policies.password.forms.PasswordPolicyForm" def form(self) -> Type[ModelForm]:
from passbook.policies.password.forms import PasswordPolicyForm
return PasswordPolicyForm
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
if self.password_field not in request.context: if self.password_field not in request.context:

View File

@ -1,6 +1,9 @@
"""passbook reputation request policy""" """passbook reputation request policy"""
from typing import Type
from django.core.cache import cache from django.core.cache import cache
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from passbook.core.models import User from passbook.core.models import User
@ -19,7 +22,10 @@ class ReputationPolicy(Policy):
check_username = models.BooleanField(default=True) check_username = models.BooleanField(default=True)
threshold = models.IntegerField(default=-5) threshold = models.IntegerField(default=-5)
form = "passbook.policies.reputation.forms.ReputationPolicyForm" def form(self) -> Type[ModelForm]:
from passbook.policies.reputation.forms import ReputationPolicyForm
return ReputationPolicyForm
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
remote_ip = get_client_ip(request.http_request) or "255.255.255.255" remote_ip = get_client_ip(request.http_request) or "255.255.255.255"

View File

@ -1,9 +1,10 @@
"""passbook app_gw models""" """passbook app_gw models"""
import string import string
from random import SystemRandom from random import SystemRandom
from typing import Optional from typing import Optional, Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from oidc_provider.models import Client from oidc_provider.models import Client
@ -23,7 +24,10 @@ class ApplicationGatewayProvider(Provider):
client = models.ForeignKey(Client, on_delete=models.CASCADE) client = models.ForeignKey(Client, on_delete=models.CASCADE)
form = "passbook.providers.app_gw.forms.ApplicationGatewayProviderForm" def form(self) -> Type[ModelForm]:
from passbook.providers.app_gw.forms import ApplicationGatewayProviderForm
return ApplicationGatewayProviderForm
def html_setup_urls(self, request: HttpRequest) -> Optional[str]: def html_setup_urls(self, request: HttpRequest) -> Optional[str]:
"""return template and context modal with URLs for authorize, token, openid-config, etc""" """return template and context modal with URLs for authorize, token, openid-config, etc"""

View File

@ -1,7 +1,8 @@
"""Oauth2 provider product extension""" """Oauth2 provider product extension"""
from typing import Optional from typing import Optional, Type
from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -16,7 +17,10 @@ class OAuth2Provider(Provider, AbstractApplication):
This Provider also supports the GitHub-pretend mode for Applications that don't support This Provider also supports the GitHub-pretend mode for Applications that don't support
generic OAuth.""" generic OAuth."""
form = "passbook.providers.oauth.forms.OAuth2ProviderForm" def form(self) -> Type[ModelForm]:
from passbook.providers.oauth.forms import OAuth2ProviderForm
return OAuth2ProviderForm
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -1,7 +1,8 @@
"""oidc models""" """oidc models"""
from typing import Optional from typing import Optional, Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -20,7 +21,10 @@ class OpenIDProvider(Provider):
oidc_client = models.OneToOneField(Client, on_delete=models.CASCADE) oidc_client = models.OneToOneField(Client, on_delete=models.CASCADE)
form = "passbook.providers.oidc.forms.OIDCProviderForm" def form(self) -> Type[ModelForm]:
from passbook.providers.oidc.forms import OIDCProviderForm
return OIDCProviderForm
@property @property
def name(self): def name(self):

View File

@ -1,7 +1,8 @@
"""passbook saml_idp Models""" """passbook saml_idp Models"""
from typing import Optional from typing import Optional, Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -101,7 +102,10 @@ class SAMLProvider(Provider):
), ),
) )
form = "passbook.providers.saml.forms.SAMLProviderForm" def form(self) -> Type[ModelForm]:
from passbook.providers.saml.forms import SAMLProviderForm
return SAMLProviderForm
def __str__(self): def __str__(self):
return self.name return self.name
@ -143,7 +147,10 @@ class SAMLPropertyMapping(PropertyMapping):
saml_name = models.TextField(verbose_name="SAML Name") saml_name = models.TextField(verbose_name="SAML Name")
friendly_name = models.TextField(default=None, blank=True, null=True) friendly_name = models.TextField(default=None, blank=True, null=True)
form = "passbook.providers.saml.forms.SAMLPropertyMappingForm" def form(self) -> Type[ModelForm]:
from passbook.providers.saml.forms import SAMLPropertyMappingForm
return SAMLPropertyMappingForm
def __str__(self): def __str__(self):
return f"SAML Property Mapping {self.saml_name}" return f"SAML Property Mapping {self.saml_name}"

View File

@ -325,6 +325,8 @@ LOG_PRE_CHAIN = [
structlog.stdlib.add_log_level, structlog.stdlib.add_log_level,
structlog.stdlib.add_logger_name, structlog.stdlib.add_logger_name,
structlog.processors.TimeStamper(), structlog.processors.TimeStamper(),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
] ]
LOGGING = { LOGGING = {

View File

@ -1,8 +1,9 @@
"""passbook LDAP Models""" """passbook LDAP Models"""
from typing import Optional from typing import Optional, Type
from django.core.validators import URLValidator from django.core.validators import URLValidator
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from ldap3 import Connection, Server from ldap3 import Connection, Server
@ -53,7 +54,10 @@ class LDAPSource(Source):
Group, blank=True, null=True, default=None, on_delete=models.SET_DEFAULT Group, blank=True, null=True, default=None, on_delete=models.SET_DEFAULT
) )
form = "passbook.sources.ldap.forms.LDAPSourceForm" def form(self) -> Type[ModelForm]:
from passbook.sources.ldap.forms import LDAPSourceForm
return LDAPSourceForm
_connection: Optional[Connection] = None _connection: Optional[Connection] = None
@ -85,7 +89,10 @@ class LDAPPropertyMapping(PropertyMapping):
object_field = models.TextField() object_field = models.TextField()
form = "passbook.sources.ldap.forms.LDAPPropertyMappingForm" def form(self) -> Type[ModelForm]:
from passbook.sources.ldap.forms import LDAPPropertyMappingForm
return LDAPPropertyMappingForm
def __str__(self): def __str__(self):
return f"LDAP Property Mapping {self.expression} -> {self.object_field}" return f"LDAP Property Mapping {self.expression} -> {self.object_field}"

View File

@ -98,9 +98,9 @@ class DiscordOAuthSourceForm(OAuthSourceForm):
overrides = { overrides = {
"provider_type": "discord", "provider_type": "discord",
"request_token_url": "", "request_token_url": "",
"authorization_url": "https://discordapp.com/api/oauth2/authorize", "authorization_url": "https://discord.com/api/oauth2/authorize",
"access_token_url": "https://discordapp.com/api/oauth2/token", "access_token_url": "https://discord.com/api/oauth2/token",
"profile_url": "https://discordapp.com/api/users/@me", "profile_url": "https://discord.com/api/users/@me",
} }

View File

@ -1,7 +1,8 @@
"""OAuth Client models""" """OAuth Client models"""
from typing import Optional from typing import Optional, Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -40,7 +41,10 @@ class OAuthSource(Source):
consumer_key = models.TextField() consumer_key = models.TextField()
consumer_secret = models.TextField() consumer_secret = models.TextField()
form = "passbook.sources.oauth.forms.OAuthSourceForm" def form(self) -> Type[ModelForm]:
from passbook.sources.oauth.forms import OAuthSourceForm
return OAuthSourceForm
@property @property
def ui_login_button(self) -> UILoginButton: def ui_login_button(self) -> UILoginButton:
@ -80,7 +84,10 @@ class OAuthSource(Source):
class GitHubOAuthSource(OAuthSource): class GitHubOAuthSource(OAuthSource):
"""Social Login using GitHub.com or a GitHub-Enterprise Instance.""" """Social Login using GitHub.com or a GitHub-Enterprise Instance."""
form = "passbook.sources.oauth.forms.GitHubOAuthSourceForm" def form(self) -> Type[ModelForm]:
from passbook.sources.oauth.forms import GitHubOAuthSourceForm
return GitHubOAuthSourceForm
class Meta: class Meta:
@ -92,7 +99,9 @@ class GitHubOAuthSource(OAuthSource):
# class TwitterOAuthSource(OAuthSource): # class TwitterOAuthSource(OAuthSource):
# """Social Login using Twitter.com""" # """Social Login using Twitter.com"""
# form = "passbook.sources.oauth.forms.TwitterOAuthSourceForm" # def form(self) -> Type[ModelForm]:
# from passbook.sources.oauth.forms import TwitterOAuthSourceForm
# return TwitterOAuthSourceForm
# class Meta: # class Meta:
@ -104,7 +113,10 @@ class GitHubOAuthSource(OAuthSource):
class FacebookOAuthSource(OAuthSource): class FacebookOAuthSource(OAuthSource):
"""Social Login using Facebook.com.""" """Social Login using Facebook.com."""
form = "passbook.sources.oauth.forms.FacebookOAuthSourceForm" def form(self) -> Type[ModelForm]:
from passbook.sources.oauth.forms import FacebookOAuthSourceForm
return FacebookOAuthSourceForm
class Meta: class Meta:
@ -116,7 +128,10 @@ class FacebookOAuthSource(OAuthSource):
class DiscordOAuthSource(OAuthSource): class DiscordOAuthSource(OAuthSource):
"""Social Login using Discord.""" """Social Login using Discord."""
form = "passbook.sources.oauth.forms.DiscordOAuthSourceForm" def form(self) -> Type[ModelForm]:
from passbook.sources.oauth.forms import DiscordOAuthSourceForm
return DiscordOAuthSourceForm
class Meta: class Meta:
@ -128,7 +143,10 @@ class DiscordOAuthSource(OAuthSource):
class GoogleOAuthSource(OAuthSource): class GoogleOAuthSource(OAuthSource):
"""Social Login using Google or Gsuite.""" """Social Login using Google or Gsuite."""
form = "passbook.sources.oauth.forms.GoogleOAuthSourceForm" def form(self) -> Type[ModelForm]:
from passbook.sources.oauth.forms import GoogleOAuthSourceForm
return GoogleOAuthSourceForm
class Meta: class Meta:
@ -140,7 +158,10 @@ class GoogleOAuthSource(OAuthSource):
class AzureADOAuthSource(OAuthSource): class AzureADOAuthSource(OAuthSource):
"""Social Login using Azure AD.""" """Social Login using Azure AD."""
form = "passbook.sources.oauth.forms.AzureADOAuthSourceForm" def form(self) -> Type[ModelForm]:
from passbook.sources.oauth.forms import AzureADOAuthSourceForm
return AzureADOAuthSourceForm
class Meta: class Meta:
@ -152,7 +173,10 @@ class AzureADOAuthSource(OAuthSource):
class OpenIDOAuthSource(OAuthSource): class OpenIDOAuthSource(OAuthSource):
"""Login using a Generic OpenID-Connect compliant provider.""" """Login using a Generic OpenID-Connect compliant provider."""
form = "passbook.sources.oauth.forms.OAuthSourceForm" def form(self) -> Type[ModelForm]:
from passbook.sources.oauth.forms import OAuthSourceForm
return OAuthSourceForm
class Meta: class Meta:

View File

@ -1,5 +1,8 @@
"""saml sp models""" """saml sp models"""
from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import reverse from django.shortcuts import reverse
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -93,7 +96,10 @@ class SAMLSource(Source):
on_delete=models.PROTECT, on_delete=models.PROTECT,
) )
form = "passbook.sources.saml.forms.SAMLSourceForm" def form(self) -> Type[ModelForm]:
from passbook.sources.saml.forms import SAMLSourceForm
return SAMLSourceForm
def get_issuer(self, request: HttpRequest) -> str: def get_issuer(self, request: HttpRequest) -> str:
"""Get Source's Issuer, falling back to our Metadata URL if none is set""" """Get Source's Issuer, falling back to our Metadata URL if none is set"""

View File

@ -1,6 +1,10 @@
"""passbook captcha stage""" """passbook captcha stage"""
from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -19,8 +23,15 @@ class CaptchaStage(Stage):
) )
) )
type = "passbook.stages.captcha.stage.CaptchaStage" def type(self) -> Type[View]:
form = "passbook.stages.captcha.forms.CaptchaStageForm" from passbook.stages.captcha.stage import CaptchaStageView
return CaptchaStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.captcha.forms import CaptchaStageForm
return CaptchaStageForm
def __str__(self): def __str__(self):
return f"Captcha Stage {self.name}" return f"Captcha Stage {self.name}"

View File

@ -6,7 +6,7 @@ from passbook.flows.stage import StageView
from passbook.stages.captcha.forms import CaptchaForm from passbook.stages.captcha.forms import CaptchaForm
class CaptchaStage(FormView, StageView): class CaptchaStageView(FormView, StageView):
"""Simple captcha checker, logic is handeled in django-captcha module""" """Simple captcha checker, logic is handeled in django-captcha module"""
form_class = CaptchaForm form_class = CaptchaForm

View File

@ -1,6 +1,10 @@
"""passbook consent stage""" """passbook consent stage"""
from django.db import models from django.db import models
from typing import Type
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.core.models import Application, ExpiringModel, User from passbook.core.models import Application, ExpiringModel, User
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -33,8 +37,15 @@ class ConsentStage(Stage):
), ),
) )
type = "passbook.stages.consent.stage.ConsentStageView" def type(self) -> Type[View]:
form = "passbook.stages.consent.forms.ConsentStageForm" from passbook.stages.consent.stage import ConsentStageView
return ConsentStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.consent.forms import ConsentStageForm
return ConsentStageForm
def __str__(self): def __str__(self):
return f"Consent Stage {self.name}" return f"Consent Stage {self.name}"

View File

@ -1,5 +1,9 @@
"""dummy stage models""" """dummy stage models"""
from typing import Type
from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views import View
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -9,8 +13,15 @@ class DummyStage(Stage):
__debug_only__ = True __debug_only__ = True
type = "passbook.stages.dummy.stage.DummyStage" def type(self) -> Type[View]:
form = "passbook.stages.dummy.forms.DummyStageForm" from passbook.stages.dummy.stage import DummyStageView
return DummyStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.dummy.forms import DummyStageForm
return DummyStageForm
def __str__(self): def __str__(self):
return f"Dummy Stage {self.name}" return f"Dummy Stage {self.name}"

View File

@ -6,7 +6,7 @@ from django.http import HttpRequest
from passbook.flows.stage import StageView from passbook.flows.stage import StageView
class DummyStage(StageView): class DummyStageView(StageView):
"""Dummy stage for testing with multiple stages""" """Dummy stage for testing with multiple stages"""
def post(self, request: HttpRequest): def post(self, request: HttpRequest):

View File

@ -1,8 +1,12 @@
"""email stage models""" """email stage models"""
from typing import Type
from django.core.mail import get_connection from django.core.mail import get_connection
from django.core.mail.backends.base import BaseEmailBackend from django.core.mail.backends.base import BaseEmailBackend
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views import View
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -40,8 +44,15 @@ class EmailStage(Stage):
choices=EmailTemplates.choices, default=EmailTemplates.PASSWORD_RESET choices=EmailTemplates.choices, default=EmailTemplates.PASSWORD_RESET
) )
type = "passbook.stages.email.stage.EmailStageView" def type(self) -> Type[View]:
form = "passbook.stages.email.forms.EmailStageForm" from passbook.stages.email.stage import EmailStageView
return EmailStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.email.forms import EmailStageForm
return EmailStageForm
@property @property
def backend(self) -> BaseEmailBackend: def backend(self) -> BaseEmailBackend:

View File

@ -1,7 +1,11 @@
"""identification stage models""" """identification stage models"""
from typing import Type
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.flows.models import Flow, Stage from passbook.flows.models import Flow, Stage
@ -52,8 +56,15 @@ class IdentificationStage(Stage):
), ),
) )
type = "passbook.stages.identification.stage.IdentificationStageView" def type(self) -> Type[View]:
form = "passbook.stages.identification.forms.IdentificationStageForm" from passbook.stages.identification.stage import IdentificationStageView
return IdentificationStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.identification.forms import IdentificationStageForm
return IdentificationStageForm
def __str__(self): def __str__(self):
return f"Identification Stage {self.name}" return f"Identification Stage {self.name}"

View File

@ -1,9 +1,12 @@
"""invitation stage models""" """invitation stage models"""
from typing import Type
from uuid import uuid4 from uuid import uuid4
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -24,8 +27,15 @@ class InvitationStage(Stage):
), ),
) )
type = "passbook.stages.invitation.stage.InvitationStageView" def type(self) -> Type[View]:
form = "passbook.stages.invitation.forms.InvitationStageForm" from passbook.stages.invitation.stage import InvitationStageView
return InvitationStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.invitation.forms import InvitationStageForm
return InvitationStageForm
def __str__(self): def __str__(self):
return f"Invitation Stage {self.name}" return f"Invitation Stage {self.name}"

View File

@ -1,9 +1,11 @@
"""OTP Static models""" """OTP Static models"""
from typing import Optional from typing import Optional, Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.core.types import UIUserSettings from passbook.core.types import UIUserSettings
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -14,8 +16,15 @@ class OTPStaticStage(Stage):
token_count = models.IntegerField(default=6) token_count = models.IntegerField(default=6)
type = "passbook.stages.otp_static.stage.OTPStaticStageView" def type(self) -> Type[View]:
form = "passbook.stages.otp_static.forms.OTPStaticStageForm" from passbook.stages.otp_static.stage import OTPStaticStageView
return OTPStaticStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.otp_static.forms import OTPStaticStageForm
return OTPStaticStageForm
@property @property
def ui_user_settings(self) -> Optional[UIUserSettings]: def ui_user_settings(self) -> Optional[UIUserSettings]:

View File

@ -1,9 +1,11 @@
"""OTP Time-based models""" """OTP Time-based models"""
from typing import Optional from typing import Optional, Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.core.types import UIUserSettings from passbook.core.types import UIUserSettings
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -21,8 +23,15 @@ class OTPTimeStage(Stage):
digits = models.IntegerField(choices=TOTPDigits.choices) digits = models.IntegerField(choices=TOTPDigits.choices)
type = "passbook.stages.otp_time.stage.OTPTimeStageView" def type(self) -> Type[View]:
form = "passbook.stages.otp_time.forms.OTPTimeStageForm" from passbook.stages.otp_time.stage import OTPTimeStageView
return OTPTimeStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.otp_time.forms import OTPTimeStageForm
return OTPTimeStageForm
@property @property
def ui_user_settings(self) -> Optional[UIUserSettings]: def ui_user_settings(self) -> Optional[UIUserSettings]:

View File

@ -1,6 +1,10 @@
"""OTP Validation Stage""" """OTP Validation Stage"""
from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.flows.models import NotConfiguredAction, Stage from passbook.flows.models import NotConfiguredAction, Stage
@ -12,8 +16,15 @@ class OTPValidateStage(Stage):
choices=NotConfiguredAction.choices, default=NotConfiguredAction.SKIP choices=NotConfiguredAction.choices, default=NotConfiguredAction.SKIP
) )
type = "passbook.stages.otp_validate.stage.OTPValidateStageView" def type(self) -> Type[View]:
form = "passbook.stages.otp_validate.forms.OTPValidateStageForm" from passbook.stages.otp_validate.stage import OTPValidateStageView
return OTPValidateStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.otp_validate.forms import OTPValidateStageForm
return OTPValidateStageForm
def __str__(self) -> str: def __str__(self) -> str:
return f"OTP Validation Stage {self.name}" return f"OTP Validation Stage {self.name}"

View File

@ -1,11 +1,13 @@
"""password stage models""" """password stage models"""
from typing import Optional from typing import Optional, Type
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db import models from django.db import models
from django.forms import ModelForm
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.core.types import UIUserSettings from passbook.core.types import UIUserSettings
from passbook.flows.models import Flow, Stage from passbook.flows.models import Flow, Stage
@ -33,8 +35,15 @@ class PasswordStage(Stage):
), ),
) )
type = "passbook.stages.password.stage.PasswordStage" def type(self) -> Type[View]:
form = "passbook.stages.password.forms.PasswordStageForm" from passbook.stages.password.stage import PasswordStageView
return PasswordStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.password.forms import PasswordStageForm
return PasswordStageForm
@property @property
def ui_user_settings(self) -> Optional[UIUserSettings]: def ui_user_settings(self) -> Optional[UIUserSettings]:

View File

@ -46,7 +46,7 @@ def authenticate(
) )
class PasswordStage(FormView, StageView): class PasswordStageView(FormView, StageView):
"""Authentication stage which authenticates against django's AuthBackend""" """Authentication stage which authenticates against django's AuthBackend"""
form_class = PasswordForm form_class = PasswordForm

View File

@ -1,9 +1,12 @@
"""prompt models""" """prompt models"""
from typing import Type
from uuid import uuid4 from uuid import uuid4
from django import forms from django import forms
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.flows.models import Stage from passbook.flows.models import Stage
from passbook.policies.models import PolicyBindingModel from passbook.policies.models import PolicyBindingModel
@ -117,8 +120,15 @@ class PromptStage(PolicyBindingModel, Stage):
fields = models.ManyToManyField(Prompt) fields = models.ManyToManyField(Prompt)
type = "passbook.stages.prompt.stage.PromptStageView" def type(self) -> Type[View]:
form = "passbook.stages.prompt.forms.PromptStageForm" from passbook.stages.prompt.stage import PromptStageView
return PromptStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.prompt.forms import PromptStageForm
return PromptStageForm
def __str__(self): def __str__(self):
return f"Prompt Stage {self.name}" return f"Prompt Stage {self.name}"

View File

@ -1,5 +1,9 @@
"""delete stage models""" """delete stage models"""
from typing import Type
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -8,8 +12,15 @@ class UserDeleteStage(Stage):
"""Deletes the currently pending user without confirmation. """Deletes the currently pending user without confirmation.
Use with caution.""" Use with caution."""
type = "passbook.stages.user_delete.stage.UserDeleteStageView" def type(self) -> Type[View]:
form = "passbook.stages.user_delete.forms.UserDeleteStageForm" from passbook.stages.user_delete.stage import UserDeleteStageView
return UserDeleteStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.user_delete.forms import UserDeleteStageForm
return UserDeleteStageForm
def __str__(self): def __str__(self):
return f"User Delete Stage {self.name}" return f"User Delete Stage {self.name}"

View File

@ -1,6 +1,10 @@
"""login stage models""" """login stage models"""
from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -16,8 +20,15 @@ class UserLoginStage(Stage):
), ),
) )
type = "passbook.stages.user_login.stage.UserLoginStageView" def type(self) -> Type[View]:
form = "passbook.stages.user_login.forms.UserLoginStageForm" from passbook.stages.user_login.stage import UserLoginStageView
return UserLoginStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.user_login.forms import UserLoginStageForm
return UserLoginStageForm
def __str__(self): def __str__(self):
return f"User Login Stage {self.name}" return f"User Login Stage {self.name}"

View File

@ -1,5 +1,9 @@
"""logout stage models""" """logout stage models"""
from typing import Type
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -7,8 +11,15 @@ from passbook.flows.models import Stage
class UserLogoutStage(Stage): class UserLogoutStage(Stage):
"""Resets the users current session.""" """Resets the users current session."""
type = "passbook.stages.user_logout.stage.UserLogoutStageView" def type(self) -> Type[View]:
form = "passbook.stages.user_logout.forms.UserLogoutStageForm" from passbook.stages.user_logout.stage import UserLogoutStageView
return UserLogoutStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.user_logout.forms import UserLogoutStageForm
return UserLogoutStageForm
def __str__(self): def __str__(self):
return f"User Logout Stage {self.name}" return f"User Logout Stage {self.name}"

View File

@ -1,5 +1,9 @@
"""write stage models""" """write stage models"""
from typing import Type
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -8,8 +12,15 @@ class UserWriteStage(Stage):
"""Writes currently pending data into the pending user, or if no user exists, """Writes currently pending data into the pending user, or if no user exists,
creates a new user with the data.""" creates a new user with the data."""
type = "passbook.stages.user_write.stage.UserWriteStageView" def type(self) -> Type[View]:
form = "passbook.stages.user_write.forms.UserWriteStageForm" from passbook.stages.user_write.stage import UserWriteStageView
return UserWriteStageView
def form(self) -> Type[ModelForm]:
from passbook.stages.user_write.forms import UserWriteStageForm
return UserWriteStageForm
def __str__(self): def __str__(self):
return f"User Write Stage {self.name}" return f"User Write Stage {self.name}"