293 lines
10 KiB
Python
293 lines
10 KiB
Python
# authentic2 - versatile identity manager
|
|
# Copyright (C) 2010-2022 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from django.apps import apps
|
|
from django.contrib import messages
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.forms.models import modelform_factory
|
|
from django.http import Http404, HttpResponseRedirect
|
|
from django.shortcuts import get_object_or_404
|
|
from django.urls import reverse, reverse_lazy
|
|
from django.utils.functional import cached_property
|
|
from django.utils.text import slugify
|
|
from django.utils.translation import ugettext as _
|
|
from django.views.generic import CreateView, DeleteView, DetailView, FormView, UpdateView
|
|
from django.views.generic.list import ListView
|
|
|
|
from authentic2.apps.authenticators import forms
|
|
from authentic2.apps.authenticators.models import BaseAuthenticator
|
|
from authentic2.apps.journal.views import JournalViewWithContext
|
|
from authentic2.manager.journal_views import BaseJournalView
|
|
from authentic2.manager.views import MediaMixin, TitleMixin
|
|
|
|
|
|
class AuthenticatorsMixin(MediaMixin, TitleMixin):
|
|
model = BaseAuthenticator
|
|
|
|
def get_queryset(self):
|
|
return self.model.authenticators.all()
|
|
|
|
|
|
class AuthenticatorsView(AuthenticatorsMixin, ListView):
|
|
template_name = 'authentic2/authenticators/authenticators.html'
|
|
title = _('Authenticators')
|
|
|
|
|
|
authenticators = AuthenticatorsView.as_view()
|
|
|
|
|
|
class AuthenticatorAddView(AuthenticatorsMixin, CreateView):
|
|
template_name = 'authentic2/authenticators/authenticator_add_form.html'
|
|
title = _('New authenticator')
|
|
form_class = forms.AuthenticatorAddForm
|
|
|
|
def get_success_url(self):
|
|
return reverse('a2-manager-authenticator-edit', kwargs={'pk': self.object.pk})
|
|
|
|
def form_valid(self, form):
|
|
resp = super().form_valid(form)
|
|
self.request.journal.record('authenticator.creation', authenticator=form.instance)
|
|
return resp
|
|
|
|
|
|
add = AuthenticatorAddView.as_view()
|
|
|
|
|
|
class AuthenticatorDetailView(AuthenticatorsMixin, DetailView):
|
|
def get_template_names(self):
|
|
return self.object.manager_view_template_name
|
|
|
|
@property
|
|
def title(self):
|
|
return str(self.object)
|
|
|
|
|
|
detail = AuthenticatorDetailView.as_view()
|
|
|
|
|
|
def build_tab_is_not_default(form):
|
|
for field_name, field in form.fields.items():
|
|
if field.initial is not None:
|
|
initial_value = field.initial() if callable(field.initial) else field.initial
|
|
if initial_value != form.initial.get(field_name):
|
|
return True
|
|
else:
|
|
if bool(form.initial.get(field_name)):
|
|
return True
|
|
return False
|
|
|
|
|
|
class MultipleFormsUpdateView(UpdateView):
|
|
def get_context_data(self, **kwargs):
|
|
kwargs['object'] = self.object
|
|
kwargs['forms'] = kwargs.get('forms') or self.get_forms()
|
|
return kwargs
|
|
|
|
|
|
class AuthenticatorEditView(AuthenticatorsMixin, MultipleFormsUpdateView):
|
|
template_name = 'authentic2/authenticators/authenticator_edit_form.html'
|
|
title = _('Edit authenticator')
|
|
|
|
def get_forms(self):
|
|
forms = []
|
|
for label, form_class in self.object.manager_form_classes:
|
|
form = form_class(**self.get_form_kwargs())
|
|
form.tab_name = label
|
|
form.tab_slug = slugify(label)
|
|
form.is_not_default = build_tab_is_not_default(form)
|
|
forms.append(form)
|
|
return forms
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
forms = self.get_forms()
|
|
|
|
all_valid = all(form.is_valid() for form in forms)
|
|
if all_valid:
|
|
for form in forms:
|
|
form.save()
|
|
self.request.journal.record('authenticator.edit', forms=forms)
|
|
return HttpResponseRedirect(self.get_success_url())
|
|
|
|
return self.render_to_response(self.get_context_data(forms=forms))
|
|
|
|
|
|
edit = AuthenticatorEditView.as_view()
|
|
|
|
|
|
class AuthenticatorDeleteView(AuthenticatorsMixin, DeleteView):
|
|
template_name = 'authentic2/authenticators/authenticator_delete_form.html'
|
|
title = _('Delete authenticator')
|
|
success_url = reverse_lazy('a2-manager-authenticators')
|
|
|
|
def dispatch(self, *args, **kwargs):
|
|
if self.get_object().protected:
|
|
raise PermissionDenied
|
|
return super().dispatch(*args, **kwargs)
|
|
|
|
def delete(self, *args, **kwargs):
|
|
self.request.journal.record('authenticator.deletion', authenticator=self.get_object())
|
|
return super().delete(*args, **kwargs)
|
|
|
|
|
|
delete = AuthenticatorDeleteView.as_view()
|
|
|
|
|
|
class AuthenticatorToggleView(AuthenticatorsMixin, DetailView):
|
|
def dispatch(self, *args, **kwargs):
|
|
self.authenticator = self.get_object()
|
|
if self.authenticator.protected or not self.authenticator.has_valid_configuration():
|
|
raise PermissionDenied
|
|
return super().dispatch(*args, **kwargs)
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
if self.authenticator.enabled:
|
|
self.authenticator.enabled = False
|
|
self.authenticator.save()
|
|
self.request.journal.record('authenticator.disable', authenticator=self.authenticator)
|
|
message = _('Authenticator has been disabled.')
|
|
else:
|
|
self.authenticator.enabled = True
|
|
self.authenticator.save()
|
|
self.request.journal.record('authenticator.enable', authenticator=self.authenticator)
|
|
message = _('Authenticator has been enabled.')
|
|
|
|
messages.info(self.request, message)
|
|
return HttpResponseRedirect(self.authenticator.get_absolute_url())
|
|
|
|
|
|
toggle = AuthenticatorToggleView.as_view()
|
|
|
|
|
|
class AuthenticatorJournal(JournalViewWithContext, BaseJournalView):
|
|
template_name = 'authentic2/authenticators/authenticator_journal.html'
|
|
title = _('Journal')
|
|
|
|
@cached_property
|
|
def context(self):
|
|
return get_object_or_404(BaseAuthenticator.authenticators.all(), pk=self.kwargs['pk'])
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
ctx['object'] = self.context
|
|
ctx['object_name'] = str(self.context)
|
|
return ctx
|
|
|
|
|
|
journal = AuthenticatorJournal.as_view()
|
|
|
|
|
|
class AuthenticatorsOrderView(AuthenticatorsMixin, FormView):
|
|
template_name = 'authentic2/authenticators/authenticators_order_form.html'
|
|
title = _('Configure display order')
|
|
form_class = forms.AuthenticatorsOrderForm
|
|
success_url = reverse_lazy('a2-manager-authenticators')
|
|
|
|
def form_valid(self, form):
|
|
order_by_pk = {pk: i for i, pk in enumerate(form.cleaned_data['order'].split(','))}
|
|
|
|
authenticators = list(self.get_queryset())
|
|
for authenticator in authenticators:
|
|
authenticator.order = order_by_pk[str(authenticator.pk)]
|
|
|
|
BaseAuthenticator.objects.bulk_update(authenticators, ['order'])
|
|
return super().form_valid(form)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['authenticators'] = self.get_queryset()
|
|
return context
|
|
|
|
def get_queryset(self):
|
|
qs = super().get_queryset()
|
|
return qs.filter(enabled=True)
|
|
|
|
|
|
order = AuthenticatorsOrderView.as_view()
|
|
|
|
|
|
class AuthenticatorRelatedObjectMixin(MediaMixin, TitleMixin):
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.authenticator = get_object_or_404(
|
|
BaseAuthenticator.authenticators.all(), pk=kwargs.get('authenticator_pk')
|
|
)
|
|
|
|
model_name = kwargs.get('model_name')
|
|
if model_name not in (x._meta.model_name for x in self.authenticator.related_models):
|
|
raise Http404()
|
|
try:
|
|
self.model = apps.get_model(self.authenticator._meta.app_label, model_name)
|
|
except LookupError:
|
|
self.model = apps.get_model('authenticators', model_name)
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_form_class(self):
|
|
return modelform_factory(self.model, self.authenticator.related_object_form_class)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
if not kwargs.get('instance'):
|
|
kwargs['instance'] = self.model()
|
|
kwargs['instance'].authenticator = self.authenticator
|
|
return kwargs
|
|
|
|
def get_success_url(self):
|
|
return (
|
|
reverse('a2-manager-authenticator-detail', kwargs={'pk': self.authenticator.pk})
|
|
+ '#open:%s' % self.model._meta.model_name
|
|
)
|
|
|
|
@property
|
|
def title(self):
|
|
return self.model._meta.verbose_name
|
|
|
|
|
|
class RelatedObjectAddView(AuthenticatorRelatedObjectMixin, CreateView):
|
|
template_name = 'authentic2/manager/form.html'
|
|
|
|
def form_valid(self, form):
|
|
resp = super().form_valid(form)
|
|
self.request.journal.record('authenticator.related_object.creation', related_object=form.instance)
|
|
return resp
|
|
|
|
|
|
add_related_object = RelatedObjectAddView.as_view()
|
|
|
|
|
|
class RelatedObjectEditView(AuthenticatorRelatedObjectMixin, UpdateView):
|
|
template_name = 'authentic2/manager/form.html'
|
|
|
|
def form_valid(self, form):
|
|
resp = super().form_valid(form)
|
|
self.request.journal.record('authenticator.related_object.edit', form=form)
|
|
return resp
|
|
|
|
|
|
edit_related_object = RelatedObjectEditView.as_view()
|
|
|
|
|
|
class RelatedObjectDeleteView(AuthenticatorRelatedObjectMixin, DeleteView):
|
|
template_name = 'authentic2/authenticators/authenticator_delete_form.html'
|
|
title = ''
|
|
|
|
def delete(self, *args, **kwargs):
|
|
self.request.journal.record('authenticator.related_object.deletion', related_object=self.get_object())
|
|
return super().delete(*args, **kwargs)
|
|
|
|
|
|
delete_related_object = RelatedObjectDeleteView.as_view()
|