diff --git a/passbook/admin/templates/administration/base.html b/passbook/admin/templates/administration/base.html
index 208108c0d..46a0aabcb 100644
--- a/passbook/admin/templates/administration/base.html
+++ b/passbook/admin/templates/administration/base.html
@@ -39,6 +39,9 @@
{% trans 'Users' %}
+
+ {% trans 'Groups' %}
+
{% trans 'Audit Log' %}
diff --git a/passbook/admin/templates/administration/group/list.html b/passbook/admin/templates/administration/group/list.html
new file mode 100644
index 000000000..4a87521b0
--- /dev/null
+++ b/passbook/admin/templates/administration/group/list.html
@@ -0,0 +1,45 @@
+{% extends "administration/base.html" %}
+
+{% load i18n %}
+{% load utils %}
+
+{% block title %}
+{% title %}
+{% endblock %}
+
+{% block content %}
+
+
{% trans "Groups" %}
+
{% trans "Group users together and give them permissions based on the membership." %}
+
+
+ {% trans 'Create...' %}
+
+
+
+
+
+ {% trans 'Name' %} |
+ {% trans 'Parent' %} |
+ {% trans 'Members' %} |
+ |
+
+
+
+ {% for group in object_list %}
+
+ {{ group.name }} |
+ {{ group.parent }} |
+ {{ group.user_set.all|length }} |
+
+ {% trans 'Edit' %}
+ {% trans 'Delete' %}
+ |
+
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/passbook/admin/templates/administration/groups/list.html b/passbook/admin/templates/administration/groups/list.html
deleted file mode 100644
index b8df11dc8..000000000
--- a/passbook/admin/templates/administration/groups/list.html
+++ /dev/null
@@ -1,83 +0,0 @@
-{% extends "administration/base.html" %}
-
-{% load i18n %}
-{% load static %}
-{% load utils %}
-
-{% block head %}
-{{ block.super }}
-
-{% endblock %}
-
-{% block scripts %}
-{{ block.super }}
-
-
-{% endblock %}
-
-{% block title %}
-{% title %}
-{% endblock %}
-
-{% block content %}
-
-
-
{% trans "Invitations" %}
-
- {% trans 'Create...' %}
-
-
-
-
-
- {% trans 'Expiry' %} |
- {% trans 'Link' %} |
- |
-
-
-
- {% for invitation in object_list %}
-
- {{ invitation.expires|default:"Never" }} |
-
- {{ invitation.link }}
- |
-
- {%
- trans 'Delete' %}
- |
-
- {% endfor %}
-
-
-
-{% endblock %}
diff --git a/passbook/admin/templates/generic/form.html b/passbook/admin/templates/generic/form.html
index 88bbec856..13cd7d8df 100644
--- a/passbook/admin/templates/generic/form.html
+++ b/passbook/admin/templates/generic/form.html
@@ -2,10 +2,20 @@
{% load i18n %}
{% load utils %}
+{% load static %}
{% block head %}
{{ block.super }}
{{ form.media.css }}
+
+
+
+
+
+
+
+
+
{% endblock %}
{% block content %}
diff --git a/passbook/admin/urls.py b/passbook/admin/urls.py
index fa4c9ec0a..3563a0c9b 100644
--- a/passbook/admin/urls.py
+++ b/passbook/admin/urls.py
@@ -67,6 +67,11 @@ urlpatterns = [
users.UserDeleteView.as_view(), name='user-delete'),
path('users//reset/',
users.UserPasswordResetView.as_view(), name='user-password-reset'),
+ # Groups
+ path('group/', groups.GroupListView.as_view(), name='group'),
+ path('group/create/', groups.GroupCreateView.as_view(), name='group-create'),
+ path('group//update/', groups.GroupUpdateView.as_view(), name='group-update'),
+ path('group//delete/', groups.GroupDeleteView.as_view(), name='group-delete'),
# Audit Log
path('audit/', audit.AuditEntryListView.as_view(), name='audit-log'),
# Groups
diff --git a/passbook/admin/views/groups.py b/passbook/admin/views/groups.py
index 629ed4aa0..1e120669d 100644
--- a/passbook/admin/views/groups.py
+++ b/passbook/admin/views/groups.py
@@ -1,12 +1,57 @@
"""passbook Group administration"""
-from django.views.generic import ListView
+from django.contrib import messages
+from django.contrib.messages.views import SuccessMessageMixin
+from django.urls import reverse_lazy
+from django.utils.translation import ugettext as _
+from django.views.generic import CreateView, DeleteView, ListView, UpdateView
from passbook.admin.mixins import AdminRequiredMixin
+from passbook.core.forms.groups import GroupForm
from passbook.core.models import Group
class GroupListView(AdminRequiredMixin, ListView):
- """Show list of all invitations"""
+ """Show list of all groups"""
model = Group
- template_name = 'administration/groups/list.html'
+ ordering = 'name'
+ template_name = 'administration/group/list.html'
+
+
+class GroupCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
+ """Create new Group"""
+
+ form_class = GroupForm
+
+ template_name = 'generic/create.html'
+ success_url = reverse_lazy('passbook_admin:groups')
+ success_message = _('Successfully created Group')
+
+ def get_context_data(self, **kwargs):
+ kwargs['type'] = 'Group'
+ return super().get_context_data(**kwargs)
+
+
+class GroupUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
+ """Update group"""
+
+ model = Group
+ form_class = GroupForm
+
+ template_name = 'generic/update.html'
+ success_url = reverse_lazy('passbook_admin:groups')
+ success_message = _('Successfully updated Group')
+
+
+class GroupDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
+ """Delete group"""
+
+ model = Group
+
+ template_name = 'generic/delete.html'
+ success_url = reverse_lazy('passbook_admin:groups')
+ success_message = _('Successfully deleted Group')
+
+ def delete(self, request, *args, **kwargs):
+ messages.success(self.request, self.success_message)
+ return super().delete(request, *args, **kwargs)
diff --git a/passbook/core/forms/applications.py b/passbook/core/forms/applications.py
index 47693fea5..254c999e6 100644
--- a/passbook/core/forms/applications.py
+++ b/passbook/core/forms/applications.py
@@ -1,5 +1,6 @@
"""passbook Core Application forms"""
from django import forms
+from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext_lazy as _
from passbook.core.models import Application, Provider
@@ -20,6 +21,7 @@ class ApplicationForm(forms.ModelForm):
'name': forms.TextInput(),
'launch_url': forms.TextInput(),
'icon_url': forms.TextInput(),
+ 'policies': FilteredSelectMultiple(_('policies'), False)
}
labels = {
'launch_url': _('Launch URL'),
diff --git a/passbook/core/forms/factors.py b/passbook/core/forms/factors.py
index 11d0061c6..79d52d0ed 100644
--- a/passbook/core/forms/factors.py
+++ b/passbook/core/forms/factors.py
@@ -1,5 +1,7 @@
"""passbook administration forms"""
from django import forms
+from django.contrib.admin.widgets import FilteredSelectMultiple
+from django.utils.translation import gettext as _
from passbook.core.models import DummyFactor, PasswordFactor
from passbook.lib.fields import DynamicArrayField
@@ -16,6 +18,7 @@ class PasswordFactorForm(forms.ModelForm):
widgets = {
'name': forms.TextInput(),
'order': forms.NumberInput(),
+ 'policies': FilteredSelectMultiple(_('policies'), False)
}
field_classes = {
'backends': DynamicArrayField
@@ -31,4 +34,5 @@ class DummyFactorForm(forms.ModelForm):
widgets = {
'name': forms.TextInput(),
'order': forms.NumberInput(),
+ 'policies': FilteredSelectMultiple(_('policies'), False)
}
diff --git a/passbook/core/forms/groups.py b/passbook/core/forms/groups.py
new file mode 100644
index 000000000..3a776cd17
--- /dev/null
+++ b/passbook/core/forms/groups.py
@@ -0,0 +1,32 @@
+"""passbook Core Group forms"""
+from django import forms
+from django.contrib.admin.widgets import FilteredSelectMultiple
+
+from passbook.core.models import Group, User
+
+
+class GroupForm(forms.ModelForm):
+ """Group Form"""
+
+ members = forms.ModelMultipleChoiceField(
+ User.objects.all(), required=False, widget=FilteredSelectMultiple('users', False))
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if self.instance.pk:
+ self.initial['members'] = self.instance.user_set.values_list('pk', flat=True)
+
+ def save(self, *args, **kwargs):
+ instance = super().save(*args, **kwargs)
+ if instance.pk:
+ instance.user_set.clear()
+ instance.user_set.add(*self.cleaned_data['members'])
+ return instance
+
+ class Meta:
+
+ model = Group
+ fields = ['name', 'parent', 'members', 'tags']
+ widgets = {
+ 'name': forms.TextInput(),
+ }
diff --git a/passbook/core/forms/policies.py b/passbook/core/forms/policies.py
index 3fd518c1b..f8d54a6c0 100644
--- a/passbook/core/forms/policies.py
+++ b/passbook/core/forms/policies.py
@@ -4,7 +4,8 @@ from django import forms
from django.utils.translation import gettext as _
from passbook.core.models import (DebugPolicy, FieldMatcherPolicy,
- PasswordPolicy, WebhookPolicy)
+ GroupMembershipPolicy, PasswordPolicy,
+ WebhookPolicy)
GENERAL_FIELDS = ['name', 'action', 'negate', 'order', ]
@@ -53,6 +54,17 @@ class DebugPolicyForm(forms.ModelForm):
}
+class GroupMembershipPolicyForm(forms.ModelForm):
+ """GroupMembershipPolicy Form"""
+
+ class Meta:
+
+ model = GroupMembershipPolicy
+ fields = GENERAL_FIELDS + ['group', ]
+ widgets = {
+ 'name': forms.TextInput(),
+ }
+
class PasswordPolicyForm(forms.ModelForm):
"""PasswordPolicy Form"""
diff --git a/passbook/core/migrations/0019_auto_20190310_1615.py b/passbook/core/migrations/0019_auto_20190310_1615.py
new file mode 100644
index 000000000..8b59ed40e
--- /dev/null
+++ b/passbook/core/migrations/0019_auto_20190310_1615.py
@@ -0,0 +1,25 @@
+# Generated by Django 2.1.7 on 2019-03-10 16:15
+
+import django.contrib.postgres.fields.hstore
+from django.contrib.postgres.operations import HStoreExtension
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('passbook_core', '0018_provider_property_mappings'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='group',
+ name='extra_data',
+ ),
+ HStoreExtension(),
+ migrations.AddField(
+ model_name='group',
+ name='tags',
+ field=django.contrib.postgres.fields.hstore.HStoreField(default=dict),
+ ),
+ ]
diff --git a/passbook/core/migrations/0020_groupmembershippolicy.py b/passbook/core/migrations/0020_groupmembershippolicy.py
new file mode 100644
index 000000000..f120f4908
--- /dev/null
+++ b/passbook/core/migrations/0020_groupmembershippolicy.py
@@ -0,0 +1,26 @@
+# Generated by Django 2.1.7 on 2019-03-10 18:25
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('passbook_core', '0019_auto_20190310_1615'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='GroupMembershipPolicy',
+ fields=[
+ ('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
+ ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Group')),
+ ],
+ options={
+ 'verbose_name': 'Group Membership Policy',
+ 'verbose_name_plural': 'Group Membership Policies',
+ },
+ bases=('passbook_core.policy',),
+ ),
+ ]
diff --git a/passbook/core/models.py b/passbook/core/models.py
index 582b1aa29..f02c3a482 100644
--- a/passbook/core/models.py
+++ b/passbook/core/models.py
@@ -8,7 +8,7 @@ from typing import Tuple, Union
from uuid import uuid4
from django.contrib.auth.models import AbstractUser
-from django.contrib.postgres.fields import ArrayField
+from django.contrib.postgres.fields import ArrayField, HStoreField
from django.db import models
from django.urls import reverse_lazy
from django.utils.timezone import now
@@ -31,7 +31,7 @@ class Group(UUIDModel):
name = models.CharField(_('name'), max_length=80)
parent = models.ForeignKey('Group', blank=True, null=True,
on_delete=models.SET_NULL, related_name='children')
- extra_data = models.TextField(blank=True)
+ tags = HStoreField(default=dict)
def __str__(self):
return "Group %s" % self.name
@@ -393,6 +393,21 @@ class DebugPolicy(Policy):
verbose_name = _('Debug Policy')
verbose_name_plural = _('Debug Policies')
+class GroupMembershipPolicy(Policy):
+ """Policy to check if the user is member in a certain group"""
+
+ group = models.ForeignKey('Group', on_delete=models.CASCADE)
+
+ form = 'passbook.core.forms.policies.GroupMembershipPolicyForm'
+
+ def passes(self, user: User) -> Union[bool, Tuple[bool, str]]:
+ return self.group.user_set.filter(pk=user.pk).exists()
+
+ class Meta:
+
+ verbose_name = _('Group Membership Policy')
+ verbose_name_plural = _('Group Membership Policies')
+
class Invitation(UUIDModel):
"""Single-use invitation link"""
diff --git a/passbook/core/settings.py b/passbook/core/settings.py
index 580c54f6f..495dd9282 100644
--- a/passbook/core/settings.py
+++ b/passbook/core/settings.py
@@ -60,6 +60,7 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
+ 'django.contrib.postgres',
'rest_framework',
'drf_yasg',
'raven.contrib.django.raven_compat',
diff --git a/passbook/core/static/css/passbook.css b/passbook/core/static/css/passbook.css
index 86711d8b1..ca643f80d 100644
--- a/passbook/core/static/css/passbook.css
+++ b/passbook/core/static/css/passbook.css
@@ -21,3 +21,177 @@
.dynamic-array-widget .remove:hover {
cursor: pointer;
}
+
+/* Selector */
+
+.selector {
+ display: flex;
+ width: 100%;
+ height: 45vh;
+}
+
+.selector .selector-filter {
+ display: flex;
+ align-items: center;
+}
+
+.selector .selector-filter label {
+ margin: 0 8px 0 0;
+}
+
+.selector .selector-filter input {
+ width: auto;
+ min-height: 0;
+ flex: 1 1;
+}
+
+.selector-available, .selector-chosen {
+ width: auto;
+ flex: 1 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.selector select {
+ width: 100%;
+ flex: 1 0 auto;
+ margin-bottom: 5px;
+}
+
+.selector ul.selector-chooser {
+ width: 26px;
+ height: 52px;
+ padding: 2px 0;
+ margin: auto 15px;
+ border-radius: 20px;
+ transform: translateY(-10px);
+ list-style: none;
+}
+
+.selector-add, .selector-remove {
+ width: 20px;
+ height: 20px;
+ background-size: 20px auto;
+}
+
+.selector-add {
+ background-position: 0 -120px;
+}
+
+.selector-remove {
+ background-position: 0 -80px;
+}
+
+a.selector-chooseall, a.selector-clearall {
+ align-self: center;
+}
+
+.stacked {
+ flex-direction: column;
+ max-width: 480px;
+}
+
+.stacked > * {
+ flex: 0 1 auto;
+}
+
+.stacked select {
+ margin-bottom: 0;
+}
+
+.stacked .selector-available, .stacked .selector-chosen {
+ width: auto;
+}
+
+.stacked ul.selector-chooser {
+ width: 52px;
+ height: 26px;
+ padding: 0 2px;
+ margin: 15px auto;
+ transform: none;
+}
+
+.stacked .selector-chooser li {
+ padding: 3px;
+}
+
+.stacked .selector-add, .stacked .selector-remove {
+ background-size: 20px auto;
+}
+
+.stacked .selector-add {
+ background-position: 0 -40px;
+}
+
+.stacked .active.selector-add {
+ background-position: 0 -60px;
+}
+
+.stacked .selector-remove {
+ background-position: 0 0;
+}
+
+.stacked .active.selector-remove {
+ background-position: 0 -20px;
+}
+
+.help-tooltip, .selector .help-icon {
+ display: none;
+}
+
+form .form-row p.datetime {
+ width: 100%;
+}
+
+.datetime input {
+ width: 50%;
+ max-width: 120px;
+}
+
+.datetime span {
+ font-size: 13px;
+}
+
+.datetime .timezonewarning {
+ display: block;
+ font-size: 11px;
+ color: #999;
+}
+
+.datetimeshortcuts {
+ color: #ccc;
+}
+
+.inline-group {
+ overflow: auto;
+}
+
+.selector-add, .selector-remove {
+ width: 16px;
+ height: 16px;
+ display: block;
+ text-indent: -3000px;
+ overflow: hidden;
+ cursor: default;
+ opacity: 0.3;
+}
+
+.active.selector-add, .active.selector-remove {
+ opacity: 1;
+}
+
+.active.selector-add:hover, .active.selector-remove:hover {
+ cursor: pointer;
+}
+
+.selector-add {
+ background: url(../admin/img/selector-icons.svg) 0 -96px no-repeat;
+}
+
+.active.selector-add:focus, .active.selector-add:hover {
+ background-position: 0 -112px;
+}
+
+.selector-remove {
+ background: url(../admin/img/selector-icons.svg) 0 -64px no-repeat;
+}
diff --git a/passbook/hibp_policy/forms.py b/passbook/hibp_policy/forms.py
index 08254a53b..643bf4791 100644
--- a/passbook/hibp_policy/forms.py
+++ b/passbook/hibp_policy/forms.py
@@ -1,6 +1,8 @@
"""passbook HaveIBeenPwned Policy forms"""
from django import forms
+from django.contrib.admin.widgets import FilteredSelectMultiple
+from django.utils.translation import gettext as _
from passbook.core.forms.policies import GENERAL_FIELDS
from passbook.hibp_policy.models import HaveIBeenPwendPolicy
@@ -16,4 +18,5 @@ class HaveIBeenPwnedPolicyForm(forms.ModelForm):
widgets = {
'name': forms.TextInput(),
'order': forms.NumberInput(),
+ 'policies': FilteredSelectMultiple(_('policies'), False)
}
diff --git a/passbook/ldap/forms.py b/passbook/ldap/forms.py
index cb47328c1..c663a1c00 100644
--- a/passbook/ldap/forms.py
+++ b/passbook/ldap/forms.py
@@ -1,10 +1,12 @@
"""passbook LDAP Forms"""
from django import forms
+from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext_lazy as _
from passbook.admin.forms.source import SOURCE_FORM_FIELDS
-from passbook.ldap.models import LDAPSource
+from passbook.core.forms.policies import GENERAL_FIELDS
+from passbook.ldap.models import LDAPGroupMembershipPolicy, LDAPSource
class LDAPSourceForm(forms.ModelForm):
@@ -23,6 +25,7 @@ class LDAPSourceForm(forms.ModelForm):
'bind_password': forms.TextInput(),
'domain': forms.TextInput(),
'base_dn': forms.TextInput(),
+ 'policies': FilteredSelectMultiple(_('policies'), False)
}
labels = {
'server_uri': _('Server URI'),
@@ -30,58 +33,18 @@ class LDAPSourceForm(forms.ModelForm):
'base_dn': _('Base DN'),
}
-# class GeneralSettingsForm(SettingsForm):
-# """general settings form"""
-# MODE_AUTHENTICATION_BACKEND = 'auth_backend'
-# MODE_CREATE_USERS = 'create_users'
-# MODE_CHOICES = (
-# (MODE_AUTHENTICATION_BACKEND, _('Authentication Backend')),
-# (MODE_CREATE_USERS, _('Create Users'))
-# )
-# namespace = 'passbook.ldap'
-# settings = ['enabled', 'mode']
+class LDAPGroupMembershipPolicyForm(forms.ModelForm):
+ """LDAPGroupMembershipPolicy Form"""
-# widgets = {
-# 'enabled': forms.BooleanField(required=False),
-# 'mode': forms.ChoiceField(widget=forms.RadioSelect, choices=MODE_CHOICES),
-# }
+ class Meta:
-
-# class ConnectionSettings(SettingsForm):
-# """Connection settings form"""
-
-# namespace = 'passbook.ldap'
-# settings = ['server', 'server:tls', 'bind:user', 'bind:password', 'domain']
-
-# attrs_map = {
-# 'server': {'placeholder': 'dc1.corp.exmaple.com'},
-# 'bind:user': {'placeholder': 'Administrator'},
-# 'domain': {'placeholder': 'corp.example.com'},
-# }
-
-# widgets = {
-# 'server:tls': forms.BooleanField(required=False, label=_('Server TLS')),
-# }
-
-
-# class AuthenticationBackendSettings(SettingsForm):
-# """Authentication backend settings"""
-
-# namespace = 'passbook.ldap'
-# settings = ['base']
-
-# attrs_map = {
-# 'base': {'placeholder': 'DN in which to search for users'},
-# }
-
-
-# class CreateUsersSettings(SettingsForm):
-# """Create users settings"""
-
-# namespace = 'passbook.ldap'
-# settings = ['create_base']
-
-# attrs_map = {
-# 'create_base': {'placeholder': 'DN in which to create users'},
-# }
+ model = LDAPGroupMembershipPolicy
+ fields = GENERAL_FIELDS + ['dn', ]
+ widgets = {
+ 'name': forms.TextInput(),
+ 'dn': forms.TextInput(),
+ }
+ labels = {
+ 'dn': _('DN')
+ }
diff --git a/passbook/ldap/migrations/0002_ldapgroupmembershippolicy.py b/passbook/ldap/migrations/0002_ldapgroupmembershippolicy.py
new file mode 100644
index 000000000..a7f2bed7e
--- /dev/null
+++ b/passbook/ldap/migrations/0002_ldapgroupmembershippolicy.py
@@ -0,0 +1,28 @@
+# Generated by Django 2.1.7 on 2019-03-10 18:38
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('passbook_core', '0020_groupmembershippolicy'),
+ ('passbook_ldap', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='LDAPGroupMembershipPolicy',
+ fields=[
+ ('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
+ ('dn', models.TextField()),
+ ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_ldap.LDAPSource')),
+ ],
+ options={
+ 'verbose_name': 'LDAP Group Membership Policy',
+ 'verbose_name_plural': 'LDAP Group Membership Policys',
+ },
+ bases=('passbook_core.policy',),
+ ),
+ ]
diff --git a/passbook/ldap/models.py b/passbook/ldap/models.py
index 6d112cfc7..190da2ebe 100644
--- a/passbook/ldap/models.py
+++ b/passbook/ldap/models.py
@@ -3,7 +3,7 @@
from django.db import models
from django.utils.translation import gettext as _
-from passbook.core.models import Source
+from passbook.core.models import Policy, Source, User
class LDAPSource(Source):
@@ -37,30 +37,19 @@ class LDAPSource(Source):
verbose_name = _('LDAP Source')
verbose_name_plural = _('LDAP Sources')
+class LDAPGroupMembershipPolicy(Policy):
+ """Policy to check if a user is in a certain LDAP Group"""
-# class LDAPModification(UUIDModel, CreatedUpdatedModel):
-# """Store LDAP Data in DB if LDAP Server is unavailable"""
-# ACTION_ADD = 'ADD'
-# ACTION_MODIFY = 'MODIFY'
+ dn = models.TextField()
+ source = models.ForeignKey('LDAPSource', on_delete=models.CASCADE)
-# ACTIONS = (
-# (ACTION_ADD, 'ADD'),
-# (ACTION_MODIFY, 'MODIFY'),
-# )
+ form = 'passbook.ldap.forms.LDAPGroupMembershipPolicyForm'
-# dn = models.CharField(max_length=255)
-# action = models.CharField(max_length=17, choices=ACTIONS, default=ACTION_MODIFY)
-# data = JSONField()
+ def passes(self, user: User):
+ """Check if user instance passes this policy"""
+ raise NotImplementedError()
-# def __str__(self):
-# return "LDAPModification %d from %s" % (self.pk, self.created)
+ class Meta:
-
-# class LDAPGroupMapping(UUIDModel, CreatedUpdatedModel):
-# """Model to map an LDAP Group to a passbook group"""
-
-# ldap_dn = models.TextField()
-# group = models.ForeignKey(Group, on_delete=models.CASCADE)
-
-# def __str__(self):
-# return "LDAPGroupMapping %s -> %s" % (self.ldap_dn, self.group.name)
+ verbose_name = _('LDAP Group Membership Policy')
+ verbose_name_plural = _('LDAP Group Membership Policys')
diff --git a/passbook/oauth_client/forms.py b/passbook/oauth_client/forms.py
index 6187f4869..cd9036316 100644
--- a/passbook/oauth_client/forms.py
+++ b/passbook/oauth_client/forms.py
@@ -1,6 +1,7 @@
"""passbook oauth_client forms"""
from django import forms
+from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext as _
from passbook.admin.forms.source import SOURCE_FORM_FIELDS
@@ -29,6 +30,7 @@ class OAuthSourceForm(forms.ModelForm):
'consumer_key': forms.TextInput(),
'consumer_secret': forms.TextInput(),
'provider_type': forms.Select(choices=MANAGER.get_name_tuple()),
+ 'policies': FilteredSelectMultiple(_('policies'), False)
}
labels = {
'request_token_url': _('Request Token URL'),
diff --git a/passbook/otp/forms.py b/passbook/otp/forms.py
index 52c00b3d2..bfe41e0bc 100644
--- a/passbook/otp/forms.py
+++ b/passbook/otp/forms.py
@@ -1,6 +1,7 @@
"""passbook OTP Forms"""
from django import forms
+from django.contrib.admin.widgets import FilteredSelectMultiple
from django.core.validators import RegexValidator
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
@@ -63,4 +64,5 @@ class OTPFactorForm(forms.ModelForm):
widgets = {
'name': forms.TextInput(),
'order': forms.NumberInput(),
+ 'policies': FilteredSelectMultiple(_('policies'), False)
}
diff --git a/passbook/password_expiry_policy/forms.py b/passbook/password_expiry_policy/forms.py
index cd957af0a..be496cd0b 100644
--- a/passbook/password_expiry_policy/forms.py
+++ b/passbook/password_expiry_policy/forms.py
@@ -1,6 +1,7 @@
"""passbook PasswordExpiry Policy forms"""
from django import forms
+from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext as _
from passbook.core.forms.policies import GENERAL_FIELDS
@@ -18,6 +19,7 @@ class PasswordExpiryPolicyForm(forms.ModelForm):
'name': forms.TextInput(),
'order': forms.NumberInput(),
'days': forms.NumberInput(),
+ 'policies': FilteredSelectMultiple(_('policies'), False)
}
labels = {
'deny_only': _("Only fail the policy, don't set user's password.")
diff --git a/passbook/saml_idp/forms.py b/passbook/saml_idp/forms.py
index e54bd6306..d305b2990 100644
--- a/passbook/saml_idp/forms.py
+++ b/passbook/saml_idp/forms.py
@@ -1,6 +1,8 @@
"""passbook SAML IDP Forms"""
from django import forms
+from django.contrib.admin.widgets import FilteredSelectMultiple
+from django.utils.translation import gettext as _
from passbook.lib.fields import DynamicArrayField
from passbook.saml_idp.models import (SAMLPropertyMapping, SAMLProvider,
@@ -32,6 +34,7 @@ class SAMLProviderForm(forms.ModelForm):
widgets = {
'name': forms.TextInput(),
'issuer': forms.TextInput(),
+ 'property_mappings': FilteredSelectMultiple(_('Property Mappings'), False)
}