add signal for password change, add field for password policies

This commit is contained in:
Jens Langhammer 2019-02-25 15:41:36 +01:00
parent 5f3ab49535
commit 408e205c5f
6 changed files with 54 additions and 12 deletions

View file

@ -39,19 +39,18 @@ class FactorCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
source_type = self.request.GET.get('type') factor_type = self.request.GET.get('type')
model = next(x for x in all_subclasses(Factor) if x.__name__ == source_type) model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
kwargs['type'] = model._meta.verbose_name kwargs['type'] = model._meta.verbose_name
return kwargs return kwargs
def get_form_class(self): def get_form_class(self):
source_type = self.request.GET.get('type') factor_type = self.request.GET.get('type')
model = next(x for x in all_subclasses(Factor) if x.__name__ == source_type) model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
if not model: if not model:
raise Http404 raise Http404
return path_to_class(model.form) return path_to_class(model.form)
class FactorUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView): class FactorUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
"""Update factor""" """Update factor"""
@ -61,11 +60,12 @@ class FactorUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
success_message = _('Successfully updated Factor') success_message = _('Successfully updated Factor')
def get_form_class(self): def get_form_class(self):
source_type = self.request.GET.get('type') form_class_path = self.get_object().form
model = next(x for x in all_subclasses(Factor) if x.__name__ == source_type) form_class = path_to_class(form_class_path)
if not model: return form_class
raise Http404
return path_to_class(model.form) def get_object(self, queryset=None):
return Factor.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
class FactorDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView): class FactorDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
"""Delete factor""" """Delete factor"""

View file

@ -11,7 +11,7 @@ class PasswordFactorForm(forms.ModelForm):
class Meta: class Meta:
model = PasswordFactor model = PasswordFactor
fields = GENERAL_FIELDS + ['backends'] fields = GENERAL_FIELDS + ['backends', 'password_policies']
widgets = { widgets = {
'name': forms.TextInput(), 'name': forms.TextInput(),
'order': forms.NumberInput(), 'order': forms.NumberInput(),

View file

@ -0,0 +1,25 @@
# Generated by Django 2.1.7 on 2019-02-25 14:38
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0010_auto_20190224_1016'),
]
operations = [
migrations.AddField(
model_name='passwordfactor',
name='password_policies',
field=models.ManyToManyField(blank=True, to='passbook_core.Policy'),
),
migrations.AddField(
model_name='user',
name='password_change_date',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
]

View file

@ -9,9 +9,11 @@ from django.contrib.auth.models import AbstractUser
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.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.timezone import now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from model_utils.managers import InheritanceManager from model_utils.managers import InheritanceManager
from passbook.core.signals import password_changed
from passbook.lib.models import CreatedUpdatedModel, UUIDModel from passbook.lib.models import CreatedUpdatedModel, UUIDModel
LOGGER = getLogger(__name__) LOGGER = getLogger(__name__)
@ -38,6 +40,12 @@ class User(AbstractUser):
sources = models.ManyToManyField('Source', through='UserSourceConnection') sources = models.ManyToManyField('Source', through='UserSourceConnection')
applications = models.ManyToManyField('Application') applications = models.ManyToManyField('Application')
groups = models.ManyToManyField('Group') groups = models.ManyToManyField('Group')
password_change_date = models.DateTimeField(auto_now_add=True)
def set_password(self, password):
password_changed.send(sender=self, user=self, password=password)
self.password_change_date = now()
return super().set_password(password)
class Provider(models.Model): class Provider(models.Model):
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application""" """Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
@ -87,6 +95,7 @@ class PasswordFactor(Factor):
"""Password-based Django-backend Authentication Factor""" """Password-based Django-backend Authentication Factor"""
backends = ArrayField(models.TextField()) backends = ArrayField(models.TextField())
password_policies = models.ManyToManyField('Policy', blank=True)
type = 'passbook.core.auth.factors.password.PasswordFactor' type = 'passbook.core.auth.factors.password.PasswordFactor'
form = 'passbook.core.forms.factors.PasswordFactorForm' form = 'passbook.core.forms.factors.PasswordFactorForm'
@ -94,6 +103,13 @@ class PasswordFactor(Factor):
def has_user_settings(self): def has_user_settings(self):
return _('Change Password'), 'pficon-key', 'passbook_core:user-change-password' return _('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"""
for policy in self.policies.all():
if not policy.passes(user):
return False
return True
def __str__(self): def __str__(self):
return "Password Factor %s" % self.slug return "Password Factor %s" % self.slug

View file

@ -9,3 +9,4 @@ from django.core.signals import Signal
user_signed_up = Signal(providing_args=['request', 'user']) user_signed_up = Signal(providing_args=['request', 'user'])
invitation_created = Signal(providing_args=['request', 'invitation']) invitation_created = Signal(providing_args=['request', 'invitation'])
invitation_used = Signal(providing_args=['request', 'invitation', 'user']) invitation_used = Signal(providing_args=['request', 'invitation', 'user'])
password_changed = Signal(providing_args=['user', 'password'])

View file

@ -29,7 +29,7 @@ class LDAPSource(Source):
form = 'passbook.ldap.forms.LDAPSourceForm' form = 'passbook.ldap.forms.LDAPSourceForm'
@property @property
def get_url(self): def get_login_button(self):
raise NotImplementedError() raise NotImplementedError()
class Meta: class Meta: