Merge branch 'master' into pf4
# Conflicts: # passbook/core/static/img/logos/discord.svg # passbook/core/static/js/passbook.js # passbook/core/templates/login/with_sources.html # passbook/core/templates/overview/index.html # passbook/core/views/authentication.py
This commit is contained in:
@ -15,11 +15,13 @@ class ApplicationSerializer(ModelSerializer):
|
||||
"pk",
|
||||
"name",
|
||||
"slug",
|
||||
"launch_url",
|
||||
"icon_url",
|
||||
"provider",
|
||||
"policies",
|
||||
"skip_authorization",
|
||||
"provider",
|
||||
"meta_launch_url",
|
||||
"meta_icon_url",
|
||||
"meta_description",
|
||||
"meta_publisher",
|
||||
"policies",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
"""passbook core exceptions"""
|
||||
from passbook.lib.sentry import SentryIgnoredException
|
||||
|
||||
|
||||
class PropertyMappingExpressionException(Exception):
|
||||
class PropertyMappingExpressionException(SentryIgnoredException):
|
||||
"""Error when a PropertyMapping Exception expression could not be parsed or evaluated."""
|
||||
|
||||
@ -19,19 +19,25 @@ class ApplicationForm(forms.ModelForm):
|
||||
fields = [
|
||||
"name",
|
||||
"slug",
|
||||
"launch_url",
|
||||
"icon_url",
|
||||
"provider",
|
||||
"policies",
|
||||
"skip_authorization",
|
||||
"provider",
|
||||
"meta_launch_url",
|
||||
"meta_icon_url",
|
||||
"meta_description",
|
||||
"meta_publisher",
|
||||
"policies",
|
||||
]
|
||||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
"launch_url": forms.TextInput(),
|
||||
"icon_url": forms.TextInput(),
|
||||
"meta_launch_url": forms.TextInput(),
|
||||
"meta_icon_url": forms.TextInput(),
|
||||
"meta_publisher": forms.TextInput(),
|
||||
"policies": FilteredSelectMultiple(_("policies"), False),
|
||||
}
|
||||
labels = {
|
||||
"launch_url": _("Launch URL"),
|
||||
"icon_url": _("Icon URL"),
|
||||
"meta_launch_url": _("Launch URL"),
|
||||
"meta_icon_url": _("Icon URL"),
|
||||
"meta_description": _("Description"),
|
||||
"meta_publisher": _("Publisher"),
|
||||
}
|
||||
help_texts = {"policies": _("Policies required to access this Application.")}
|
||||
|
||||
29
passbook/core/migrations/0008_auto_20200220_1242.py
Normal file
29
passbook/core/migrations/0008_auto_20200220_1242.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Generated by Django 3.0.3 on 2020-02-20 12:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("passbook_core", "0007_auto_20200217_1934"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="application", old_name="icon_url", new_name="meta_icon_url",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="application", old_name="launch_url", new_name="meta_launch_url",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="application",
|
||||
name="meta_description",
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="application",
|
||||
name="meta_publisher",
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@ -23,9 +23,10 @@ from structlog import get_logger
|
||||
|
||||
from passbook.core.exceptions import PropertyMappingExpressionException
|
||||
from passbook.core.signals import password_changed
|
||||
from passbook.core.types import UILoginButton, UIUserSettings
|
||||
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
|
||||
from passbook.policies.exceptions import PolicyException
|
||||
from passbook.policies.struct import PolicyRequest, PolicyResult
|
||||
from passbook.policies.types import PolicyRequest, PolicyResult
|
||||
|
||||
LOGGER = get_logger()
|
||||
NATIVE_ENVIRONMENT = NativeEnvironment()
|
||||
@ -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):
|
||||
@ -143,16 +132,19 @@ class Application(ExportModelOperationsMixin("application"), PolicyModel):
|
||||
|
||||
name = models.TextField()
|
||||
slug = models.SlugField()
|
||||
launch_url = models.URLField(null=True, blank=True)
|
||||
icon_url = models.TextField(null=True, blank=True)
|
||||
skip_authorization = models.BooleanField(default=False)
|
||||
provider = models.OneToOneField(
|
||||
"Provider", null=True, blank=True, default=None, on_delete=models.SET_DEFAULT
|
||||
)
|
||||
skip_authorization = models.BooleanField(default=False)
|
||||
|
||||
meta_launch_url = models.URLField(null=True, blank=True)
|
||||
meta_icon_url = models.TextField(null=True, blank=True)
|
||||
meta_description = models.TextField(null=True, blank=True)
|
||||
meta_publisher = models.TextField(null=True, blank=True)
|
||||
|
||||
objects = InheritanceManager()
|
||||
|
||||
def get_provider(self):
|
||||
def get_provider(self) -> Optional[Provider]:
|
||||
"""Get casted provider instance"""
|
||||
if not self.provider:
|
||||
return None
|
||||
@ -167,6 +159,7 @@ class Source(ExportModelOperationsMixin("source"), PolicyModel):
|
||||
|
||||
name = models.TextField()
|
||||
slug = models.SlugField()
|
||||
|
||||
enabled = models.BooleanField(default=True)
|
||||
property_mappings = models.ManyToManyField(
|
||||
"PropertyMapping", default=None, blank=True
|
||||
@ -177,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):
|
||||
|
||||
@ -1,46 +1,53 @@
|
||||
"""passbook user settings template tags"""
|
||||
from typing import List
|
||||
from typing import Iterable, List
|
||||
|
||||
from django import template
|
||||
from django.template.context import RequestContext
|
||||
|
||||
from passbook.core.models import Factor, Source, UserSettings
|
||||
from passbook.core.models import Factor, Source
|
||||
from passbook.core.types import UIUserSettings
|
||||
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
|
||||
|
||||
29
passbook/core/types.py
Normal file
29
passbook/core/types.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""passbook core dataclasses"""
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@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
|
||||
@ -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)
|
||||
if ui_login_button:
|
||||
kwargs["sources"].append(ui_login_button)
|
||||
ui_login_button = source.ui_login_button
|
||||
# if kwargs["sources"]:
|
||||
# self.template_name = "login/with_sources.html"
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
Reference in New Issue
Block a user