Rule -> Policies

This commit is contained in:
Jens Langhammer 2019-02-16 10:24:31 +01:00
parent d32699b332
commit d6f4832e90
37 changed files with 334 additions and 544 deletions

View file

@ -4,7 +4,7 @@ from django import forms
from passbook.core.models import User from passbook.core.models import User
class RuleTestForm(forms.Form): class PolicyTestForm(forms.Form):
"""Form to test rule against user""" """Form to test policies against user"""
user = forms.ModelChoiceField(queryset=User.objects.all()) user = forms.ModelChoiceField(queryset=User.objects.all())

View file

@ -20,8 +20,8 @@
<li class="{% is_active 'passbook_admin:factors' 'passbook_admin:factor-create' 'passbook_admin:factor-update' 'passbook_admin:factor-delete' %}"> <li class="{% is_active 'passbook_admin:factors' 'passbook_admin:factor-create' 'passbook_admin:factor-update' 'passbook_admin:factor-delete' %}">
<a href="{% url 'passbook_admin:factors' %}">{% trans 'Factors' %}</a> <a href="{% url 'passbook_admin:factors' %}">{% trans 'Factors' %}</a>
</li> </li>
<li class="{% is_active 'passbook_admin:rules' 'passbook_admin:rule-create' 'passbook_admin:rule-update' 'passbook_admin:rule-delete' 'passbook_admin:rule-test' %}"> <li class="{% is_active 'passbook_admin:policys' 'passbook_admin:policy-create' 'passbook_admin:policy-update' 'passbook_admin:policy-delete' 'passbook_admin:policy-test' %}">
<a href="{% url 'passbook_admin:rules' %}">{% trans 'Rules' %}</a> <a href="{% url 'passbook_admin:policys' %}">{% trans 'Policies' %}</a>
</li> </li>
<li class="{% is_active 'passbook_admin:invitations' 'passbook_admin:invitation-create' 'passbook_admin:invitation-update' 'passbook_admin:invitation-delete' 'passbook_admin:invitation-test' %}"> <li class="{% is_active 'passbook_admin:invitations' 'passbook_admin:invitation-create' 'passbook_admin:invitation-update' 'passbook_admin:invitation-delete' 'passbook_admin:invitation-test' %}">
<a href="{% url 'passbook_admin:invitations' %}">{% trans 'Invitations' %}</a> <a href="{% url 'passbook_admin:invitations' %}">{% trans 'Invitations' %}</a>

View file

@ -31,11 +31,11 @@
<div class="col-xs-6 col-sm-2 col-md-2"> <div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status"> <div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title"> <h2 class="card-pf-title">
<a href="#"><span class="fa fa-shield"></span><span class="card-pf-aggregate-status-count"></span> {% trans 'Rules' %}</a> <a href="#"><span class="fa fa-shield"></span><span class="card-pf-aggregate-status-count"></span> {% trans 'Policies' %}</a>
</h2> </h2>
<div class="card-pf-body"> <div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications"> <p class="card-pf-aggregate-status-notifications">
<span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ rule_count }}</a></span> <span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ policy_count }}</a></span>
</p> </p>
</div> </div>
</div> </div>
@ -53,4 +53,4 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -9,7 +9,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<h1>{% trans "Rules" %}</h1> <h1>{% trans "Policies" %}</h1>
<div class="dropdown"> <div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="createDropdown" data-toggle="dropdown"> <button class="btn btn-primary dropdown-toggle" type="button" id="createDropdown" data-toggle="dropdown">
{% trans 'Create...' %} {% trans 'Create...' %}
@ -17,7 +17,7 @@
</button> </button>
<ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown"> <ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown">
{% for type, name in types.items %} {% for type, name in types.items %}
<li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'passbook_admin:rule-create' %}?type={{ type }}">{{ name }}</a></li> <li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'passbook_admin:policy-create' %}?type={{ type }}">{{ name }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -31,18 +31,18 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for rule in object_list %} {% for policy in object_list %}
<tr> <tr>
<td>{{ rule.name }}</td> <td>{{ policy.name }}</td>
<td>{{ rule|fieldtype }}</td> <td>{{ policy|fieldtype }}</td>
<td> <td>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:rule-update' pk=rule.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a> <a class="btn btn-default btn-sm" href="{% url 'passbook_admin:policy-update' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:rule-test' pk=rule.uuid %}?back={{ request.get_full_path }}">{% trans 'Test' %}</a> <a class="btn btn-default btn-sm" href="{% url 'passbook_admin:policy-test' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Test' %}</a>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:rule-delete' pk=rule.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a> <a class="btn btn-default btn-sm" href="{% url 'passbook_admin:policy-delete' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,7 @@
{% extends 'generic/form.html' %}
{% load i18n %}
{% block above_form %}
<h1>{% blocktrans with policy=policy %}Test policy {{ policy }}{% endblocktrans %}</h1>
{% endblock %}

View file

@ -1,7 +0,0 @@
{% extends 'generic/form.html' %}
{% load i18n %}
{% block above_form %}
<h1>{% blocktrans with rule=rule %}Test rule {{ rule }}{% endblocktrans %}</h1>
{% endblock %}

View file

@ -3,7 +3,7 @@ from django.urls import include, path
from rest_framework_swagger.views import get_swagger_view from rest_framework_swagger.views import get_swagger_view
from passbook.admin.views import (applications, audit, factors, groups, from passbook.admin.views import (applications, audit, factors, groups,
invitations, overview, providers, rules, invitations, overview, policy, providers,
sources, users) sources, users)
schema_view = get_swagger_view(title='passbook Admin Internal API') schema_view = get_swagger_view(title='passbook Admin Internal API')
@ -25,12 +25,12 @@ urlpatterns = [
path('sources/create/', sources.SourceCreateView.as_view(), name='source-create'), path('sources/create/', sources.SourceCreateView.as_view(), name='source-create'),
path('sources/<uuid:pk>/update/', sources.SourceUpdateView.as_view(), name='source-update'), path('sources/<uuid:pk>/update/', sources.SourceUpdateView.as_view(), name='source-update'),
path('sources/<uuid:pk>/delete/', sources.SourceDeleteView.as_view(), name='source-delete'), path('sources/<uuid:pk>/delete/', sources.SourceDeleteView.as_view(), name='source-delete'),
# Rules # Policies
path('rules/', rules.RuleListView.as_view(), name='rules'), path('policies/', policy.PolicyListView.as_view(), name='policies'),
path('rules/create/', rules.RuleCreateView.as_view(), name='rule-create'), path('policies/create/', policy.PolicyCreateView.as_view(), name='policy-create'),
path('rules/<uuid:pk>/update/', rules.RuleUpdateView.as_view(), name='rule-update'), path('policies/<uuid:pk>/update/', policy.PolicyUpdateView.as_view(), name='policy-update'),
path('rules/<uuid:pk>/delete/', rules.RuleDeleteView.as_view(), name='rule-delete'), path('policies/<uuid:pk>/delete/', policy.PolicyDeleteView.as_view(), name='policy-delete'),
path('rules/<uuid:pk>/test/', rules.RuleTestView.as_view(), name='rule-test'), path('policies/<uuid:pk>/test/', policy.PolicyTestView.as_view(), name='policy-test'),
# Providers # Providers
path('providers/', providers.ProviderListView.as_view(), name='providers'), path('providers/', providers.ProviderListView.as_view(), name='providers'),
path('providers/create/', path('providers/create/',

View file

@ -2,7 +2,7 @@
from django.views.generic import TemplateView from django.views.generic import TemplateView
from passbook.admin.mixins import AdminRequiredMixin from passbook.admin.mixins import AdminRequiredMixin
from passbook.core.models import Application, Provider, Rule, User from passbook.core.models import Application, Policy, Provider, User
class AdministrationOverviewView(AdminRequiredMixin, TemplateView): class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
@ -12,7 +12,7 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['application_count'] = len(Application.objects.all()) kwargs['application_count'] = len(Application.objects.all())
kwargs['rule_count'] = len(Rule.objects.all()) kwargs['policy_count'] = len(Policy.objects.all())
kwargs['user_count'] = len(User.objects.all()) kwargs['user_count'] = len(User.objects.all())
kwargs['provider_count'] = len(Provider.objects.all()) kwargs['provider_count'] = len(Provider.objects.all())
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View file

@ -0,0 +1,104 @@
"""passbook Policy administration"""
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.http import Http404
from django.urls import reverse_lazy
from django.utils.translation import ugettext as _
from django.views.generic import (CreateView, DeleteView, FormView, ListView,
UpdateView)
from django.views.generic.detail import DetailView
from passbook.admin.forms.policies import PolicyTestForm
from passbook.admin.mixins import AdminRequiredMixin
from passbook.core.models import Policy
from passbook.lib.utils.reflection import path_to_class
class PolicyListView(AdminRequiredMixin, ListView):
"""Show list of all policys"""
model = Policy
template_name = 'administration/policy/list.html'
def get_context_data(self, **kwargs):
kwargs['types'] = {
x.__name__: x._meta.verbose_name for x in Policy.__subclasses__()}
return super().get_context_data(**kwargs)
def get_queryset(self):
return super().get_queryset().order_by('order').select_subclasses()
class PolicyCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
"""Create new Policy"""
template_name = 'generic/create_inheritance.html'
success_url = reverse_lazy('passbook_admin:policys')
success_message = _('Successfully created Policy')
def get_form_class(self):
policy_type = self.request.GET.get('type')
model = next(x for x in Policy.__subclasses__()
if x.__name__ == policy_type)
if not model:
raise Http404
return path_to_class(model.form)
class PolicyUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
"""Update policy"""
model = Policy
template_name = 'generic/update.html'
success_url = reverse_lazy('passbook_admin:policys')
success_message = _('Successfully updated Policy')
def get_form_class(self):
form_class_path = self.get_object().form
form_class = path_to_class(form_class_path)
return form_class
def get_object(self, queryset=None):
return Policy.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
class PolicyDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
"""Delete policy"""
model = Policy
template_name = 'generic/delete.html'
success_url = reverse_lazy('passbook_admin:policys')
success_message = _('Successfully updated Policy')
def get_object(self, queryset=None):
return Policy.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
class PolicyTestView(AdminRequiredMixin, DetailView, FormView):
"""View to test policy(s)"""
model = Policy
form_class = PolicyTestForm
template_name = 'administration/policy/test.html'
object = None
def get_object(self, queryset=None):
return Policy.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
def get_context_data(self, **kwargs):
kwargs['policy'] = self.get_object()
return super().get_context_data(**kwargs)
def post(self, *args, **kwargs):
self.object = self.get_object()
return super().post(*args, **kwargs)
def form_valid(self, form):
policy = self.get_object()
user = form.cleaned_data.get('user')
result = policy.passes(user)
if result:
messages.success(self.request, _('User successfully passed policy.'))
else:
messages.error(self.request, _("User didn't pass policy."))
return self.render_to_response(self.get_context_data(form=form, result=result))

View file

@ -1,104 +0,0 @@
"""passbook Rule administration"""
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.http import Http404
from django.urls import reverse_lazy
from django.utils.translation import ugettext as _
from django.views.generic import (CreateView, DeleteView, FormView, ListView,
UpdateView)
from django.views.generic.detail import DetailView
from passbook.admin.forms.rule import RuleTestForm
from passbook.admin.mixins import AdminRequiredMixin
from passbook.core.models import Rule
from passbook.lib.utils.reflection import path_to_class
class RuleListView(AdminRequiredMixin, ListView):
"""Show list of all rules"""
model = Rule
template_name = 'administration/rule/list.html'
def get_context_data(self, **kwargs):
kwargs['types'] = {
x.__name__: x._meta.verbose_name for x in Rule.__subclasses__()}
return super().get_context_data(**kwargs)
def get_queryset(self):
return super().get_queryset().order_by('order').select_subclasses()
class RuleCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
"""Create new Rule"""
template_name = 'generic/create_inheritance.html'
success_url = reverse_lazy('passbook_admin:rules')
success_message = _('Successfully created Rule')
def get_form_class(self):
rule_type = self.request.GET.get('type')
model = next(x for x in Rule.__subclasses__()
if x.__name__ == rule_type)
if not model:
raise Http404
return path_to_class(model.form)
class RuleUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
"""Update rule"""
model = Rule
template_name = 'generic/update.html'
success_url = reverse_lazy('passbook_admin:rules')
success_message = _('Successfully updated Rule')
def get_form_class(self):
form_class_path = self.get_object().form
form_class = path_to_class(form_class_path)
return form_class
def get_object(self, queryset=None):
return Rule.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
class RuleDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
"""Delete rule"""
model = Rule
template_name = 'generic/delete.html'
success_url = reverse_lazy('passbook_admin:rules')
success_message = _('Successfully updated Rule')
def get_object(self, queryset=None):
return Rule.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
class RuleTestView(AdminRequiredMixin, DetailView, FormView):
"""View to test rule(s)"""
model = Rule
form_class = RuleTestForm
template_name = 'administration/rule/test.html'
object = None
def get_object(self, queryset=None):
return Rule.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
def get_context_data(self, **kwargs):
kwargs['rule'] = self.get_object()
return super().get_context_data(**kwargs)
def post(self, *args, **kwargs):
self.object = self.get_object()
return super().post(*args, **kwargs)
def form_valid(self, form):
rule = self.get_object()
user = form.cleaned_data.get('user')
result = rule.passes(user)
if result:
messages.success(self.request, _('User successfully passed rule.'))
else:
messages.error(self.request, _("User didn't pass rule."))
return self.render_to_response(self.get_context_data(form=form, result=result))

View file

@ -1,4 +1,4 @@
# Generated by Django 2.1.3 on 2018-11-25 10:39 # Generated by Django 2.1.7 on 2019-02-16 09:13
import uuid import uuid
@ -20,13 +20,32 @@ class Migration(migrations.Migration):
name='AuditEntry', name='AuditEntry',
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('action', models.TextField()), ('action', models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')])),
('date', models.DateTimeField(auto_now_add=True)), ('date', models.DateTimeField(auto_now_add=True)),
('app', models.TextField()), ('app', models.TextField()),
('_context', models.TextField()),
('request_ip', models.GenericIPAddressField()),
('created', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
], ],
options={ options={
'abstract': False, 'verbose_name': 'Audit Entry',
'verbose_name_plural': 'Audit Entries',
}, },
), ),
migrations.CreateModel(
name='LoginAttempt',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateField(auto_now_add=True)),
('last_updated', models.DateTimeField(auto_now=True)),
('target_uid', models.CharField(max_length=254)),
('request_ip', models.GenericIPAddressField()),
('attempts', models.IntegerField(default=1)),
],
),
migrations.AlterUniqueTogether(
name='loginattempt',
unique_together={('target_uid', 'request_ip', 'created')},
),
] ]

View file

@ -1,30 +0,0 @@
# Generated by Django 2.1.4 on 2018-12-10 10:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_audit', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='auditentry',
name='context',
field=models.TextField(default=''),
preserve_default=False,
),
migrations.AddField(
model_name='auditentry',
name='request_ip',
field=models.GenericIPAddressField(default=''),
preserve_default=False,
),
migrations.AlterField(
model_name='auditentry',
name='action',
field=models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset')]),
),
]

View file

@ -1,27 +0,0 @@
# Generated by Django 2.1.4 on 2018-12-10 12:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_audit', '0002_auto_20181210_1039'),
]
operations = [
migrations.AlterModelOptions(
name='auditentry',
options={'verbose_name': 'Audit Entry', 'verbose_name_plural': 'Audit Entries'},
),
migrations.RenameField(
model_name='auditentry',
old_name='context',
new_name='_context',
),
migrations.AlterField(
model_name='auditentry',
name='action',
field=models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_used', 'invitation_used')]),
),
]

View file

@ -1,18 +0,0 @@
# Generated by Django 2.1.4 on 2018-12-10 13:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_audit', '0003_auto_20181210_1213'),
]
operations = [
migrations.AlterField(
model_name='auditentry',
name='action',
field=models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')]),
),
]

View file

@ -1,20 +0,0 @@
# Generated by Django 2.1.4 on 2018-12-11 15:00
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_audit', '0004_auto_20181210_1348'),
]
operations = [
migrations.AddField(
model_name='auditentry',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
]

View file

@ -1,28 +0,0 @@
# Generated by Django 2.1.4 on 2018-12-18 12:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_audit', '0005_auditentry_created'),
]
operations = [
migrations.CreateModel(
name='LoginAttempt',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateField(auto_now_add=True)),
('last_updated', models.DateTimeField(auto_now=True)),
('target_uid', models.CharField(max_length=254)),
('request_ip', models.GenericIPAddressField()),
('attempts', models.IntegerField(default=1)),
],
),
migrations.AlterUniqueTogether(
name='loginattempt',
unique_together={('target_uid', 'request_ip', 'created')},
),
]

View file

@ -16,7 +16,7 @@ class PassbookCoreConfig(AppConfig):
verbose_name = 'passbook Core' verbose_name = 'passbook Core'
def ready(self): def ready(self):
import_module('passbook.core.rules') import_module('passbook.core.policies')
factors_to_load = CONFIG.y('passbook.factors', []) factors_to_load = CONFIG.y('passbook.factors', [])
for factors_to_load in factors_to_load: for factors_to_load in factors_to_load:
try: try:

View file

@ -49,7 +49,7 @@ class AuthenticationView(UserPassesTestMixin, View):
self.pending_factors = request.session[AuthenticationView.SESSION_PENDING_FACTORS] self.pending_factors = request.session[AuthenticationView.SESSION_PENDING_FACTORS]
else: else:
# Get an initial list of factors which are currently enabled # Get an initial list of factors which are currently enabled
# and apply to the current user. We check rules here and block the request # and apply to the current user. We check policies here and block the request
_all_factors = Factor.objects.filter(enabled=True) _all_factors = Factor.objects.filter(enabled=True)
self.pending_factors = [] self.pending_factors = []
for factor in _all_factors: for factor in _all_factors:

View file

@ -13,7 +13,7 @@ class ApplicationForm(forms.ModelForm):
model = Application model = Application
fields = ['name', 'slug', 'launch_url', 'icon_url', fields = ['name', 'slug', 'launch_url', 'icon_url',
'rules', 'provider', 'skip_authorization'] 'policies', 'provider', 'skip_authorization']
widgets = { widgets = {
'name': forms.TextInput(), 'name': forms.TextInput(),
'launch_url': forms.TextInput(), 'launch_url': forms.TextInput(),

View file

@ -17,7 +17,7 @@ class FactorForm(forms.ModelForm):
class Meta: class Meta:
model = Factor model = Factor
fields = ['name', 'slug', 'order', 'rules', 'type', 'enabled'] fields = ['name', 'slug', 'order', 'policies', 'type', 'enabled']
widgets = { widgets = {
'type': forms.Select(choices=get_factors()), 'type': forms.Select(choices=get_factors()),
'name': forms.TextInput(), 'name': forms.TextInput(),

View file

@ -1,18 +1,18 @@
"""passbook rule forms""" """passbook Policy forms"""
from django import forms from django import forms
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from passbook.core.models import DebugRule, FieldMatcherRule, WebhookRule from passbook.core.models import DebugPolicy, FieldMatcherPolicy, WebhookPolicy
GENERAL_FIELDS = ['name', 'action', 'negate', 'order', ] GENERAL_FIELDS = ['name', 'action', 'negate', 'order', ]
class FieldMatcherRuleForm(forms.ModelForm): class FieldMatcherPolicyForm(forms.ModelForm):
"""FieldMatcherRule Form""" """FieldMatcherPolicy Form"""
class Meta: class Meta:
model = FieldMatcherRule model = FieldMatcherPolicy
fields = GENERAL_FIELDS + ['user_field', 'match_action', 'value', ] fields = GENERAL_FIELDS + ['user_field', 'match_action', 'value', ]
widgets = { widgets = {
'name': forms.TextInput(), 'name': forms.TextInput(),
@ -20,12 +20,12 @@ class FieldMatcherRuleForm(forms.ModelForm):
} }
class WebhookRuleForm(forms.ModelForm): class WebhookPolicyForm(forms.ModelForm):
"""WebhookRuleForm Form""" """WebhookPolicyForm Form"""
class Meta: class Meta:
model = WebhookRule model = WebhookPolicy
fields = GENERAL_FIELDS + ['url', 'method', 'json_body', 'json_headers', fields = GENERAL_FIELDS + ['url', 'method', 'json_body', 'json_headers',
'result_jsonpath', 'result_json_value', ] 'result_jsonpath', 'result_json_value', ]
widgets = { widgets = {
@ -37,12 +37,12 @@ class WebhookRuleForm(forms.ModelForm):
} }
class DebugRuleForm(forms.ModelForm): class DebugPolicyForm(forms.ModelForm):
"""DebugRuleForm Form""" """DebugPolicyForm Form"""
class Meta: class Meta:
model = DebugRule model = DebugPolicy
fields = GENERAL_FIELDS + ['result', 'wait_min', 'wait_max'] fields = GENERAL_FIELDS + ['result', 'wait_min', 'wait_max']
widgets = { widgets = {
'name': forms.TextInput(), 'name': forms.TextInput(),

View file

@ -1,4 +1,4 @@
# Generated by Django 2.1.5 on 2019-02-08 10:42 # Generated by Django 2.1.7 on 2019-02-16 09:10
import uuid import uuid
@ -68,13 +68,7 @@ class Migration(migrations.Migration):
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Provider', name='Policy',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
),
migrations.CreateModel(
name='Rule',
fields=[ fields=[
('created', models.DateField(auto_now_add=True)), ('created', models.DateField(auto_now_add=True)),
('last_updated', models.DateTimeField(auto_now=True)), ('last_updated', models.DateTimeField(auto_now=True)),
@ -89,7 +83,7 @@ class Migration(migrations.Migration):
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='RuleModel', name='PolicyModel',
fields=[ fields=[
('created', models.DateField(auto_now_add=True)), ('created', models.DateField(auto_now_add=True)),
('last_updated', models.DateTimeField(auto_now=True)), ('last_updated', models.DateTimeField(auto_now=True)),
@ -99,6 +93,12 @@ class Migration(migrations.Migration):
'abstract': False, 'abstract': False,
}, },
), ),
migrations.CreateModel(
name='Provider',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
),
migrations.CreateModel( migrations.CreateModel(
name='UserSourceConnection', name='UserSourceConnection',
fields=[ fields=[
@ -111,51 +111,82 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Application', name='Application',
fields=[ fields=[
('rulemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.RuleModel')), ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
('name', models.TextField()), ('name', models.TextField()),
('slug', models.SlugField()), ('slug', models.SlugField()),
('launch_url', models.URLField(blank=True, null=True)), ('launch_url', models.URLField(blank=True, null=True)),
('icon_url', models.TextField(blank=True, null=True)), ('icon_url', models.TextField(blank=True, null=True)),
('skip_authorization', models.BooleanField(default=False)), ('skip_authorization', models.BooleanField(default=False)),
('provider', models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='passbook_core.Provider')), ('provider', models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='passbook_core.Provider')),
], ],
options={ options={
'abstract': False, 'abstract': False,
}, },
bases=('passbook_core.rulemodel',), bases=('passbook_core.policymodel',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='DebugRule', name='DebugPolicy',
fields=[ fields=[
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Rule')), ('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')),
('result', models.BooleanField(default=False)), ('result', models.BooleanField(default=False)),
('wait_min', models.IntegerField(default=5)), ('wait_min', models.IntegerField(default=5)),
('wait_max', models.IntegerField(default=30)), ('wait_max', models.IntegerField(default=30)),
], ],
options={ options={
'verbose_name': 'Debug Rule', 'verbose_name': 'Debug Policy',
'verbose_name_plural': 'Debug Rules', 'verbose_name_plural': 'Debug Policys',
}, },
bases=('passbook_core.rule',), bases=('passbook_core.policy',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='FieldMatcherRule', name='Factor',
fields=[ fields=[
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Rule')), ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
('user_field', models.TextField(choices=[('username', 'username'), ('first_name', 'first_name'), ('last_name', 'last_name'), ('email', 'email'), ('is_staff', 'is_staff'), ('is_active', 'is_active'), ('data_joined', 'data_joined')])), ('name', models.TextField()),
('slug', models.SlugField(unique=True)),
('order', models.IntegerField()),
('type', models.TextField(unique=True)),
('enabled', models.BooleanField(default=True)),
],
options={
'abstract': False,
},
bases=('passbook_core.policymodel',),
),
migrations.CreateModel(
name='FieldMatcherPolicy',
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')),
('user_field', models.TextField(choices=[('username', 'Username'), ('first_name', 'First Name'), ('last_name', 'Last Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')])),
('match_action', models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('endswith', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50)), ('match_action', models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('endswith', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50)),
('value', models.TextField()), ('value', models.TextField()),
], ],
options={ options={
'verbose_name': 'Field matcher Rule', 'verbose_name': 'Field matcher Policy',
'verbose_name_plural': 'Field matcher Rules', 'verbose_name_plural': 'Field matcher Policys',
}, },
bases=('passbook_core.rule',), bases=('passbook_core.policy',),
),
migrations.CreateModel(
name='PasswordPolicyPolicy',
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')),
('amount_uppercase', models.IntegerField(default=0)),
('amount_lowercase', models.IntegerField(default=0)),
('amount_symbols', models.IntegerField(default=0)),
('length_min', models.IntegerField(default=0)),
('symbol_charset', models.TextField(default='!\\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ')),
],
options={
'verbose_name': 'Password Policy Policy',
'verbose_name_plural': 'Password Policy Policys',
},
bases=('passbook_core.policy',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Source', name='Source',
fields=[ fields=[
('rulemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.RuleModel')), ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
('name', models.TextField()), ('name', models.TextField()),
('slug', models.SlugField()), ('slug', models.SlugField()),
('enabled', models.BooleanField(default=True)), ('enabled', models.BooleanField(default=True)),
@ -163,12 +194,12 @@ class Migration(migrations.Migration):
options={ options={
'abstract': False, 'abstract': False,
}, },
bases=('passbook_core.rulemodel',), bases=('passbook_core.policymodel',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='WebhookRule', name='WebhookPolicy',
fields=[ fields=[
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Rule')), ('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')),
('url', models.URLField()), ('url', models.URLField()),
('method', models.CharField(choices=[('GET', 'GET'), ('POST', 'POST'), ('PATCH', 'PATCH'), ('DELETE', 'DELETE'), ('PUT', 'PUT')], max_length=10)), ('method', models.CharField(choices=[('GET', 'GET'), ('POST', 'POST'), ('PATCH', 'PATCH'), ('DELETE', 'DELETE'), ('PUT', 'PUT')], max_length=10)),
('json_body', models.TextField()), ('json_body', models.TextField()),
@ -177,15 +208,15 @@ class Migration(migrations.Migration):
('result_json_value', models.TextField()), ('result_json_value', models.TextField()),
], ],
options={ options={
'verbose_name': 'Webhook Rule', 'verbose_name': 'Webhook Policy',
'verbose_name_plural': 'Webhook Rules', 'verbose_name_plural': 'Webhook Policys',
}, },
bases=('passbook_core.rule',), bases=('passbook_core.policy',),
), ),
migrations.AddField( migrations.AddField(
model_name='rulemodel', model_name='policymodel',
name='rules', name='policies',
field=models.ManyToManyField(blank=True, to='passbook_core.Rule'), field=models.ManyToManyField(blank=True, to='passbook_core.Policy'),
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name='user',

View file

@ -1,35 +0,0 @@
# Generated by Django 2.1.5 on 2019-02-08 15:14
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='PasswordPolicyRule',
fields=[
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Rule')),
('amount_uppercase', models.IntegerField(default=0)),
('amount_lowercase', models.IntegerField(default=0)),
('amount_symbols', models.IntegerField(default=0)),
('length_min', models.IntegerField(default=0)),
('symbol_charset', models.TextField(default='!\\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ')),
],
options={
'verbose_name': 'Password Policy Rule',
'verbose_name_plural': 'Password Policy Rules',
},
bases=('passbook_core.rule',),
),
migrations.AlterField(
model_name='fieldmatcherrule',
name='user_field',
field=models.TextField(choices=[('username', 'Username'), ('first_name', 'First Name'), ('last_name', 'Last Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')]),
),
]

View file

@ -1,28 +0,0 @@
# Generated by Django 2.1.7 on 2019-02-14 15:41
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0002_auto_20190208_1514'),
]
operations = [
migrations.CreateModel(
name='Factor',
fields=[
('rulemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.RuleModel')),
('name', models.TextField()),
('slug', models.SlugField(unique=True)),
('order', models.IntegerField()),
('type', models.TextField()),
],
options={
'abstract': False,
},
bases=('passbook_core.rulemodel',),
),
]

View file

@ -1,24 +0,0 @@
# Generated by Django 2.1.7 on 2019-02-15 15:34
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0003_factor'),
]
operations = [
migrations.AddField(
model_name='factor',
name='enabled',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='application',
name='provider',
field=models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='passbook_core.Provider'),
),
]

View file

@ -1,18 +0,0 @@
# Generated by Django 2.1.7 on 2019-02-16 08:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0004_auto_20190215_1534'),
]
operations = [
migrations.AlterField(
model_name='factor',
name='type',
field=models.TextField(unique=True),
),
]

View file

@ -49,19 +49,19 @@ class Provider(models.Model):
return getattr(self, 'name') return getattr(self, 'name')
return super().__str__() return super().__str__()
class RuleModel(UUIDModel, CreatedUpdatedModel): class PolicyModel(UUIDModel, CreatedUpdatedModel):
"""Base model which can have rules applied to it""" """Base model which can have policies applied to it"""
rules = models.ManyToManyField('Rule', blank=True) policies = models.ManyToManyField('Policy', blank=True)
def passes(self, user: User) -> bool: def passes(self, user: User) -> bool:
"""Return true if user passes, otherwise False or raise Exception""" """Return true if user passes, otherwise False or raise Exception"""
for rule in self.rules: for policy in self.policies:
if not rule.passes(user): if not policy.passes(user):
return False return False
return True return True
class Factor(RuleModel): class Factor(PolicyModel):
"""Authentication factor, multiple instances of the same Factor can be used""" """Authentication factor, multiple instances of the same Factor can be used"""
name = models.TextField() name = models.TextField()
@ -73,7 +73,7 @@ class Factor(RuleModel):
def __str__(self): def __str__(self):
return "Factor %s" % self.slug return "Factor %s" % self.slug
class Application(RuleModel): class Application(PolicyModel):
"""Every Application which uses passbook for authentication/identification/authorization """Every Application which uses passbook for authentication/identification/authorization
needs an Application record. Other authentication types can subclass this Model to needs an Application record. Other authentication types can subclass this Model to
add custom fields and other properties""" add custom fields and other properties"""
@ -90,13 +90,13 @@ class Application(RuleModel):
def user_is_authorized(self, user: User) -> bool: def user_is_authorized(self, user: User) -> bool:
"""Check if user is authorized to use this application""" """Check if user is authorized to use this application"""
from passbook.core.rules import RuleEngine from passbook.core.policies import PolicyEngine
return RuleEngine(self.rules.all()).for_user(user).result return PolicyEngine(self.policies.all()).for_user(user).result
def __str__(self): def __str__(self):
return self.name return self.name
class Source(RuleModel): class Source(PolicyModel):
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server""" """Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""
name = models.TextField() name = models.TextField()
@ -129,8 +129,8 @@ class UserSourceConnection(CreatedUpdatedModel):
unique_together = (('user', 'source'),) unique_together = (('user', 'source'),)
class Rule(UUIDModel, CreatedUpdatedModel): class Policy(UUIDModel, CreatedUpdatedModel):
"""Rules which specify if a user is authorized to use an Application. Can be overridden by """Policys which specify if a user is authorized to use an Application. Can be overridden by
other types to add other fields, more logic, etc.""" other types to add other fields, more logic, etc."""
ACTION_ALLOW = 'allow' ACTION_ALLOW = 'allow'
@ -153,11 +153,11 @@ class Rule(UUIDModel, CreatedUpdatedModel):
return "%s action %s" % (self.name, self.action) return "%s action %s" % (self.name, self.action)
def passes(self, user: User) -> bool: def passes(self, user: User) -> bool:
"""Check if user instance passes this rule""" """Check if user instance passes this policy"""
raise NotImplementedError() raise NotImplementedError()
class FieldMatcherRule(Rule): class FieldMatcherPolicy(Policy):
"""Rule which checks if a field of the User model matches/doesn't match a """Policy which checks if a field of the User model matches/doesn't match a
certain pattern""" certain pattern"""
MATCH_STARTSWITH = 'startswith' MATCH_STARTSWITH = 'startswith'
@ -188,7 +188,7 @@ class FieldMatcherRule(Rule):
match_action = models.CharField(max_length=50, choices=MATCHES) match_action = models.CharField(max_length=50, choices=MATCHES)
value = models.TextField() value = models.TextField()
form = 'passbook.core.forms.rules.FieldMatcherRuleForm' form = 'passbook.core.forms.policies.FieldMatcherPolicyForm'
def __str__(self): def __str__(self):
description = "%s, user.%s %s '%s'" % (self.name, self.user_field, description = "%s, user.%s %s '%s'" % (self.name, self.user_field,
@ -205,13 +205,13 @@ class FieldMatcherRule(Rule):
LOGGER.debug("Checked '%s' %s with '%s'...", LOGGER.debug("Checked '%s' %s with '%s'...",
user_field_value, self.match_action, self.value) user_field_value, self.match_action, self.value)
passes = False passes = False
if self.match_action == FieldMatcherRule.MATCH_STARTSWITH: if self.match_action == FieldMatcherPolicy.MATCH_STARTSWITH:
passes = user_field_value.startswith(self.value) passes = user_field_value.startswith(self.value)
if self.match_action == FieldMatcherRule.MATCH_ENDSWITH: if self.match_action == FieldMatcherPolicy.MATCH_ENDSWITH:
passes = user_field_value.endswith(self.value) passes = user_field_value.endswith(self.value)
if self.match_action == FieldMatcherRule.MATCH_CONTAINS: if self.match_action == FieldMatcherPolicy.MATCH_CONTAINS:
passes = self.value in user_field_value passes = self.value in user_field_value
if self.match_action == FieldMatcherRule.MATCH_REGEXP: if self.match_action == FieldMatcherPolicy.MATCH_REGEXP:
pattern = re.compile(self.value) pattern = re.compile(self.value)
passes = bool(pattern.match(user_field_value)) passes = bool(pattern.match(user_field_value))
if self.negate: if self.negate:
@ -221,11 +221,11 @@ class FieldMatcherRule(Rule):
class Meta: class Meta:
verbose_name = _('Field matcher Rule') verbose_name = _('Field matcher Policy')
verbose_name_plural = _('Field matcher Rules') verbose_name_plural = _('Field matcher Policys')
class PasswordPolicyRule(Rule): class PasswordPolicyPolicy(Policy):
"""Rule to make sure passwords have certain properties""" """Policy to make sure passwords have certain properties"""
amount_uppercase = models.IntegerField(default=0) amount_uppercase = models.IntegerField(default=0)
amount_lowercase = models.IntegerField(default=0) amount_lowercase = models.IntegerField(default=0)
@ -233,7 +233,7 @@ class PasswordPolicyRule(Rule):
length_min = models.IntegerField(default=0) length_min = models.IntegerField(default=0)
symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ") symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ")
form = 'passbook.core.forms.rules.PasswordPolicyRuleForm' form = 'passbook.core.forms.policies.PasswordPolicyPolicyForm'
def passes(self, user: User) -> bool: def passes(self, user: User) -> bool:
# Only check if password is being set # Only check if password is being set
@ -254,12 +254,12 @@ class PasswordPolicyRule(Rule):
class Meta: class Meta:
verbose_name = _('Password Policy Rule') verbose_name = _('Password Policy Policy')
verbose_name_plural = _('Password Policy Rules') verbose_name_plural = _('Password Policy Policys')
class WebhookRule(Rule): class WebhookPolicy(Policy):
"""Rule that asks webhook""" """Policy that asks webhook"""
METHOD_GET = 'GET' METHOD_GET = 'GET'
METHOD_POST = 'POST' METHOD_POST = 'POST'
@ -282,7 +282,7 @@ class WebhookRule(Rule):
result_jsonpath = models.TextField() result_jsonpath = models.TextField()
result_json_value = models.TextField() result_json_value = models.TextField()
form = 'passbook.core.forms.rules.WebhookRuleForm' form = 'passbook.core.forms.policies.WebhookPolicyForm'
def passes(self, user: User): def passes(self, user: User):
"""Call webhook asynchronously and report back""" """Call webhook asynchronously and report back"""
@ -290,30 +290,30 @@ class WebhookRule(Rule):
class Meta: class Meta:
verbose_name = _('Webhook Rule') verbose_name = _('Webhook Policy')
verbose_name_plural = _('Webhook Rules') verbose_name_plural = _('Webhook Policys')
class DebugRule(Rule): class DebugPolicy(Policy):
"""Rule used for debugging the RuleEngine. Returns a fixed result, """Policy used for debugging the PolicyEngine. Returns a fixed result,
but takes a random time to process.""" but takes a random time to process."""
result = models.BooleanField(default=False) result = models.BooleanField(default=False)
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.core.forms.rules.DebugRuleForm' form = 'passbook.core.forms.policies.DebugPolicyForm'
def passes(self, user: User): def passes(self, user: User):
"""Wait random time then return result""" """Wait random time then return result"""
wait = SystemRandom().randrange(self.wait_min, self.wait_max) wait = SystemRandom().randrange(self.wait_min, self.wait_max)
LOGGER.debug("Rule '%s' waiting for %ds", self.name, wait) LOGGER.debug("Policy '%s' waiting for %ds", self.name, wait)
sleep(wait) sleep(wait)
return self.result return self.result
class Meta: class Meta:
verbose_name = _('Debug Rule') verbose_name = _('Debug Policy')
verbose_name_plural = _('Debug Rules') verbose_name_plural = _('Debug Policys')
class Invitation(UUIDModel): class Invitation(UUIDModel):
"""Single-use invitation link""" """Single-use invitation link"""

48
passbook/core/policies.py Normal file
View file

@ -0,0 +1,48 @@
"""passbook core policy engine"""
from logging import getLogger
from celery import group
from passbook.core.celery import CELERY_APP
from passbook.core.models import Policy, User
LOGGER = getLogger(__name__)
@CELERY_APP.task()
def _policy_engine_task(user_pk, policy_pk, **kwargs):
"""Task wrapper to run policy checking"""
policy_obj = Policy.objects.filter(pk=policy_pk).select_subclasses().first()
user_obj = User.objects.get(pk=user_pk)
for key, value in kwargs.items():
setattr(user_obj, key, value)
LOGGER.debug("Running policy `%s`#%s for user %s...", policy_obj.name,
policy_obj.pk.hex, user_obj)
return policy_obj.passes(user_obj)
class PolicyEngine:
"""Orchestrate policy checking, launch tasks and return result"""
policies = None
_group = None
def __init__(self, policies):
self.policies = policies
def for_user(self, user):
"""Check policies for user"""
signatures = []
kwargs = {
'__password__': getattr(user, '__password__')
}
for policy in self.policies:
signatures.append(_policy_engine_task.s(user.pk, policy.pk.hex, **kwargs))
self._group = group(signatures)()
return self
@property
def result(self):
"""Get policy-checking result"""
for policy_result in self._group.get():
if policy_result is False:
return False
return True

View file

@ -1,47 +0,0 @@
"""passbook core rule engine"""
from logging import getLogger
from celery import group
from passbook.core.celery import CELERY_APP
from passbook.core.models import Rule, User
LOGGER = getLogger(__name__)
@CELERY_APP.task()
def _rule_engine_task(user_pk, rule_pk, **kwargs):
"""Task wrapper to run rule checking"""
rule_obj = Rule.objects.filter(pk=rule_pk).select_subclasses().first()
user_obj = User.objects.get(pk=user_pk)
for key, value in kwargs.items():
setattr(user_obj, key, value)
LOGGER.debug("Running rule `%s`#%s for user %s...", rule_obj.name, rule_obj.pk.hex, user_obj)
return rule_obj.passes(user_obj)
class RuleEngine:
"""Orchestrate rule checking, launch tasks and return result"""
rules = None
_group = None
def __init__(self, rules):
self.rules = rules
def for_user(self, user):
"""Check rules for user"""
signatures = []
kwargs = {
'__password__': getattr(user, '__password__')
}
for rule in self.rules:
signatures.append(_rule_engine_task.s(user.pk, rule.pk.hex, **kwargs))
self._group = group(signatures)()
return self
@property
def result(self):
"""Get rule-checking result"""
for rule_result in self._group.get():
if rule_result is False:
return False
return True

View file

@ -14,7 +14,7 @@ class LDAPSourceForm(forms.ModelForm):
model = LDAPSource model = LDAPSource
fields = SOURCE_FORM_FIELDS + ['server_uri', 'bind_cn', 'bind_password', fields = SOURCE_FORM_FIELDS + ['server_uri', 'bind_cn', 'bind_password',
'type', 'domain', 'base_dn', 'create_user', 'type', 'domain', 'base_dn', 'create_user',
'reset_password', 'rules'] 'reset_password', 'policies']
widgets = { widgets = {
'name': forms.TextInput(), 'name': forms.TextInput(),
'server_uri': forms.TextInput(), 'server_uri': forms.TextInput(),

View file

@ -1,4 +1,4 @@
# Generated by Django 2.1.4 on 2018-12-10 09:16 # Generated by Django 2.1.7 on 2019-02-16 09:13
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models

View file

@ -1,4 +1,4 @@
# Generated by Django 2.1.4 on 2018-12-10 09:16 # Generated by Django 2.1.7 on 2019-02-16 09:13
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models
@ -26,8 +26,8 @@ class Migration(migrations.Migration):
('consumer_secret', models.TextField()), ('consumer_secret', models.TextField()),
], ],
options={ options={
'verbose_name': 'OAuth Source', 'verbose_name': 'Generic OAuth Source',
'verbose_name_plural': 'OAuth Sources', 'verbose_name_plural': 'Generic OAuth Sources',
}, },
bases=('passbook_core.source',), bases=('passbook_core.source',),
), ),

View file

@ -1,17 +0,0 @@
# Generated by Django 2.1.4 on 2018-12-18 10:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('passbook_oauth_client', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='oauthsource',
options={'verbose_name': 'Generic OAuth Source', 'verbose_name_plural': 'Generic OAuth Sources'},
),
]

View file

@ -1,4 +1,4 @@
# Generated by Django 2.1.3 on 2018-11-25 10:39 # Generated by Django 2.1.7 on 2019-02-16 09:13
import django.db.models.deletion import django.db.models.deletion
import oauth2_provider.generators import oauth2_provider.generators
@ -15,8 +15,8 @@ class Migration(migrations.Migration):
] ]
dependencies = [ dependencies = [
('passbook_core', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('passbook_core', '0001_initial'),
] ]
operations = [ operations = [
@ -36,7 +36,8 @@ class Migration(migrations.Migration):
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='passbook_oauth_provider_oauth2provider', to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='passbook_oauth_provider_oauth2provider', to=settings.AUTH_USER_MODEL)),
], ],
options={ options={
'abstract': False, 'verbose_name': 'OAuth2 Provider',
'verbose_name_plural': 'OAuth2 Providers',
}, },
bases=('passbook_core.provider', models.Model), bases=('passbook_core.provider', models.Model),
), ),

View file

@ -1,17 +0,0 @@
# Generated by Django 2.1.3 on 2018-11-26 15:14
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('passbook_oauth_provider', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='oauth2provider',
options={'verbose_name': 'OAuth2 Provider', 'verbose_name_plural': 'OAuth2 Providers'},
),
]

View file

@ -29,7 +29,7 @@ class OAuthPermissionDenied(PermissionDeniedView):
class PassbookAuthorizationView(AccessMixin, AuthorizationView): class PassbookAuthorizationView(AccessMixin, AuthorizationView):
"""Custom OAuth2 Authorization View which checks rules, etc""" """Custom OAuth2 Authorization View which checks policies, etc"""
_application = None _application = None

View file

@ -1,4 +1,4 @@
# Generated by Django 2.1.4 on 2018-12-10 09:16 # Generated by Django 2.1.7 on 2019-02-16 09:13
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models