Rule -> Policies
This commit is contained in:
		| @ -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> | ||||
|  | ||||
| @ -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,14 +31,14 @@ | ||||
|       </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 %} | ||||
							
								
								
									
										7
									
								
								passbook/admin/templates/administration/policy/test.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								passbook/admin/templates/administration/policy/test.html
									
									
									
									
									
										Normal 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 %} | ||||
| @ -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) | ||||
|  | ||||
							
								
								
									
										104
									
								
								passbook/admin/views/policy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								passbook/admin/views/policy.py
									
									
									
									
									
										Normal 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)) | ||||
| @ -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""" | ||||
|  | ||||
							
								
								
									
										48
									
								
								passbook/core/policies.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								passbook/core/policies.py
									
									
									
									
									
										Normal 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 | ||||
| @ -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
	
	Block a user
	 Jens Langhammer
					Jens Langhammer