Rule -> Policies
This commit is contained in:
parent
d32699b332
commit
d6f4832e90
|
@ -4,7 +4,7 @@ from django import forms
|
|||
from passbook.core.models import User
|
||||
|
||||
|
||||
class RuleTestForm(forms.Form):
|
||||
"""Form to test rule against user"""
|
||||
class PolicyTestForm(forms.Form):
|
||||
"""Form to test policies against user"""
|
||||
|
||||
user = forms.ModelChoiceField(queryset=User.objects.all())
|
|
@ -20,8 +20,8 @@
|
|||
<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>
|
||||
</li>
|
||||
<li class="{% is_active 'passbook_admin:rules' 'passbook_admin:rule-create' 'passbook_admin:rule-update' 'passbook_admin:rule-delete' 'passbook_admin:rule-test' %}">
|
||||
<a href="{% url 'passbook_admin:rules' %}">{% trans 'Rules' %}</a>
|
||||
<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:policys' %}">{% trans 'Policies' %}</a>
|
||||
</li>
|
||||
<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>
|
||||
|
|
|
@ -31,11 +31,11 @@
|
|||
<div class="col-xs-6 col-sm-2 col-md-2">
|
||||
<div class="card-pf card-pf-accented card-pf-aggregate-status">
|
||||
<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>
|
||||
<div class="card-pf-body">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -53,4 +53,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h1>{% trans "Rules" %}</h1>
|
||||
<h1>{% trans "Policies" %}</h1>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-primary dropdown-toggle" type="button" id="createDropdown" data-toggle="dropdown">
|
||||
{% trans 'Create...' %}
|
||||
|
@ -17,7 +17,7 @@
|
|||
</button>
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown">
|
||||
{% 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 %}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -31,18 +31,18 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for rule in object_list %}
|
||||
{% for policy in object_list %}
|
||||
<tr>
|
||||
<td>{{ rule.name }}</td>
|
||||
<td>{{ rule|fieldtype }}</td>
|
||||
<td>{{ policy.name }}</td>
|
||||
<td>{{ policy|fieldtype }}</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:rule-test' pk=rule.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-update' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</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:policy-delete' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,7 @@
|
|||
{% extends 'generic/form.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block above_form %}
|
||||
<h1>{% blocktrans with policy=policy %}Test policy {{ policy }}{% endblocktrans %}</h1>
|
||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||
{% extends 'generic/form.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block above_form %}
|
||||
<h1>{% blocktrans with rule=rule %}Test rule {{ rule }}{% endblocktrans %}</h1>
|
||||
{% endblock %}
|
|
@ -3,7 +3,7 @@ from django.urls import include, path
|
|||
from rest_framework_swagger.views import get_swagger_view
|
||||
|
||||
from passbook.admin.views import (applications, audit, factors, groups,
|
||||
invitations, overview, providers, rules,
|
||||
invitations, overview, policy, providers,
|
||||
sources, users)
|
||||
|
||||
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/<uuid:pk>/update/', sources.SourceUpdateView.as_view(), name='source-update'),
|
||||
path('sources/<uuid:pk>/delete/', sources.SourceDeleteView.as_view(), name='source-delete'),
|
||||
# Rules
|
||||
path('rules/', rules.RuleListView.as_view(), name='rules'),
|
||||
path('rules/create/', rules.RuleCreateView.as_view(), name='rule-create'),
|
||||
path('rules/<uuid:pk>/update/', rules.RuleUpdateView.as_view(), name='rule-update'),
|
||||
path('rules/<uuid:pk>/delete/', rules.RuleDeleteView.as_view(), name='rule-delete'),
|
||||
path('rules/<uuid:pk>/test/', rules.RuleTestView.as_view(), name='rule-test'),
|
||||
# Policies
|
||||
path('policies/', policy.PolicyListView.as_view(), name='policies'),
|
||||
path('policies/create/', policy.PolicyCreateView.as_view(), name='policy-create'),
|
||||
path('policies/<uuid:pk>/update/', policy.PolicyUpdateView.as_view(), name='policy-update'),
|
||||
path('policies/<uuid:pk>/delete/', policy.PolicyDeleteView.as_view(), name='policy-delete'),
|
||||
path('policies/<uuid:pk>/test/', policy.PolicyTestView.as_view(), name='policy-test'),
|
||||
# Providers
|
||||
path('providers/', providers.ProviderListView.as_view(), name='providers'),
|
||||
path('providers/create/',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from django.views.generic import TemplateView
|
||||
|
||||
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):
|
||||
|
@ -12,7 +12,7 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
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['provider_count'] = len(Provider.objects.all())
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
|
@ -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))
|
|
@ -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))
|
|
@ -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
|
||||
|
||||
|
@ -20,13 +20,32 @@ class Migration(migrations.Migration):
|
|||
name='AuditEntry',
|
||||
fields=[
|
||||
('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)),
|
||||
('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)),
|
||||
],
|
||||
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')},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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')]),
|
||||
),
|
||||
]
|
|
@ -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')]),
|
||||
),
|
||||
]
|
|
@ -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')]),
|
||||
),
|
||||
]
|
|
@ -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,
|
||||
),
|
||||
]
|
|
@ -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')},
|
||||
),
|
||||
]
|
|
@ -16,7 +16,7 @@ class PassbookCoreConfig(AppConfig):
|
|||
verbose_name = 'passbook Core'
|
||||
|
||||
def ready(self):
|
||||
import_module('passbook.core.rules')
|
||||
import_module('passbook.core.policies')
|
||||
factors_to_load = CONFIG.y('passbook.factors', [])
|
||||
for factors_to_load in factors_to_load:
|
||||
try:
|
||||
|
|
|
@ -49,7 +49,7 @@ class AuthenticationView(UserPassesTestMixin, View):
|
|||
self.pending_factors = request.session[AuthenticationView.SESSION_PENDING_FACTORS]
|
||||
else:
|
||||
# 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)
|
||||
self.pending_factors = []
|
||||
for factor in _all_factors:
|
||||
|
|
|
@ -13,7 +13,7 @@ class ApplicationForm(forms.ModelForm):
|
|||
|
||||
model = Application
|
||||
fields = ['name', 'slug', 'launch_url', 'icon_url',
|
||||
'rules', 'provider', 'skip_authorization']
|
||||
'policies', 'provider', 'skip_authorization']
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
'launch_url': forms.TextInput(),
|
||||
|
|
|
@ -17,7 +17,7 @@ class FactorForm(forms.ModelForm):
|
|||
class Meta:
|
||||
|
||||
model = Factor
|
||||
fields = ['name', 'slug', 'order', 'rules', 'type', 'enabled']
|
||||
fields = ['name', 'slug', 'order', 'policies', 'type', 'enabled']
|
||||
widgets = {
|
||||
'type': forms.Select(choices=get_factors()),
|
||||
'name': forms.TextInput(),
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
"""passbook rule forms"""
|
||||
"""passbook Policy forms"""
|
||||
|
||||
from django import forms
|
||||
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', ]
|
||||
|
||||
class FieldMatcherRuleForm(forms.ModelForm):
|
||||
"""FieldMatcherRule Form"""
|
||||
class FieldMatcherPolicyForm(forms.ModelForm):
|
||||
"""FieldMatcherPolicy Form"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = FieldMatcherRule
|
||||
model = FieldMatcherPolicy
|
||||
fields = GENERAL_FIELDS + ['user_field', 'match_action', 'value', ]
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
|
@ -20,12 +20,12 @@ class FieldMatcherRuleForm(forms.ModelForm):
|
|||
}
|
||||
|
||||
|
||||
class WebhookRuleForm(forms.ModelForm):
|
||||
"""WebhookRuleForm Form"""
|
||||
class WebhookPolicyForm(forms.ModelForm):
|
||||
"""WebhookPolicyForm Form"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = WebhookRule
|
||||
model = WebhookPolicy
|
||||
fields = GENERAL_FIELDS + ['url', 'method', 'json_body', 'json_headers',
|
||||
'result_jsonpath', 'result_json_value', ]
|
||||
widgets = {
|
||||
|
@ -37,12 +37,12 @@ class WebhookRuleForm(forms.ModelForm):
|
|||
}
|
||||
|
||||
|
||||
class DebugRuleForm(forms.ModelForm):
|
||||
"""DebugRuleForm Form"""
|
||||
class DebugPolicyForm(forms.ModelForm):
|
||||
"""DebugPolicyForm Form"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = DebugRule
|
||||
model = DebugPolicy
|
||||
fields = GENERAL_FIELDS + ['result', 'wait_min', 'wait_max']
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
|
@ -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
|
||||
|
||||
|
@ -68,13 +68,7 @@ class Migration(migrations.Migration):
|
|||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Provider',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Rule',
|
||||
name='Policy',
|
||||
fields=[
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
|
@ -89,7 +83,7 @@ class Migration(migrations.Migration):
|
|||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RuleModel',
|
||||
name='PolicyModel',
|
||||
fields=[
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
|
@ -99,6 +93,12 @@ class Migration(migrations.Migration):
|
|||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Provider',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserSourceConnection',
|
||||
fields=[
|
||||
|
@ -111,51 +111,82 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Application',
|
||||
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()),
|
||||
('slug', models.SlugField()),
|
||||
('launch_url', models.URLField(blank=True, null=True)),
|
||||
('icon_url', models.TextField(blank=True, null=True)),
|
||||
('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={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('passbook_core.rulemodel',),
|
||||
bases=('passbook_core.policymodel',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DebugRule',
|
||||
name='DebugPolicy',
|
||||
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)),
|
||||
('wait_min', models.IntegerField(default=5)),
|
||||
('wait_max', models.IntegerField(default=30)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Debug Rule',
|
||||
'verbose_name_plural': 'Debug Rules',
|
||||
'verbose_name': 'Debug Policy',
|
||||
'verbose_name_plural': 'Debug Policys',
|
||||
},
|
||||
bases=('passbook_core.rule',),
|
||||
bases=('passbook_core.policy',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FieldMatcherRule',
|
||||
name='Factor',
|
||||
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')),
|
||||
('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')])),
|
||||
('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()),
|
||||
('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)),
|
||||
('value', models.TextField()),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Field matcher Rule',
|
||||
'verbose_name_plural': 'Field matcher Rules',
|
||||
'verbose_name': 'Field matcher Policy',
|
||||
'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(
|
||||
name='Source',
|
||||
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()),
|
||||
('slug', models.SlugField()),
|
||||
('enabled', models.BooleanField(default=True)),
|
||||
|
@ -163,12 +194,12 @@ class Migration(migrations.Migration):
|
|||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('passbook_core.rulemodel',),
|
||||
bases=('passbook_core.policymodel',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WebhookRule',
|
||||
name='WebhookPolicy',
|
||||
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()),
|
||||
('method', models.CharField(choices=[('GET', 'GET'), ('POST', 'POST'), ('PATCH', 'PATCH'), ('DELETE', 'DELETE'), ('PUT', 'PUT')], max_length=10)),
|
||||
('json_body', models.TextField()),
|
||||
|
@ -177,15 +208,15 @@ class Migration(migrations.Migration):
|
|||
('result_json_value', models.TextField()),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Webhook Rule',
|
||||
'verbose_name_plural': 'Webhook Rules',
|
||||
'verbose_name': 'Webhook Policy',
|
||||
'verbose_name_plural': 'Webhook Policys',
|
||||
},
|
||||
bases=('passbook_core.rule',),
|
||||
bases=('passbook_core.policy',),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rulemodel',
|
||||
name='rules',
|
||||
field=models.ManyToManyField(blank=True, to='passbook_core.Rule'),
|
||||
model_name='policymodel',
|
||||
name='policies',
|
||||
field=models.ManyToManyField(blank=True, to='passbook_core.Policy'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
|
|
|
@ -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')]),
|
||||
),
|
||||
]
|
|
@ -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',),
|
||||
),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -49,19 +49,19 @@ class Provider(models.Model):
|
|||
return getattr(self, 'name')
|
||||
return super().__str__()
|
||||
|
||||
class RuleModel(UUIDModel, CreatedUpdatedModel):
|
||||
"""Base model which can have rules applied to it"""
|
||||
class PolicyModel(UUIDModel, CreatedUpdatedModel):
|
||||
"""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:
|
||||
"""Return true if user passes, otherwise False or raise Exception"""
|
||||
for rule in self.rules:
|
||||
if not rule.passes(user):
|
||||
for policy in self.policies:
|
||||
if not policy.passes(user):
|
||||
return False
|
||||
return True
|
||||
|
||||
class Factor(RuleModel):
|
||||
class Factor(PolicyModel):
|
||||
"""Authentication factor, multiple instances of the same Factor can be used"""
|
||||
|
||||
name = models.TextField()
|
||||
|
@ -73,7 +73,7 @@ class Factor(RuleModel):
|
|||
def __str__(self):
|
||||
return "Factor %s" % self.slug
|
||||
|
||||
class Application(RuleModel):
|
||||
class Application(PolicyModel):
|
||||
"""Every Application which uses passbook for authentication/identification/authorization
|
||||
needs an Application record. Other authentication types can subclass this Model to
|
||||
add custom fields and other properties"""
|
||||
|
@ -90,13 +90,13 @@ class Application(RuleModel):
|
|||
|
||||
def user_is_authorized(self, user: User) -> bool:
|
||||
"""Check if user is authorized to use this application"""
|
||||
from passbook.core.rules import RuleEngine
|
||||
return RuleEngine(self.rules.all()).for_user(user).result
|
||||
from passbook.core.policies import PolicyEngine
|
||||
return PolicyEngine(self.policies.all()).for_user(user).result
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Source(RuleModel):
|
||||
class Source(PolicyModel):
|
||||
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""
|
||||
|
||||
name = models.TextField()
|
||||
|
@ -129,8 +129,8 @@ class UserSourceConnection(CreatedUpdatedModel):
|
|||
|
||||
unique_together = (('user', 'source'),)
|
||||
|
||||
class Rule(UUIDModel, CreatedUpdatedModel):
|
||||
"""Rules which specify if a user is authorized to use an Application. Can be overridden by
|
||||
class Policy(UUIDModel, CreatedUpdatedModel):
|
||||
"""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."""
|
||||
|
||||
ACTION_ALLOW = 'allow'
|
||||
|
@ -153,11 +153,11 @@ class Rule(UUIDModel, CreatedUpdatedModel):
|
|||
return "%s action %s" % (self.name, self.action)
|
||||
|
||||
def passes(self, user: User) -> bool:
|
||||
"""Check if user instance passes this rule"""
|
||||
"""Check if user instance passes this policy"""
|
||||
raise NotImplementedError()
|
||||
|
||||
class FieldMatcherRule(Rule):
|
||||
"""Rule which checks if a field of the User model matches/doesn't match a
|
||||
class FieldMatcherPolicy(Policy):
|
||||
"""Policy which checks if a field of the User model matches/doesn't match a
|
||||
certain pattern"""
|
||||
|
||||
MATCH_STARTSWITH = 'startswith'
|
||||
|
@ -188,7 +188,7 @@ class FieldMatcherRule(Rule):
|
|||
match_action = models.CharField(max_length=50, choices=MATCHES)
|
||||
value = models.TextField()
|
||||
|
||||
form = 'passbook.core.forms.rules.FieldMatcherRuleForm'
|
||||
form = 'passbook.core.forms.policies.FieldMatcherPolicyForm'
|
||||
|
||||
def __str__(self):
|
||||
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'...",
|
||||
user_field_value, self.match_action, self.value)
|
||||
passes = False
|
||||
if self.match_action == FieldMatcherRule.MATCH_STARTSWITH:
|
||||
if self.match_action == FieldMatcherPolicy.MATCH_STARTSWITH:
|
||||
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)
|
||||
if self.match_action == FieldMatcherRule.MATCH_CONTAINS:
|
||||
if self.match_action == FieldMatcherPolicy.MATCH_CONTAINS:
|
||||
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)
|
||||
passes = bool(pattern.match(user_field_value))
|
||||
if self.negate:
|
||||
|
@ -221,11 +221,11 @@ class FieldMatcherRule(Rule):
|
|||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Field matcher Rule')
|
||||
verbose_name_plural = _('Field matcher Rules')
|
||||
verbose_name = _('Field matcher Policy')
|
||||
verbose_name_plural = _('Field matcher Policys')
|
||||
|
||||
class PasswordPolicyRule(Rule):
|
||||
"""Rule to make sure passwords have certain properties"""
|
||||
class PasswordPolicyPolicy(Policy):
|
||||
"""Policy to make sure passwords have certain properties"""
|
||||
|
||||
amount_uppercase = models.IntegerField(default=0)
|
||||
amount_lowercase = models.IntegerField(default=0)
|
||||
|
@ -233,7 +233,7 @@ class PasswordPolicyRule(Rule):
|
|||
length_min = models.IntegerField(default=0)
|
||||
symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ")
|
||||
|
||||
form = 'passbook.core.forms.rules.PasswordPolicyRuleForm'
|
||||
form = 'passbook.core.forms.policies.PasswordPolicyPolicyForm'
|
||||
|
||||
def passes(self, user: User) -> bool:
|
||||
# Only check if password is being set
|
||||
|
@ -254,12 +254,12 @@ class PasswordPolicyRule(Rule):
|
|||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Password Policy Rule')
|
||||
verbose_name_plural = _('Password Policy Rules')
|
||||
verbose_name = _('Password Policy Policy')
|
||||
verbose_name_plural = _('Password Policy Policys')
|
||||
|
||||
|
||||
class WebhookRule(Rule):
|
||||
"""Rule that asks webhook"""
|
||||
class WebhookPolicy(Policy):
|
||||
"""Policy that asks webhook"""
|
||||
|
||||
METHOD_GET = 'GET'
|
||||
METHOD_POST = 'POST'
|
||||
|
@ -282,7 +282,7 @@ class WebhookRule(Rule):
|
|||
result_jsonpath = models.TextField()
|
||||
result_json_value = models.TextField()
|
||||
|
||||
form = 'passbook.core.forms.rules.WebhookRuleForm'
|
||||
form = 'passbook.core.forms.policies.WebhookPolicyForm'
|
||||
|
||||
def passes(self, user: User):
|
||||
"""Call webhook asynchronously and report back"""
|
||||
|
@ -290,30 +290,30 @@ class WebhookRule(Rule):
|
|||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Webhook Rule')
|
||||
verbose_name_plural = _('Webhook Rules')
|
||||
verbose_name = _('Webhook Policy')
|
||||
verbose_name_plural = _('Webhook Policys')
|
||||
|
||||
class DebugRule(Rule):
|
||||
"""Rule used for debugging the RuleEngine. Returns a fixed result,
|
||||
class DebugPolicy(Policy):
|
||||
"""Policy used for debugging the PolicyEngine. Returns a fixed result,
|
||||
but takes a random time to process."""
|
||||
|
||||
result = models.BooleanField(default=False)
|
||||
wait_min = models.IntegerField(default=5)
|
||||
wait_max = models.IntegerField(default=30)
|
||||
|
||||
form = 'passbook.core.forms.rules.DebugRuleForm'
|
||||
form = 'passbook.core.forms.policies.DebugPolicyForm'
|
||||
|
||||
def passes(self, user: User):
|
||||
"""Wait random time then return result"""
|
||||
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)
|
||||
return self.result
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Debug Rule')
|
||||
verbose_name_plural = _('Debug Rules')
|
||||
verbose_name = _('Debug Policy')
|
||||
verbose_name_plural = _('Debug Policys')
|
||||
|
||||
class Invitation(UUIDModel):
|
||||
"""Single-use invitation link"""
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -14,7 +14,7 @@ class LDAPSourceForm(forms.ModelForm):
|
|||
model = LDAPSource
|
||||
fields = SOURCE_FORM_FIELDS + ['server_uri', 'bind_cn', 'bind_password',
|
||||
'type', 'domain', 'base_dn', 'create_user',
|
||||
'reset_password', 'rules']
|
||||
'reset_password', 'policies']
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
'server_uri': forms.TextInput(),
|
||||
|
|
|
@ -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
|
||||
from django.db import migrations, models
|
||||
|
|
|
@ -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
|
||||
from django.db import migrations, models
|
||||
|
@ -26,8 +26,8 @@ class Migration(migrations.Migration):
|
|||
('consumer_secret', models.TextField()),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'OAuth Source',
|
||||
'verbose_name_plural': 'OAuth Sources',
|
||||
'verbose_name': 'Generic OAuth Source',
|
||||
'verbose_name_plural': 'Generic OAuth Sources',
|
||||
},
|
||||
bases=('passbook_core.source',),
|
||||
),
|
||||
|
|
|
@ -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'},
|
||||
),
|
||||
]
|
|
@ -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 oauth2_provider.generators
|
||||
|
@ -15,8 +15,8 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('passbook_core', '0001_initial'),
|
||||
]
|
||||
|
||||
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)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'verbose_name': 'OAuth2 Provider',
|
||||
'verbose_name_plural': 'OAuth2 Providers',
|
||||
},
|
||||
bases=('passbook_core.provider', models.Model),
|
||||
),
|
||||
|
|
|
@ -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'},
|
||||
),
|
||||
]
|
|
@ -29,7 +29,7 @@ class OAuthPermissionDenied(PermissionDeniedView):
|
|||
|
||||
|
||||
class PassbookAuthorizationView(AccessMixin, AuthorizationView):
|
||||
"""Custom OAuth2 Authorization View which checks rules, etc"""
|
||||
"""Custom OAuth2 Authorization View which checks policies, etc"""
|
||||
|
||||
_application = None
|
||||
|
||||
|
|
|
@ -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
|
||||
from django.db import migrations, models
|
||||
|
|
Reference in New Issue