*(minor): switch has_user_settings to return Optional dataclass instead of tuple

This commit is contained in:
Langhammer, Jens 2019-10-09 12:47:14 +02:00
parent 088b9592cd
commit 2e15b24f0a
8 changed files with 62 additions and 43 deletions

View File

@ -2,6 +2,7 @@
from datetime import timedelta
from random import SystemRandom
from time import sleep
from typing import Optional
from uuid import uuid4
from django.contrib.auth.models import AbstractUser
@ -74,6 +75,20 @@ 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(PolicyModel):
"""Authentication factor, multiple instances of the same Factor can be used"""
@ -86,11 +101,10 @@ class Factor(PolicyModel):
type = ''
form = ''
def has_user_settings(self):
"""Entrypoint to integrate with User settings. Can either return False if no
user settings are available, or a tuple or string, string, string where the first string
is the name the item has, the second string is the icon and the third is the view-name."""
return False
def user_settings(self) -> Optional[UserSettings]:
"""Entrypoint to integrate with User settings. Can either return None if no
user settings are available, or an instanace of UserSettings."""
return None
def __str__(self):
return f"Factor {self.slug}"
@ -147,11 +161,10 @@ class Source(PolicyModel):
"""Return additional Info, such as a callback URL. Show in the administration interface."""
return None
def has_user_settings(self):
"""Entrypoint to integrate with User settings. Can either return False if no
user settings are available, or a tuple or string, string, string where the first string
is the name the item has, the second string is the icon and the third is the view-name."""
return False
def user_settings(self) -> Optional[UserSettings]:
"""Entrypoint to integrate with User settings. Can either return None if no
user settings are available, or an instanace of UserSettings."""
return None
def __str__(self):
return self.name

View File

@ -18,19 +18,19 @@
</li>
<li class="nav-divider"></li>
{% user_factors as uf %}
{% for name, icon, link in uf %}
<li class="{% is_active link %}">
<a href="{% url link %}">
<i class="{{ icon }}"></i> {{ name }}
{% for user_settings in uf %}
<li class="{% is_active user_settings.view_name %}">
<a href="{% url user_settings.view_name %}">
<i class="{{ user_settings.icon }}"></i> {{ user_settings.name }}
</a>
</li>
{% endfor %}
<li class="nav-divider"></li>
{% user_sources as us %}
{% for name, icon, link in us %}
<li class="{% if link == request.get_full_path %} active {% endif %}">
<a href="{{ link }}">
<i class="{{ icon }}"></i> {{ name }}
{% for user_settings in us %}
<li class="{% if user_settings.view_name == request.get_full_path %} active {% endif %}">
<a href="{{ user_settings.view_name }}">
<i class="{{ user_settings.icon }}"></i> {{ user_settings.name }}
</a>
</li>
{% endfor %}

View File

@ -1,37 +1,37 @@
"""passbook user settings template tags"""
from typing import List
from django import template
from django.template.context import RequestContext
from passbook.core.models import Factor, Source
from passbook.core.models import Factor, Source, UserSettings
from passbook.policies.engine import PolicyEngine
register = template.Library()
@register.simple_tag(takes_context=True)
def user_factors(context: RequestContext):
def user_factors(context: RequestContext) -> List[UserSettings]:
"""Return list of all factors which apply to user"""
user = context.get('request').user
_all_factors = Factor.objects.filter(enabled=True).order_by('order').select_subclasses()
matching_factors = []
matching_factors: List[UserSettings] = []
for factor in _all_factors:
_link = factor.has_user_settings()
user_settings = factor.user_settings()
policy_engine = PolicyEngine(factor.policies.all())
policy_engine.for_user(user).with_request(context.get('request')).build()
if policy_engine.passing and _link:
matching_factors.append(_link)
if policy_engine.passing and user_settings:
matching_factors.append(user_settings)
return matching_factors
@register.simple_tag(takes_context=True)
def user_sources(context: RequestContext):
def user_sources(context: RequestContext) -> List[UserSettings]:
"""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 = []
matching_sources: List[UserSettings] = []
for factor in _all_sources:
_link = factor.has_user_settings()
user_settings = factor.user_settings()
policy_engine = PolicyEngine(factor.policies.all())
policy_engine.for_user(user).with_request(context.get('request')).build()
if policy_engine.passing and _link:
matching_sources.append(_link)
if policy_engine.passing and user_settings:
matching_sources.append(user_settings)
return matching_sources

View File

@ -0,0 +1,5 @@
"""captcha factor admin"""
from passbook.lib.admin import admin_autoregister
admin_autoregister('passbook_factors_captcha')

View File

@ -1,6 +1,8 @@
"""passbook captcha factor forms"""
from captcha.fields import ReCaptchaField
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext as _
from passbook.factors.captcha.models import CaptchaFactor
from passbook.factors.forms import GENERAL_FIELDS
@ -21,6 +23,7 @@ class CaptchaFactorForm(forms.ModelForm):
widgets = {
'name': forms.TextInput(),
'order': forms.NumberInput(),
'policies': FilteredSelectMultiple(_('policies'), False),
'public_key': forms.TextInput(),
'private_key': forms.TextInput(),
}

View File

@ -3,7 +3,7 @@
from django.db import models
from django.utils.translation import gettext as _
from passbook.core.models import Factor
from passbook.core.models import Factor, UserSettings
class OTPFactor(Factor):
@ -15,8 +15,8 @@ class OTPFactor(Factor):
type = 'passbook.factors.otp.factors.OTPFactor'
form = 'passbook.factors.otp.forms.OTPFactorForm'
def has_user_settings(self):
return _('OTP'), 'pficon-locked', 'passbook_factors_otp:otp-user-settings'
def user_settings(self) -> UserSettings:
return UserSettings(_('OTP'), 'pficon-locked', 'passbook_factors_otp:otp-user-settings')
def __str__(self):
return f"OTP Factor {self.slug}"

View File

@ -3,7 +3,7 @@ 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
from passbook.core.models import Factor, Policy, User, UserSettings
class PasswordFactor(Factor):
@ -16,8 +16,9 @@ class PasswordFactor(Factor):
type = 'passbook.factors.password.factor.PasswordFactor'
form = 'passbook.factors.password.forms.PasswordFactorForm'
def has_user_settings(self):
return _('Change Password'), 'pficon-key', 'passbook_core:user-change-password'
def user_settings(self):
return UserSettings(_('Change Password'), 'pficon-key',
'passbook_core:user-change-password')
def password_passes(self, user: User) -> bool:
"""Return true if user's password passes, otherwise False or raise Exception"""

View File

@ -4,7 +4,7 @@ from django.db import models
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _
from passbook.core.models import Source, UserSourceConnection
from passbook.core.models import Source, UserSourceConnection, UserSettings
from passbook.sources.oauth.clients import get_client
@ -37,18 +37,15 @@ class OAuthSource(Source):
reverse_lazy('passbook_sources_oauth:oauth-client-callback',
kwargs={'source_slug': self.slug})
def has_user_settings(self):
"""Entrypoint to integrate with User settings. Can either return False if no
user settings are available, or a tuple or string, string, string where the first string
is the name the item has, the second string is the icon and the third is the view-name."""
def user_settings(self) -> UserSettings:
icon_type = self.provider_type
if icon_type == 'azure ad':
icon_type = 'windows'
icon_class = 'fa fa-%s' % icon_type
view_name = 'passbook_sources_oauth:oauth-client-user'
return self.name, icon_class, reverse((view_name), kwargs={
return UserSettings(self.name, icon_class, reverse((view_name), kwargs={
'source_slug': self.slug
})
}))
class Meta: