Rule -> Policies
This commit is contained in:
		| @ -4,7 +4,7 @@ from django import forms | |||||||
| from passbook.core.models import User | from passbook.core.models import User | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RuleTestForm(forms.Form): | class PolicyTestForm(forms.Form): | ||||||
|     """Form to test rule against user""" |     """Form to test policies against user""" | ||||||
| 
 | 
 | ||||||
|     user = forms.ModelChoiceField(queryset=User.objects.all()) |     user = forms.ModelChoiceField(queryset=User.objects.all()) | ||||||
| @ -20,8 +20,8 @@ | |||||||
|   <li class="{% is_active 'passbook_admin:factors' 'passbook_admin:factor-create' 'passbook_admin:factor-update' 'passbook_admin:factor-delete' %}"> |   <li class="{% is_active 'passbook_admin:factors' 'passbook_admin:factor-create' 'passbook_admin:factor-update' 'passbook_admin:factor-delete' %}"> | ||||||
|     <a href="{% url 'passbook_admin:factors' %}">{% trans 'Factors' %}</a> |     <a href="{% url 'passbook_admin:factors' %}">{% trans 'Factors' %}</a> | ||||||
|   </li> |   </li> | ||||||
|   <li class="{% is_active 'passbook_admin:rules' 'passbook_admin:rule-create' 'passbook_admin:rule-update' 'passbook_admin:rule-delete' 'passbook_admin:rule-test' %}"> |   <li class="{% is_active 'passbook_admin:policys' 'passbook_admin:policy-create' 'passbook_admin:policy-update' 'passbook_admin:policy-delete' 'passbook_admin:policy-test' %}"> | ||||||
|     <a href="{% url 'passbook_admin:rules' %}">{% trans 'Rules' %}</a> |     <a href="{% url 'passbook_admin:policys' %}">{% trans 'Policies' %}</a> | ||||||
|   </li> |   </li> | ||||||
|   <li class="{% is_active 'passbook_admin:invitations' 'passbook_admin:invitation-create' 'passbook_admin:invitation-update' 'passbook_admin:invitation-delete' 'passbook_admin:invitation-test' %}"> |   <li class="{% is_active 'passbook_admin:invitations' 'passbook_admin:invitation-create' 'passbook_admin:invitation-update' 'passbook_admin:invitation-delete' 'passbook_admin:invitation-test' %}"> | ||||||
|     <a href="{% url 'passbook_admin:invitations' %}">{% trans 'Invitations' %}</a> |     <a href="{% url 'passbook_admin:invitations' %}">{% trans 'Invitations' %}</a> | ||||||
|  | |||||||
| @ -31,11 +31,11 @@ | |||||||
|   <div class="col-xs-6 col-sm-2 col-md-2"> |   <div class="col-xs-6 col-sm-2 col-md-2"> | ||||||
|     <div class="card-pf card-pf-accented card-pf-aggregate-status"> |     <div class="card-pf card-pf-accented card-pf-aggregate-status"> | ||||||
|       <h2 class="card-pf-title"> |       <h2 class="card-pf-title"> | ||||||
|         <a href="#"><span class="fa fa-shield"></span><span class="card-pf-aggregate-status-count"></span> {% trans 'Rules' %}</a> |         <a href="#"><span class="fa fa-shield"></span><span class="card-pf-aggregate-status-count"></span> {% trans 'Policies' %}</a> | ||||||
|       </h2> |       </h2> | ||||||
|       <div class="card-pf-body"> |       <div class="card-pf-body"> | ||||||
|         <p class="card-pf-aggregate-status-notifications"> |         <p class="card-pf-aggregate-status-notifications"> | ||||||
|           <span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ rule_count }}</a></span> |           <span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ policy_count }}</a></span> | ||||||
|         </p> |         </p> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ | |||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
| <div class="container"> | <div class="container"> | ||||||
|   <h1>{% trans "Rules" %}</h1> |   <h1>{% trans "Policies" %}</h1> | ||||||
|   <div class="dropdown"> |   <div class="dropdown"> | ||||||
|     <button class="btn btn-primary dropdown-toggle" type="button" id="createDropdown" data-toggle="dropdown"> |     <button class="btn btn-primary dropdown-toggle" type="button" id="createDropdown" data-toggle="dropdown"> | ||||||
|       {% trans 'Create...' %} |       {% trans 'Create...' %} | ||||||
| @ -17,7 +17,7 @@ | |||||||
|     </button> |     </button> | ||||||
|     <ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown"> |     <ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown"> | ||||||
|       {% for type, name in types.items %} |       {% for type, name in types.items %} | ||||||
|       <li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'passbook_admin:rule-create' %}?type={{ type }}">{{ name }}</a></li> |       <li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'passbook_admin:policy-create' %}?type={{ type }}">{{ name }}</a></li> | ||||||
|       {% endfor %} |       {% endfor %} | ||||||
|     </ul> |     </ul> | ||||||
|   </div> |   </div> | ||||||
| @ -31,14 +31,14 @@ | |||||||
|       </tr> |       </tr> | ||||||
|     </thead> |     </thead> | ||||||
|     <tbody> |     <tbody> | ||||||
|       {% for rule in object_list %} |       {% for policy in object_list %} | ||||||
|         <tr> |         <tr> | ||||||
|           <td>{{ rule.name }}</td> |           <td>{{ policy.name }}</td> | ||||||
|           <td>{{ rule|fieldtype }}</td> |           <td>{{ policy|fieldtype }}</td> | ||||||
|           <td> |           <td> | ||||||
|             <a class="btn btn-default btn-sm" href="{% url 'passbook_admin:rule-update' pk=rule.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a> |             <a class="btn btn-default btn-sm" href="{% url 'passbook_admin:policy-update' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a> | ||||||
|             <a class="btn btn-default btn-sm" href="{% url 'passbook_admin:rule-test' pk=rule.uuid %}?back={{ request.get_full_path }}">{% trans 'Test' %}</a> |             <a class="btn btn-default btn-sm" href="{% url 'passbook_admin:policy-test' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Test' %}</a> | ||||||
|             <a class="btn btn-default btn-sm" href="{% url 'passbook_admin:rule-delete' pk=rule.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a> |             <a class="btn btn-default btn-sm" href="{% url 'passbook_admin:policy-delete' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a> | ||||||
|           </td> |           </td> | ||||||
|         </tr> |         </tr> | ||||||
|       {% endfor %} |       {% endfor %} | ||||||
							
								
								
									
										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 rest_framework_swagger.views import get_swagger_view | ||||||
|  |  | ||||||
| from passbook.admin.views import (applications, audit, factors, groups, | from passbook.admin.views import (applications, audit, factors, groups, | ||||||
|                                   invitations, overview, providers, rules, |                                   invitations, overview, policy, providers, | ||||||
|                                   sources, users) |                                   sources, users) | ||||||
|  |  | ||||||
| schema_view = get_swagger_view(title='passbook Admin Internal API') | schema_view = get_swagger_view(title='passbook Admin Internal API') | ||||||
| @ -25,12 +25,12 @@ urlpatterns = [ | |||||||
|     path('sources/create/', sources.SourceCreateView.as_view(), name='source-create'), |     path('sources/create/', sources.SourceCreateView.as_view(), name='source-create'), | ||||||
|     path('sources/<uuid:pk>/update/', sources.SourceUpdateView.as_view(), name='source-update'), |     path('sources/<uuid:pk>/update/', sources.SourceUpdateView.as_view(), name='source-update'), | ||||||
|     path('sources/<uuid:pk>/delete/', sources.SourceDeleteView.as_view(), name='source-delete'), |     path('sources/<uuid:pk>/delete/', sources.SourceDeleteView.as_view(), name='source-delete'), | ||||||
|     # Rules |     # Policies | ||||||
|     path('rules/', rules.RuleListView.as_view(), name='rules'), |     path('policies/', policy.PolicyListView.as_view(), name='policies'), | ||||||
|     path('rules/create/', rules.RuleCreateView.as_view(), name='rule-create'), |     path('policies/create/', policy.PolicyCreateView.as_view(), name='policy-create'), | ||||||
|     path('rules/<uuid:pk>/update/', rules.RuleUpdateView.as_view(), name='rule-update'), |     path('policies/<uuid:pk>/update/', policy.PolicyUpdateView.as_view(), name='policy-update'), | ||||||
|     path('rules/<uuid:pk>/delete/', rules.RuleDeleteView.as_view(), name='rule-delete'), |     path('policies/<uuid:pk>/delete/', policy.PolicyDeleteView.as_view(), name='policy-delete'), | ||||||
|     path('rules/<uuid:pk>/test/', rules.RuleTestView.as_view(), name='rule-test'), |     path('policies/<uuid:pk>/test/', policy.PolicyTestView.as_view(), name='policy-test'), | ||||||
|     # Providers |     # Providers | ||||||
|     path('providers/', providers.ProviderListView.as_view(), name='providers'), |     path('providers/', providers.ProviderListView.as_view(), name='providers'), | ||||||
|     path('providers/create/', |     path('providers/create/', | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| from django.views.generic import TemplateView | from django.views.generic import TemplateView | ||||||
|  |  | ||||||
| from passbook.admin.mixins import AdminRequiredMixin | from passbook.admin.mixins import AdminRequiredMixin | ||||||
| from passbook.core.models import Application, Provider, Rule, User | from passbook.core.models import Application, Policy, Provider, User | ||||||
|  |  | ||||||
|  |  | ||||||
| class AdministrationOverviewView(AdminRequiredMixin, TemplateView): | class AdministrationOverviewView(AdminRequiredMixin, TemplateView): | ||||||
| @ -12,7 +12,7 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView): | |||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs): |     def get_context_data(self, **kwargs): | ||||||
|         kwargs['application_count'] = len(Application.objects.all()) |         kwargs['application_count'] = len(Application.objects.all()) | ||||||
|         kwargs['rule_count'] = len(Rule.objects.all()) |         kwargs['policy_count'] = len(Policy.objects.all()) | ||||||
|         kwargs['user_count'] = len(User.objects.all()) |         kwargs['user_count'] = len(User.objects.all()) | ||||||
|         kwargs['provider_count'] = len(Provider.objects.all()) |         kwargs['provider_count'] = len(Provider.objects.all()) | ||||||
|         return super().get_context_data(**kwargs) |         return super().get_context_data(**kwargs) | ||||||
|  | |||||||
							
								
								
									
										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 | import uuid | ||||||
|  |  | ||||||
| @ -20,13 +20,32 @@ class Migration(migrations.Migration): | |||||||
|             name='AuditEntry', |             name='AuditEntry', | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), |                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||||||
|                 ('action', models.TextField()), |                 ('action', models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')])), | ||||||
|                 ('date', models.DateTimeField(auto_now_add=True)), |                 ('date', models.DateTimeField(auto_now_add=True)), | ||||||
|                 ('app', models.TextField()), |                 ('app', models.TextField()), | ||||||
|  |                 ('_context', models.TextField()), | ||||||
|  |                 ('request_ip', models.GenericIPAddressField()), | ||||||
|  |                 ('created', models.DateTimeField(auto_now_add=True)), | ||||||
|                 ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), |                 ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), | ||||||
|             ], |             ], | ||||||
|             options={ |             options={ | ||||||
|                 'abstract': False, |                 'verbose_name': 'Audit Entry', | ||||||
|  |                 'verbose_name_plural': 'Audit Entries', | ||||||
|             }, |             }, | ||||||
|         ), |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='LoginAttempt', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||||
|  |                 ('created', models.DateField(auto_now_add=True)), | ||||||
|  |                 ('last_updated', models.DateTimeField(auto_now=True)), | ||||||
|  |                 ('target_uid', models.CharField(max_length=254)), | ||||||
|  |                 ('request_ip', models.GenericIPAddressField()), | ||||||
|  |                 ('attempts', models.IntegerField(default=1)), | ||||||
|  |             ], | ||||||
|  |         ), | ||||||
|  |         migrations.AlterUniqueTogether( | ||||||
|  |             name='loginattempt', | ||||||
|  |             unique_together={('target_uid', 'request_ip', 'created')}, | ||||||
|  |         ), | ||||||
|     ] |     ] | ||||||
|  | |||||||
| @ -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' |     verbose_name = 'passbook Core' | ||||||
|  |  | ||||||
|     def ready(self): |     def ready(self): | ||||||
|         import_module('passbook.core.rules') |         import_module('passbook.core.policies') | ||||||
|         factors_to_load = CONFIG.y('passbook.factors', []) |         factors_to_load = CONFIG.y('passbook.factors', []) | ||||||
|         for factors_to_load in factors_to_load: |         for factors_to_load in factors_to_load: | ||||||
|             try: |             try: | ||||||
|  | |||||||
| @ -49,7 +49,7 @@ class AuthenticationView(UserPassesTestMixin, View): | |||||||
|             self.pending_factors = request.session[AuthenticationView.SESSION_PENDING_FACTORS] |             self.pending_factors = request.session[AuthenticationView.SESSION_PENDING_FACTORS] | ||||||
|         else: |         else: | ||||||
|             # Get an initial list of factors which are currently enabled |             # Get an initial list of factors which are currently enabled | ||||||
|             # and apply to the current user. We check rules here and block the request |             # and apply to the current user. We check policies here and block the request | ||||||
|             _all_factors = Factor.objects.filter(enabled=True) |             _all_factors = Factor.objects.filter(enabled=True) | ||||||
|             self.pending_factors = [] |             self.pending_factors = [] | ||||||
|             for factor in _all_factors: |             for factor in _all_factors: | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ class ApplicationForm(forms.ModelForm): | |||||||
|  |  | ||||||
|         model = Application |         model = Application | ||||||
|         fields = ['name', 'slug', 'launch_url', 'icon_url', |         fields = ['name', 'slug', 'launch_url', 'icon_url', | ||||||
|                   'rules', 'provider', 'skip_authorization'] |                   'policies', 'provider', 'skip_authorization'] | ||||||
|         widgets = { |         widgets = { | ||||||
|             'name': forms.TextInput(), |             'name': forms.TextInput(), | ||||||
|             'launch_url': forms.TextInput(), |             'launch_url': forms.TextInput(), | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ class FactorForm(forms.ModelForm): | |||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         model = Factor |         model = Factor | ||||||
|         fields = ['name', 'slug', 'order', 'rules', 'type', 'enabled'] |         fields = ['name', 'slug', 'order', 'policies', 'type', 'enabled'] | ||||||
|         widgets = { |         widgets = { | ||||||
|             'type': forms.Select(choices=get_factors()), |             'type': forms.Select(choices=get_factors()), | ||||||
|             'name': forms.TextInput(), |             'name': forms.TextInput(), | ||||||
|  | |||||||
| @ -1,18 +1,18 @@ | |||||||
| """passbook rule forms""" | """passbook Policy forms""" | ||||||
| 
 | 
 | ||||||
| from django import forms | from django import forms | ||||||
| from django.utils.translation import gettext as _ | from django.utils.translation import gettext as _ | ||||||
| 
 | 
 | ||||||
| from passbook.core.models import DebugRule, FieldMatcherRule, WebhookRule | from passbook.core.models import DebugPolicy, FieldMatcherPolicy, WebhookPolicy | ||||||
| 
 | 
 | ||||||
| GENERAL_FIELDS = ['name', 'action', 'negate', 'order', ] | GENERAL_FIELDS = ['name', 'action', 'negate', 'order', ] | ||||||
| 
 | 
 | ||||||
| class FieldMatcherRuleForm(forms.ModelForm): | class FieldMatcherPolicyForm(forms.ModelForm): | ||||||
|     """FieldMatcherRule Form""" |     """FieldMatcherPolicy Form""" | ||||||
| 
 | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
| 
 | 
 | ||||||
|         model = FieldMatcherRule |         model = FieldMatcherPolicy | ||||||
|         fields = GENERAL_FIELDS + ['user_field', 'match_action', 'value', ] |         fields = GENERAL_FIELDS + ['user_field', 'match_action', 'value', ] | ||||||
|         widgets = { |         widgets = { | ||||||
|             'name': forms.TextInput(), |             'name': forms.TextInput(), | ||||||
| @ -20,12 +20,12 @@ class FieldMatcherRuleForm(forms.ModelForm): | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class WebhookRuleForm(forms.ModelForm): | class WebhookPolicyForm(forms.ModelForm): | ||||||
|     """WebhookRuleForm Form""" |     """WebhookPolicyForm Form""" | ||||||
| 
 | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
| 
 | 
 | ||||||
|         model = WebhookRule |         model = WebhookPolicy | ||||||
|         fields = GENERAL_FIELDS + ['url', 'method', 'json_body', 'json_headers', |         fields = GENERAL_FIELDS + ['url', 'method', 'json_body', 'json_headers', | ||||||
|                                    'result_jsonpath', 'result_json_value', ] |                                    'result_jsonpath', 'result_json_value', ] | ||||||
|         widgets = { |         widgets = { | ||||||
| @ -37,12 +37,12 @@ class WebhookRuleForm(forms.ModelForm): | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class DebugRuleForm(forms.ModelForm): | class DebugPolicyForm(forms.ModelForm): | ||||||
|     """DebugRuleForm Form""" |     """DebugPolicyForm Form""" | ||||||
| 
 | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
| 
 | 
 | ||||||
|         model = DebugRule |         model = DebugPolicy | ||||||
|         fields = GENERAL_FIELDS + ['result', 'wait_min', 'wait_max'] |         fields = GENERAL_FIELDS + ['result', 'wait_min', 'wait_max'] | ||||||
|         widgets = { |         widgets = { | ||||||
|             'name': forms.TextInput(), |             'name': forms.TextInput(), | ||||||
| @ -1,4 +1,4 @@ | |||||||
| # Generated by Django 2.1.5 on 2019-02-08 10:42 | # Generated by Django 2.1.7 on 2019-02-16 09:10 | ||||||
|  |  | ||||||
| import uuid | import uuid | ||||||
|  |  | ||||||
| @ -68,13 +68,7 @@ class Migration(migrations.Migration): | |||||||
|             }, |             }, | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='Provider', |             name='Policy', | ||||||
|             fields=[ |  | ||||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |  | ||||||
|             ], |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='Rule', |  | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('created', models.DateField(auto_now_add=True)), |                 ('created', models.DateField(auto_now_add=True)), | ||||||
|                 ('last_updated', models.DateTimeField(auto_now=True)), |                 ('last_updated', models.DateTimeField(auto_now=True)), | ||||||
| @ -89,7 +83,7 @@ class Migration(migrations.Migration): | |||||||
|             }, |             }, | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='RuleModel', |             name='PolicyModel', | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('created', models.DateField(auto_now_add=True)), |                 ('created', models.DateField(auto_now_add=True)), | ||||||
|                 ('last_updated', models.DateTimeField(auto_now=True)), |                 ('last_updated', models.DateTimeField(auto_now=True)), | ||||||
| @ -99,6 +93,12 @@ class Migration(migrations.Migration): | |||||||
|                 'abstract': False, |                 'abstract': False, | ||||||
|             }, |             }, | ||||||
|         ), |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Provider', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||||
|  |             ], | ||||||
|  |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='UserSourceConnection', |             name='UserSourceConnection', | ||||||
|             fields=[ |             fields=[ | ||||||
| @ -111,51 +111,82 @@ class Migration(migrations.Migration): | |||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='Application', |             name='Application', | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('rulemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.RuleModel')), |                 ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')), | ||||||
|                 ('name', models.TextField()), |                 ('name', models.TextField()), | ||||||
|                 ('slug', models.SlugField()), |                 ('slug', models.SlugField()), | ||||||
|                 ('launch_url', models.URLField(blank=True, null=True)), |                 ('launch_url', models.URLField(blank=True, null=True)), | ||||||
|                 ('icon_url', models.TextField(blank=True, null=True)), |                 ('icon_url', models.TextField(blank=True, null=True)), | ||||||
|                 ('skip_authorization', models.BooleanField(default=False)), |                 ('skip_authorization', models.BooleanField(default=False)), | ||||||
|                 ('provider', models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='passbook_core.Provider')), |                 ('provider', models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='passbook_core.Provider')), | ||||||
|             ], |             ], | ||||||
|             options={ |             options={ | ||||||
|                 'abstract': False, |                 'abstract': False, | ||||||
|             }, |             }, | ||||||
|             bases=('passbook_core.rulemodel',), |             bases=('passbook_core.policymodel',), | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='DebugRule', |             name='DebugPolicy', | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Rule')), |                 ('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')), | ||||||
|                 ('result', models.BooleanField(default=False)), |                 ('result', models.BooleanField(default=False)), | ||||||
|                 ('wait_min', models.IntegerField(default=5)), |                 ('wait_min', models.IntegerField(default=5)), | ||||||
|                 ('wait_max', models.IntegerField(default=30)), |                 ('wait_max', models.IntegerField(default=30)), | ||||||
|             ], |             ], | ||||||
|             options={ |             options={ | ||||||
|                 'verbose_name': 'Debug Rule', |                 'verbose_name': 'Debug Policy', | ||||||
|                 'verbose_name_plural': 'Debug Rules', |                 'verbose_name_plural': 'Debug Policys', | ||||||
|             }, |             }, | ||||||
|             bases=('passbook_core.rule',), |             bases=('passbook_core.policy',), | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='FieldMatcherRule', |             name='Factor', | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Rule')), |                 ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')), | ||||||
|                 ('user_field', models.TextField(choices=[('username', 'username'), ('first_name', 'first_name'), ('last_name', 'last_name'), ('email', 'email'), ('is_staff', 'is_staff'), ('is_active', 'is_active'), ('data_joined', 'data_joined')])), |                 ('name', models.TextField()), | ||||||
|  |                 ('slug', models.SlugField(unique=True)), | ||||||
|  |                 ('order', models.IntegerField()), | ||||||
|  |                 ('type', models.TextField(unique=True)), | ||||||
|  |                 ('enabled', models.BooleanField(default=True)), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'abstract': False, | ||||||
|  |             }, | ||||||
|  |             bases=('passbook_core.policymodel',), | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='FieldMatcherPolicy', | ||||||
|  |             fields=[ | ||||||
|  |                 ('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')), | ||||||
|  |                 ('user_field', models.TextField(choices=[('username', 'Username'), ('first_name', 'First Name'), ('last_name', 'Last Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')])), | ||||||
|                 ('match_action', models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('endswith', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50)), |                 ('match_action', models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('endswith', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50)), | ||||||
|                 ('value', models.TextField()), |                 ('value', models.TextField()), | ||||||
|             ], |             ], | ||||||
|             options={ |             options={ | ||||||
|                 'verbose_name': 'Field matcher Rule', |                 'verbose_name': 'Field matcher Policy', | ||||||
|                 'verbose_name_plural': 'Field matcher Rules', |                 'verbose_name_plural': 'Field matcher Policys', | ||||||
|             }, |             }, | ||||||
|             bases=('passbook_core.rule',), |             bases=('passbook_core.policy',), | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='PasswordPolicyPolicy', | ||||||
|  |             fields=[ | ||||||
|  |                 ('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')), | ||||||
|  |                 ('amount_uppercase', models.IntegerField(default=0)), | ||||||
|  |                 ('amount_lowercase', models.IntegerField(default=0)), | ||||||
|  |                 ('amount_symbols', models.IntegerField(default=0)), | ||||||
|  |                 ('length_min', models.IntegerField(default=0)), | ||||||
|  |                 ('symbol_charset', models.TextField(default='!\\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Password Policy Policy', | ||||||
|  |                 'verbose_name_plural': 'Password Policy Policys', | ||||||
|  |             }, | ||||||
|  |             bases=('passbook_core.policy',), | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='Source', |             name='Source', | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('rulemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.RuleModel')), |                 ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')), | ||||||
|                 ('name', models.TextField()), |                 ('name', models.TextField()), | ||||||
|                 ('slug', models.SlugField()), |                 ('slug', models.SlugField()), | ||||||
|                 ('enabled', models.BooleanField(default=True)), |                 ('enabled', models.BooleanField(default=True)), | ||||||
| @ -163,12 +194,12 @@ class Migration(migrations.Migration): | |||||||
|             options={ |             options={ | ||||||
|                 'abstract': False, |                 'abstract': False, | ||||||
|             }, |             }, | ||||||
|             bases=('passbook_core.rulemodel',), |             bases=('passbook_core.policymodel',), | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='WebhookRule', |             name='WebhookPolicy', | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Rule')), |                 ('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')), | ||||||
|                 ('url', models.URLField()), |                 ('url', models.URLField()), | ||||||
|                 ('method', models.CharField(choices=[('GET', 'GET'), ('POST', 'POST'), ('PATCH', 'PATCH'), ('DELETE', 'DELETE'), ('PUT', 'PUT')], max_length=10)), |                 ('method', models.CharField(choices=[('GET', 'GET'), ('POST', 'POST'), ('PATCH', 'PATCH'), ('DELETE', 'DELETE'), ('PUT', 'PUT')], max_length=10)), | ||||||
|                 ('json_body', models.TextField()), |                 ('json_body', models.TextField()), | ||||||
| @ -177,15 +208,15 @@ class Migration(migrations.Migration): | |||||||
|                 ('result_json_value', models.TextField()), |                 ('result_json_value', models.TextField()), | ||||||
|             ], |             ], | ||||||
|             options={ |             options={ | ||||||
|                 'verbose_name': 'Webhook Rule', |                 'verbose_name': 'Webhook Policy', | ||||||
|                 'verbose_name_plural': 'Webhook Rules', |                 'verbose_name_plural': 'Webhook Policys', | ||||||
|             }, |             }, | ||||||
|             bases=('passbook_core.rule',), |             bases=('passbook_core.policy',), | ||||||
|         ), |         ), | ||||||
|         migrations.AddField( |         migrations.AddField( | ||||||
|             model_name='rulemodel', |             model_name='policymodel', | ||||||
|             name='rules', |             name='policies', | ||||||
|             field=models.ManyToManyField(blank=True, to='passbook_core.Rule'), |             field=models.ManyToManyField(blank=True, to='passbook_core.Policy'), | ||||||
|         ), |         ), | ||||||
|         migrations.AddField( |         migrations.AddField( | ||||||
|             model_name='user', |             model_name='user', | ||||||
|  | |||||||
| @ -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 getattr(self, 'name') | ||||||
|         return super().__str__() |         return super().__str__() | ||||||
|  |  | ||||||
| class RuleModel(UUIDModel, CreatedUpdatedModel): | class PolicyModel(UUIDModel, CreatedUpdatedModel): | ||||||
|     """Base model which can have rules applied to it""" |     """Base model which can have policies applied to it""" | ||||||
|  |  | ||||||
|     rules = models.ManyToManyField('Rule', blank=True) |     policies = models.ManyToManyField('Policy', blank=True) | ||||||
|  |  | ||||||
|     def passes(self, user: User) -> bool: |     def passes(self, user: User) -> bool: | ||||||
|         """Return true if user passes, otherwise False or raise Exception""" |         """Return true if user passes, otherwise False or raise Exception""" | ||||||
|         for rule in self.rules: |         for policy in self.policies: | ||||||
|             if not rule.passes(user): |             if not policy.passes(user): | ||||||
|                 return False |                 return False | ||||||
|         return True |         return True | ||||||
|  |  | ||||||
| class Factor(RuleModel): | class Factor(PolicyModel): | ||||||
|     """Authentication factor, multiple instances of the same Factor can be used""" |     """Authentication factor, multiple instances of the same Factor can be used""" | ||||||
|  |  | ||||||
|     name = models.TextField() |     name = models.TextField() | ||||||
| @ -73,7 +73,7 @@ class Factor(RuleModel): | |||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return "Factor %s" % self.slug |         return "Factor %s" % self.slug | ||||||
|  |  | ||||||
| class Application(RuleModel): | class Application(PolicyModel): | ||||||
|     """Every Application which uses passbook for authentication/identification/authorization |     """Every Application which uses passbook for authentication/identification/authorization | ||||||
|     needs an Application record. Other authentication types can subclass this Model to |     needs an Application record. Other authentication types can subclass this Model to | ||||||
|     add custom fields and other properties""" |     add custom fields and other properties""" | ||||||
| @ -90,13 +90,13 @@ class Application(RuleModel): | |||||||
|  |  | ||||||
|     def user_is_authorized(self, user: User) -> bool: |     def user_is_authorized(self, user: User) -> bool: | ||||||
|         """Check if user is authorized to use this application""" |         """Check if user is authorized to use this application""" | ||||||
|         from passbook.core.rules import RuleEngine |         from passbook.core.policies import PolicyEngine | ||||||
|         return RuleEngine(self.rules.all()).for_user(user).result |         return PolicyEngine(self.policies.all()).for_user(user).result | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return self.name |         return self.name | ||||||
|  |  | ||||||
| class Source(RuleModel): | class Source(PolicyModel): | ||||||
|     """Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server""" |     """Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server""" | ||||||
|  |  | ||||||
|     name = models.TextField() |     name = models.TextField() | ||||||
| @ -129,8 +129,8 @@ class UserSourceConnection(CreatedUpdatedModel): | |||||||
|  |  | ||||||
|         unique_together = (('user', 'source'),) |         unique_together = (('user', 'source'),) | ||||||
|  |  | ||||||
| class Rule(UUIDModel, CreatedUpdatedModel): | class Policy(UUIDModel, CreatedUpdatedModel): | ||||||
|     """Rules which specify if a user is authorized to use an Application. Can be overridden by |     """Policys which specify if a user is authorized to use an Application. Can be overridden by | ||||||
|     other types to add other fields, more logic, etc.""" |     other types to add other fields, more logic, etc.""" | ||||||
|  |  | ||||||
|     ACTION_ALLOW = 'allow' |     ACTION_ALLOW = 'allow' | ||||||
| @ -153,11 +153,11 @@ class Rule(UUIDModel, CreatedUpdatedModel): | |||||||
|         return "%s action %s" % (self.name, self.action) |         return "%s action %s" % (self.name, self.action) | ||||||
|  |  | ||||||
|     def passes(self, user: User) -> bool: |     def passes(self, user: User) -> bool: | ||||||
|         """Check if user instance passes this rule""" |         """Check if user instance passes this policy""" | ||||||
|         raise NotImplementedError() |         raise NotImplementedError() | ||||||
|  |  | ||||||
| class FieldMatcherRule(Rule): | class FieldMatcherPolicy(Policy): | ||||||
|     """Rule which checks if a field of the User model matches/doesn't match a |     """Policy which checks if a field of the User model matches/doesn't match a | ||||||
|     certain pattern""" |     certain pattern""" | ||||||
|  |  | ||||||
|     MATCH_STARTSWITH = 'startswith' |     MATCH_STARTSWITH = 'startswith' | ||||||
| @ -188,7 +188,7 @@ class FieldMatcherRule(Rule): | |||||||
|     match_action = models.CharField(max_length=50, choices=MATCHES) |     match_action = models.CharField(max_length=50, choices=MATCHES) | ||||||
|     value = models.TextField() |     value = models.TextField() | ||||||
|  |  | ||||||
|     form = 'passbook.core.forms.rules.FieldMatcherRuleForm' |     form = 'passbook.core.forms.policies.FieldMatcherPolicyForm' | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         description = "%s, user.%s %s '%s'" % (self.name, self.user_field, |         description = "%s, user.%s %s '%s'" % (self.name, self.user_field, | ||||||
| @ -205,13 +205,13 @@ class FieldMatcherRule(Rule): | |||||||
|         LOGGER.debug("Checked '%s' %s with '%s'...", |         LOGGER.debug("Checked '%s' %s with '%s'...", | ||||||
|                      user_field_value, self.match_action, self.value) |                      user_field_value, self.match_action, self.value) | ||||||
|         passes = False |         passes = False | ||||||
|         if self.match_action == FieldMatcherRule.MATCH_STARTSWITH: |         if self.match_action == FieldMatcherPolicy.MATCH_STARTSWITH: | ||||||
|             passes = user_field_value.startswith(self.value) |             passes = user_field_value.startswith(self.value) | ||||||
|         if self.match_action == FieldMatcherRule.MATCH_ENDSWITH: |         if self.match_action == FieldMatcherPolicy.MATCH_ENDSWITH: | ||||||
|             passes = user_field_value.endswith(self.value) |             passes = user_field_value.endswith(self.value) | ||||||
|         if self.match_action == FieldMatcherRule.MATCH_CONTAINS: |         if self.match_action == FieldMatcherPolicy.MATCH_CONTAINS: | ||||||
|             passes = self.value in user_field_value |             passes = self.value in user_field_value | ||||||
|         if self.match_action == FieldMatcherRule.MATCH_REGEXP: |         if self.match_action == FieldMatcherPolicy.MATCH_REGEXP: | ||||||
|             pattern = re.compile(self.value) |             pattern = re.compile(self.value) | ||||||
|             passes = bool(pattern.match(user_field_value)) |             passes = bool(pattern.match(user_field_value)) | ||||||
|         if self.negate: |         if self.negate: | ||||||
| @ -221,11 +221,11 @@ class FieldMatcherRule(Rule): | |||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         verbose_name = _('Field matcher Rule') |         verbose_name = _('Field matcher Policy') | ||||||
|         verbose_name_plural = _('Field matcher Rules') |         verbose_name_plural = _('Field matcher Policys') | ||||||
|  |  | ||||||
| class PasswordPolicyRule(Rule): | class PasswordPolicyPolicy(Policy): | ||||||
|     """Rule to make sure passwords have certain properties""" |     """Policy to make sure passwords have certain properties""" | ||||||
|  |  | ||||||
|     amount_uppercase = models.IntegerField(default=0) |     amount_uppercase = models.IntegerField(default=0) | ||||||
|     amount_lowercase = models.IntegerField(default=0) |     amount_lowercase = models.IntegerField(default=0) | ||||||
| @ -233,7 +233,7 @@ class PasswordPolicyRule(Rule): | |||||||
|     length_min = models.IntegerField(default=0) |     length_min = models.IntegerField(default=0) | ||||||
|     symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ") |     symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ") | ||||||
|  |  | ||||||
|     form = 'passbook.core.forms.rules.PasswordPolicyRuleForm' |     form = 'passbook.core.forms.policies.PasswordPolicyPolicyForm' | ||||||
|  |  | ||||||
|     def passes(self, user: User) -> bool: |     def passes(self, user: User) -> bool: | ||||||
|         # Only check if password is being set |         # Only check if password is being set | ||||||
| @ -254,12 +254,12 @@ class PasswordPolicyRule(Rule): | |||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         verbose_name = _('Password Policy Rule') |         verbose_name = _('Password Policy Policy') | ||||||
|         verbose_name_plural = _('Password Policy Rules') |         verbose_name_plural = _('Password Policy Policys') | ||||||
|  |  | ||||||
|  |  | ||||||
| class WebhookRule(Rule): | class WebhookPolicy(Policy): | ||||||
|     """Rule that asks webhook""" |     """Policy that asks webhook""" | ||||||
|  |  | ||||||
|     METHOD_GET = 'GET' |     METHOD_GET = 'GET' | ||||||
|     METHOD_POST = 'POST' |     METHOD_POST = 'POST' | ||||||
| @ -282,7 +282,7 @@ class WebhookRule(Rule): | |||||||
|     result_jsonpath = models.TextField() |     result_jsonpath = models.TextField() | ||||||
|     result_json_value = models.TextField() |     result_json_value = models.TextField() | ||||||
|  |  | ||||||
|     form = 'passbook.core.forms.rules.WebhookRuleForm' |     form = 'passbook.core.forms.policies.WebhookPolicyForm' | ||||||
|  |  | ||||||
|     def passes(self, user: User): |     def passes(self, user: User): | ||||||
|         """Call webhook asynchronously and report back""" |         """Call webhook asynchronously and report back""" | ||||||
| @ -290,30 +290,30 @@ class WebhookRule(Rule): | |||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         verbose_name = _('Webhook Rule') |         verbose_name = _('Webhook Policy') | ||||||
|         verbose_name_plural = _('Webhook Rules') |         verbose_name_plural = _('Webhook Policys') | ||||||
|  |  | ||||||
| class DebugRule(Rule): | class DebugPolicy(Policy): | ||||||
|     """Rule used for debugging the RuleEngine. Returns a fixed result, |     """Policy used for debugging the PolicyEngine. Returns a fixed result, | ||||||
|     but takes a random time to process.""" |     but takes a random time to process.""" | ||||||
|  |  | ||||||
|     result = models.BooleanField(default=False) |     result = models.BooleanField(default=False) | ||||||
|     wait_min = models.IntegerField(default=5) |     wait_min = models.IntegerField(default=5) | ||||||
|     wait_max = models.IntegerField(default=30) |     wait_max = models.IntegerField(default=30) | ||||||
|  |  | ||||||
|     form = 'passbook.core.forms.rules.DebugRuleForm' |     form = 'passbook.core.forms.policies.DebugPolicyForm' | ||||||
|  |  | ||||||
|     def passes(self, user: User): |     def passes(self, user: User): | ||||||
|         """Wait random time then return result""" |         """Wait random time then return result""" | ||||||
|         wait = SystemRandom().randrange(self.wait_min, self.wait_max) |         wait = SystemRandom().randrange(self.wait_min, self.wait_max) | ||||||
|         LOGGER.debug("Rule '%s' waiting for %ds", self.name, wait) |         LOGGER.debug("Policy '%s' waiting for %ds", self.name, wait) | ||||||
|         sleep(wait) |         sleep(wait) | ||||||
|         return self.result |         return self.result | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         verbose_name = _('Debug Rule') |         verbose_name = _('Debug Policy') | ||||||
|         verbose_name_plural = _('Debug Rules') |         verbose_name_plural = _('Debug Policys') | ||||||
|  |  | ||||||
| class Invitation(UUIDModel): | class Invitation(UUIDModel): | ||||||
|     """Single-use invitation link""" |     """Single-use invitation link""" | ||||||
|  | |||||||
							
								
								
									
										48
									
								
								passbook/core/policies.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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 |         model = LDAPSource | ||||||
|         fields = SOURCE_FORM_FIELDS + ['server_uri', 'bind_cn', 'bind_password', |         fields = SOURCE_FORM_FIELDS + ['server_uri', 'bind_cn', 'bind_password', | ||||||
|                                        'type', 'domain', 'base_dn', 'create_user', |                                        'type', 'domain', 'base_dn', 'create_user', | ||||||
|                                        'reset_password', 'rules'] |                                        'reset_password', 'policies'] | ||||||
|         widgets = { |         widgets = { | ||||||
|             'name': forms.TextInput(), |             'name': forms.TextInput(), | ||||||
|             'server_uri': forms.TextInput(), |             'server_uri': forms.TextInput(), | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| # Generated by Django 2.1.4 on 2018-12-10 09:16 | # Generated by Django 2.1.7 on 2019-02-16 09:13 | ||||||
|  |  | ||||||
| import django.db.models.deletion | import django.db.models.deletion | ||||||
| from django.db import migrations, models | from django.db import migrations, models | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| # Generated by Django 2.1.4 on 2018-12-10 09:16 | # Generated by Django 2.1.7 on 2019-02-16 09:13 | ||||||
|  |  | ||||||
| import django.db.models.deletion | import django.db.models.deletion | ||||||
| from django.db import migrations, models | from django.db import migrations, models | ||||||
| @ -26,8 +26,8 @@ class Migration(migrations.Migration): | |||||||
|                 ('consumer_secret', models.TextField()), |                 ('consumer_secret', models.TextField()), | ||||||
|             ], |             ], | ||||||
|             options={ |             options={ | ||||||
|                 'verbose_name': 'OAuth Source', |                 'verbose_name': 'Generic OAuth Source', | ||||||
|                 'verbose_name_plural': 'OAuth Sources', |                 'verbose_name_plural': 'Generic OAuth Sources', | ||||||
|             }, |             }, | ||||||
|             bases=('passbook_core.source',), |             bases=('passbook_core.source',), | ||||||
|         ), |         ), | ||||||
|  | |||||||
| @ -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 django.db.models.deletion | ||||||
| import oauth2_provider.generators | import oauth2_provider.generators | ||||||
| @ -15,8 +15,8 @@ class Migration(migrations.Migration): | |||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     dependencies = [ |     dependencies = [ | ||||||
|         ('passbook_core', '0001_initial'), |  | ||||||
|         migrations.swappable_dependency(settings.AUTH_USER_MODEL), |         migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||||||
|  |         ('passbook_core', '0001_initial'), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     operations = [ |     operations = [ | ||||||
| @ -36,7 +36,8 @@ class Migration(migrations.Migration): | |||||||
|                 ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='passbook_oauth_provider_oauth2provider', to=settings.AUTH_USER_MODEL)), |                 ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='passbook_oauth_provider_oauth2provider', to=settings.AUTH_USER_MODEL)), | ||||||
|             ], |             ], | ||||||
|             options={ |             options={ | ||||||
|                 'abstract': False, |                 'verbose_name': 'OAuth2 Provider', | ||||||
|  |                 'verbose_name_plural': 'OAuth2 Providers', | ||||||
|             }, |             }, | ||||||
|             bases=('passbook_core.provider', models.Model), |             bases=('passbook_core.provider', models.Model), | ||||||
|         ), |         ), | ||||||
|  | |||||||
| @ -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): | class PassbookAuthorizationView(AccessMixin, AuthorizationView): | ||||||
|     """Custom OAuth2 Authorization View which checks rules, etc""" |     """Custom OAuth2 Authorization View which checks policies, etc""" | ||||||
|  |  | ||||||
|     _application = None |     _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 | import django.db.models.deletion | ||||||
| from django.db import migrations, models | from django.db import migrations, models | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer