842 lines
29 KiB
Python
842 lines
29 KiB
Python
# authentic2 - versatile identity manager
|
||
# Copyright (C) 2010-2019 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/>.
|
||
|
||
import json
|
||
from functools import reduce
|
||
|
||
from django.contrib import messages
|
||
from django.contrib.auth import get_user_model
|
||
from django.contrib.contenttypes.models import ContentType
|
||
from django.core.exceptions import PermissionDenied, ValidationError
|
||
from django.core.paginator import EmptyPage, Paginator
|
||
from django.db import transaction
|
||
from django.db.models import BooleanField, Count, ExpressionWrapper, F, Prefetch, Q, Value
|
||
from django.db.models.functions import Cast
|
||
from django.http import Http404, JsonResponse
|
||
from django.shortcuts import get_object_or_404
|
||
from django.urls import reverse
|
||
from django.utils.functional import cached_property
|
||
from django.utils.translation import gettext_lazy as _
|
||
from django.views.generic import DetailView, FormView, TemplateView
|
||
from django.views.generic.detail import SingleObjectMixin
|
||
|
||
from authentic2 import data_transfer
|
||
from authentic2.a2_rbac.models import OrganizationalUnit, Permission, Role, RoleParenting
|
||
from authentic2.a2_rbac.utils import get_default_ou
|
||
from authentic2.apps.journal.views import JournalViewWithContext
|
||
from authentic2.forms.profile import modelform_factory
|
||
from authentic2.utils import crypto, hooks
|
||
from authentic2.utils.misc import redirect
|
||
|
||
from . import forms, resources, tables, views
|
||
from .journal_views import BaseJournalView
|
||
from .utils import has_show_username, label_from_role, label_from_user
|
||
|
||
User = get_user_model()
|
||
|
||
|
||
class RolesMixin:
|
||
service_roles = True
|
||
admin_roles = False
|
||
|
||
def get_queryset(self):
|
||
qs = super().get_queryset()
|
||
qs = qs.select_related('ou')
|
||
permission_ct = ContentType.objects.get_for_model(Permission)
|
||
ct_ct = ContentType.objects.get_for_model(ContentType)
|
||
ou_ct = ContentType.objects.get_for_model(OrganizationalUnit)
|
||
permission_qs = Permission.objects.filter(target_ct_id__in=[ct_ct.id, ou_ct.id]).values_list(
|
||
'id', flat=True
|
||
)
|
||
# only non role-admin roles, they are accessed through the
|
||
# RoleManager views
|
||
if not self.admin_roles:
|
||
qs = qs.filter(
|
||
Q(admin_scope_ct__isnull=True)
|
||
| Q(admin_scope_ct=permission_ct, admin_scope_id__in=permission_qs)
|
||
)
|
||
if not self.service_roles:
|
||
qs = qs.filter(service__isnull=True)
|
||
return qs
|
||
|
||
|
||
class RolesView(views.SearchOUMixin, views.HideOUColumnMixin, RolesMixin, views.BaseTableView):
|
||
template_name = 'authentic2/manager/roles.html'
|
||
model = Role
|
||
table_class = tables.RoleTable
|
||
search_form_class = forms.RoleSearchForm
|
||
permissions = ['a2_rbac.search_role']
|
||
title = _('Roles')
|
||
|
||
def get_queryset(self):
|
||
qs = super().get_queryset()
|
||
qs = qs.annotate(member_count=Count('members'))
|
||
return qs
|
||
|
||
def get_search_form_kwargs(self):
|
||
kwargs = super().get_search_form_kwargs()
|
||
kwargs['queryset'] = self.get_queryset()
|
||
return kwargs
|
||
|
||
|
||
listing = RolesView.as_view()
|
||
|
||
|
||
class RoleAddView(views.BaseAddView):
|
||
template_name = 'authentic2/manager/role_add.html'
|
||
model = Role
|
||
title = _('Add role')
|
||
success_view_name = 'a2-manager-role-members'
|
||
exclude_fields = ('slug',)
|
||
|
||
def get_initial(self):
|
||
initial = super().get_initial()
|
||
search_ou = self.request.GET.get('search-ou')
|
||
initial['ou'] = search_ou or get_default_ou()
|
||
return initial
|
||
|
||
def get_form_class(self):
|
||
form = forms.RoleEditForm
|
||
fields = [x for x in form.base_fields.keys() if x not in self.exclude_fields]
|
||
return modelform_factory(self.model, form=form, fields=fields)
|
||
|
||
def form_valid(self, form):
|
||
response = super().form_valid(form)
|
||
hooks.call_hooks(
|
||
'event', name='manager-add-role', user=self.request.user, instance=form.instance, form=form
|
||
)
|
||
self.request.journal.record('manager.role.creation', role=form.instance)
|
||
return response
|
||
|
||
|
||
add = RoleAddView.as_view()
|
||
|
||
|
||
class RolesExportView(views.ExportMixin, RolesView):
|
||
resource_class = resources.RoleResource
|
||
export_prefix = 'roles-export-'
|
||
|
||
def get(self, request, *args, **kwargs):
|
||
export_format = kwargs['format'].lower()
|
||
if export_format == 'json':
|
||
export = data_transfer.export_site(
|
||
data_transfer.ExportContext(
|
||
role_qs=self.get_table_data(), export_roles=True, export_ous=False
|
||
)
|
||
)
|
||
return self.export_response(json.dumps(export, indent=4), 'application/json', 'json')
|
||
return super().get(request, *args, **kwargs)
|
||
|
||
|
||
export = RolesExportView.as_view()
|
||
|
||
|
||
class RoleViewMixin(RolesMixin):
|
||
model = Role
|
||
|
||
def get_context_data(self, **kwargs):
|
||
ctx = super().get_context_data(**kwargs)
|
||
ctx['ou'] = self.get_object().ou
|
||
return ctx
|
||
|
||
|
||
class RoleEditView(RoleViewMixin, views.BaseEditView):
|
||
template_name = 'authentic2/manager/role_edit.html'
|
||
title = _('Edit role description')
|
||
|
||
def get_form_class(self):
|
||
return forms.RoleEditForm
|
||
|
||
def form_valid(self, form):
|
||
response = super().form_valid(form)
|
||
hooks.call_hooks(
|
||
'event', name='manager-edit-role', user=self.request.user, instance=form.instance, form=form
|
||
)
|
||
self.request.journal.record('manager.role.edit', role=form.instance, form=form)
|
||
return response
|
||
|
||
|
||
edit = RoleEditView.as_view()
|
||
|
||
|
||
class RoleMembersView(views.HideOUColumnMixin, RoleViewMixin, views.BaseSubTableView):
|
||
template_name = 'authentic2/manager/role_members.html'
|
||
form_class = forms.ChooseUserOrRoleForm
|
||
success_url = '.'
|
||
search_form_class = forms.RoleMembersSearchForm
|
||
permissions = ['a2_rbac.view_role']
|
||
slug_field = 'uuid'
|
||
admin_roles = True
|
||
|
||
@property
|
||
def table_class(self):
|
||
if self.view_all_members:
|
||
return tables.RoleMembersTable
|
||
return tables.MixedUserRoleTable
|
||
|
||
@property
|
||
def title(self):
|
||
return self.get_instance_name()
|
||
|
||
@property
|
||
def can_manage_members(self):
|
||
return self.object.can_manage_members and getattr(self, '_can_manage_members', False)
|
||
|
||
@can_manage_members.setter
|
||
def can_manage_members(self, value):
|
||
self._can_manage_members = value
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
self.children = views.filter_view(
|
||
self.request, self.get_object().children(include_self=False, annotate=True)
|
||
)
|
||
return super().dispatch(request, *args, **kwargs)
|
||
|
||
def get_table_data(self):
|
||
if self.view_all_members:
|
||
return super().get_table_data()
|
||
members = views.filter_view(self.request, self.object.members.all())
|
||
members = members.annotate(direct=Value(True, output_field=BooleanField()))
|
||
members = self.filter_by_search(members)
|
||
return list(self.children) + list(members)
|
||
|
||
def get_table_queryset(self):
|
||
via_prefetch = Prefetch('roles', queryset=self.children, to_attr='via')
|
||
return self.object.all_members().prefetch_related(via_prefetch)
|
||
|
||
@property
|
||
def view_all_members(self):
|
||
return self.search_form.is_valid() and self.search_form.cleaned_data.get('all_members')
|
||
|
||
def form_valid(self, form):
|
||
action = form.cleaned_data['action']
|
||
if not self.can_manage_members:
|
||
messages.warning(self.request, _('You are not authorized'))
|
||
elif 'user' in form.cleaned_data:
|
||
if action == 'add':
|
||
self.add_user(form.cleaned_data['user'])
|
||
elif action == 'remove':
|
||
self.remove_user(form.cleaned_data['user'])
|
||
elif 'role' in form.cleaned_data:
|
||
if action == 'add':
|
||
self.add_role(form.cleaned_data['role'])
|
||
elif action == 'remove':
|
||
self.remove_role(form.cleaned_data['role'])
|
||
return super().form_valid(form)
|
||
|
||
def add_user(self, user):
|
||
if self.object.members.filter(pk=user.pk).exists():
|
||
messages.warning(self.request, _('User already in this role.'))
|
||
else:
|
||
self.object.members.add(user)
|
||
hooks.call_hooks(
|
||
'event', name='manager-add-role-member', user=self.request.user, role=self.object, member=user
|
||
)
|
||
self.request.journal.record('manager.role.membership.grant', role=self.object, member=user)
|
||
|
||
def remove_user(self, user):
|
||
if not self.object.members.filter(pk=user.pk).exists():
|
||
messages.warning(self.request, _('User was not in this role.'))
|
||
else:
|
||
self.object.members.remove(user)
|
||
hooks.call_hooks(
|
||
'event',
|
||
name='manager-remove-role-member',
|
||
user=self.request.user,
|
||
role=self.object,
|
||
member=user,
|
||
)
|
||
self.request.journal.record('manager.role.membership.removal', role=self.object, member=user)
|
||
|
||
def add_role(self, role):
|
||
self.object.add_child(role)
|
||
hooks.call_hooks(
|
||
'event', name='manager-add-child-role', user=self.request.user, parent=self.object, child=role
|
||
)
|
||
self.request.journal.record('manager.role.inheritance.addition', parent=self.object, child=role)
|
||
|
||
def remove_role(self, role):
|
||
self.object.remove_child(role)
|
||
hooks.call_hooks(
|
||
'event', name='manager-remove-child-role', user=self.request.user, parent=self.object, child=role
|
||
)
|
||
self.request.journal.record('manager.role.inheritance.removal', parent=self.object, child=role)
|
||
|
||
def get_form_kwargs(self):
|
||
kwargs = super().get_form_kwargs()
|
||
kwargs['role'] = self.object
|
||
return kwargs
|
||
|
||
def get_search_form_kwargs(self):
|
||
kwargs = super().get_search_form_kwargs()
|
||
if not self.children:
|
||
kwargs['data']['search-all_members'] = 'on'
|
||
kwargs['disable_all_members'] = True
|
||
return kwargs
|
||
|
||
def get_context_data(self, **kwargs):
|
||
ctx = super().get_context_data(**kwargs)
|
||
ctx['parents'] = list(
|
||
views.filter_view(
|
||
self.request,
|
||
self.object.parents(include_self=False, annotate=True).order_by(
|
||
F('ou').asc(nulls_first=True), 'name'
|
||
),
|
||
)[:11]
|
||
)
|
||
ctx['has_multiple_ou'] = OrganizationalUnit.objects.count() > 1
|
||
ctx['admin_roles'] = views.filter_view(
|
||
self.request, self.object.get_admin_role().children(include_self=False, annotate=True)
|
||
)
|
||
ctx['from_ldap'] = self._can_manage_members and not self.can_manage_members
|
||
return ctx
|
||
|
||
def is_ou_specified(self):
|
||
return self.search_form.is_valid() and self.search_form.cleaned_data.get('ou')
|
||
|
||
def get_table(self, **kwargs):
|
||
show_username = has_show_username()
|
||
if not show_username and self.is_ou_specified():
|
||
show_username = self.is_ou_specified().show_username
|
||
if not show_username:
|
||
exclude = kwargs.setdefault('exclude', [])
|
||
if 'username' not in exclude:
|
||
exclude.append('username')
|
||
return super().get_table(**kwargs)
|
||
|
||
|
||
members = RoleMembersView.as_view()
|
||
|
||
|
||
class RoleDeleteView(RoleViewMixin, views.BaseDeleteView):
|
||
title = _('Delete role')
|
||
template_name = 'authentic2/manager/role_delete.html'
|
||
|
||
def post(self, request, *args, **kwargs):
|
||
if not self.can_delete:
|
||
raise PermissionDenied
|
||
return super().post(request, *args, **kwargs)
|
||
|
||
def get_success_url(self):
|
||
return reverse('a2-manager-roles')
|
||
|
||
def delete(self, request, *args, **kwargs):
|
||
role = self.get_object()
|
||
|
||
hooks.call_hooks('event', name='manager-delete-role', user=request.user, role=role)
|
||
self.request.journal.record('manager.role.deletion', role=role)
|
||
return super().delete(request, *args, **kwargs)
|
||
|
||
|
||
delete = RoleDeleteView.as_view()
|
||
|
||
|
||
class RoleMembersExportView(views.ExportMixin, RoleMembersView):
|
||
resource_class = resources.UserResource
|
||
permissions = ['a2_rbac.view_role']
|
||
|
||
def get_data(self):
|
||
return self.get_table_data()
|
||
|
||
|
||
members_export = RoleMembersExportView.as_view()
|
||
|
||
|
||
class RoleChildrenView(RoleViewMixin, views.HideOUColumnMixin, views.BaseSubTableView):
|
||
title = _('Add child role')
|
||
form_class = forms.ChooseRoleForm
|
||
table_class = tables.InheritanceRolesTable
|
||
search_form_class = forms.RoleSearchForm
|
||
template_name = 'authentic2/manager/roles_inheritance.html'
|
||
permissions = ['a2_rbac.manage_members_role']
|
||
success_url = '.'
|
||
slug_field = 'uuid'
|
||
|
||
def get_table_queryset(self):
|
||
qs = super().get_table_queryset()
|
||
qs = qs.exclude(pk=self.object.pk)
|
||
children = self.object.children(annotate=True, include_self=False)
|
||
children = children.annotate(is_direct=Cast('direct', output_field=BooleanField()))
|
||
qs = qs.annotate(
|
||
checked=ExpressionWrapper(Q(pk__in=children.filter(is_direct=True)), output_field=BooleanField())
|
||
)
|
||
qs = qs.annotate(
|
||
indeterminate=ExpressionWrapper(
|
||
Q(pk__in=children.filter(is_direct=False)), output_field=BooleanField()
|
||
)
|
||
)
|
||
rp_qs = RoleParenting.alive.filter(parent__in=children).annotate(name=F('parent__name'))
|
||
qs = qs.prefetch_related(Prefetch('parent_relation', queryset=rp_qs, to_attr='via'))
|
||
return qs
|
||
|
||
def form_valid(self, form):
|
||
role = form.cleaned_data['role']
|
||
action = form.cleaned_data['action']
|
||
if action == 'add':
|
||
self.object.add_child(role)
|
||
hooks.call_hooks(
|
||
'event', name='manager-add-child-role', user=self.request.user, parent=self.object, child=role
|
||
)
|
||
self.request.journal.record('manager.role.inheritance.addition', parent=self.object, child=role)
|
||
elif action == 'remove':
|
||
self.object.remove_child(role)
|
||
hooks.call_hooks(
|
||
'event',
|
||
name='manager-remove-child-role',
|
||
user=self.request.user,
|
||
parent=self.object,
|
||
child=role,
|
||
)
|
||
self.request.journal.record('manager.role.inheritance.removal', parent=self.object, child=role)
|
||
return super().form_valid(form)
|
||
|
||
def get_search_form_kwargs(self):
|
||
kwargs = super().get_search_form_kwargs()
|
||
kwargs['queryset'] = self.request.user.filter_by_perm('a2_rbac.view_role', Role.objects.all())
|
||
return kwargs
|
||
|
||
|
||
children = RoleChildrenView.as_view()
|
||
|
||
|
||
class RoleParentsView(RoleViewMixin, views.HideOUColumnMixin, views.BaseSubTableView):
|
||
title = _('Include permissions from roles')
|
||
form_class = forms.RoleParentForm
|
||
table_class = tables.InheritanceRolesTable
|
||
search_form_class = forms.RoleSearchForm
|
||
template_name = 'authentic2/manager/roles_inheritance.html'
|
||
success_url = '.'
|
||
slug_field = 'uuid'
|
||
|
||
@property
|
||
def admin_roles(self):
|
||
if not hasattr(self, 'search_form'):
|
||
return False
|
||
return self.search_form.cleaned_data.get('admin_roles', False)
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
if self.get_object().is_internal():
|
||
raise PermissionDenied
|
||
return super().dispatch(request, *args, **kwargs)
|
||
|
||
def get_table_queryset(self):
|
||
qs = super().get_table_queryset()
|
||
qs = self.request.user.filter_by_perm('a2_rbac.manage_members_role', qs)
|
||
qs = qs.exclude(pk=self.object.pk)
|
||
parents = self.object.parents(annotate=True, include_self=False)
|
||
parents = parents.annotate(is_direct=Cast('direct', output_field=BooleanField()))
|
||
qs = qs.annotate(
|
||
checked=ExpressionWrapper(Q(pk__in=parents.filter(is_direct=True)), output_field=BooleanField())
|
||
)
|
||
qs = qs.annotate(
|
||
indeterminate=ExpressionWrapper(
|
||
Q(pk__in=parents.filter(is_direct=False)), output_field=BooleanField()
|
||
)
|
||
)
|
||
rp_qs = RoleParenting.alive.filter(child__in=parents).annotate(name=F('child__name'))
|
||
qs = qs.prefetch_related(Prefetch('child_relation', queryset=rp_qs, to_attr='via'))
|
||
return qs
|
||
|
||
def form_valid(self, form):
|
||
role = form.cleaned_data['role']
|
||
action = form.cleaned_data['action']
|
||
if action == 'add':
|
||
self.object.add_parent(role)
|
||
hooks.call_hooks(
|
||
'event', name='manager-add-child-role', user=self.request.user, parent=role, child=self.object
|
||
)
|
||
self.request.journal.record('manager.role.inheritance.addition', parent=role, child=self.object)
|
||
elif action == 'remove':
|
||
self.object.remove_parent(role)
|
||
hooks.call_hooks(
|
||
'event',
|
||
name='manager-remove-child-role',
|
||
user=self.request.user,
|
||
parent=role,
|
||
child=self.object,
|
||
)
|
||
self.request.journal.record('manager.role.inheritance.removal', parent=role, child=self.object)
|
||
return super().form_valid(form)
|
||
|
||
def get_search_form_kwargs(self):
|
||
kwargs = super().get_search_form_kwargs()
|
||
kwargs['queryset'] = self.request.user.filter_by_perm(
|
||
'a2_rbac.manage_members_role', Role.objects.all()
|
||
)
|
||
return kwargs
|
||
|
||
|
||
parents = RoleParentsView.as_view()
|
||
|
||
|
||
class RoleAddAdminRoleView(
|
||
views.AjaxFormViewMixin,
|
||
views.TitleMixin,
|
||
views.PermissionMixin,
|
||
views.FormNeedsRequest,
|
||
SingleObjectMixin,
|
||
FormView,
|
||
):
|
||
title = _('Add admin role')
|
||
model = Role
|
||
form_class = forms.RolesForm
|
||
success_url = '..'
|
||
template_name = 'authentic2/manager/form.html'
|
||
permissions = ['a2_rbac.change_role']
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
self.object = self.get_object()
|
||
return super().dispatch(request, *args, **kwargs)
|
||
|
||
def form_valid(self, form):
|
||
administered_role = self.get_object()
|
||
for role in form.cleaned_data['roles']:
|
||
administered_role.get_admin_role().add_child(role)
|
||
hooks.call_hooks(
|
||
'event',
|
||
name='manager-add-admin-role',
|
||
user=self.request.user,
|
||
role=administered_role,
|
||
admin_role=role,
|
||
)
|
||
self.request.journal.record(
|
||
'manager.role.administrator.role.addition', role=administered_role, admin_role=role
|
||
)
|
||
return super().form_valid(form)
|
||
|
||
|
||
add_admin_role = RoleAddAdminRoleView.as_view()
|
||
|
||
|
||
class RoleRemoveAdminRoleView(
|
||
views.TitleMixin, views.AjaxFormViewMixin, SingleObjectMixin, views.PermissionMixin, TemplateView
|
||
):
|
||
title = _('Remove admin role')
|
||
model = Role
|
||
success_url = '../..'
|
||
template_name = 'authentic2/manager/role_remove_admin_role.html'
|
||
permissions = ['a2_rbac.change_role']
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
self.object = self.get_object()
|
||
self.child = self.get_queryset().get(pk=kwargs['role_pk'])
|
||
return super().dispatch(request, *args, **kwargs)
|
||
|
||
def get_context_data(self, **kwargs):
|
||
ctx = super().get_context_data(**kwargs)
|
||
ctx['child'] = self.child
|
||
return ctx
|
||
|
||
def post(self, request, *args, **kwargs):
|
||
self.object.get_admin_role().remove_child(self.child)
|
||
hooks.call_hooks(
|
||
'event',
|
||
name='manager-remove-admin-role',
|
||
user=self.request.user,
|
||
role=self.object,
|
||
admin_role=self.child,
|
||
)
|
||
self.request.journal.record(
|
||
'manager.role.administrator.role.removal', role=self.object, admin_role=self.child
|
||
)
|
||
return redirect(self.request, self.success_url)
|
||
|
||
|
||
remove_admin_role = RoleRemoveAdminRoleView.as_view()
|
||
|
||
|
||
class RoleAddAdminUserView(
|
||
views.AjaxFormViewMixin,
|
||
views.TitleMixin,
|
||
views.PermissionMixin,
|
||
views.FormNeedsRequest,
|
||
SingleObjectMixin,
|
||
FormView,
|
||
):
|
||
title = _('Add admin user')
|
||
model = Role
|
||
form_class = forms.UsersForm
|
||
success_url = '..'
|
||
template_name = 'authentic2/manager/form.html'
|
||
permissions = ['a2_rbac.change_role']
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
self.object = self.get_object()
|
||
return super().dispatch(request, *args, **kwargs)
|
||
|
||
def form_valid(self, form):
|
||
administered_role = self.get_object()
|
||
for user in form.cleaned_data['users']:
|
||
administered_role.get_admin_role().members.add(user)
|
||
hooks.call_hooks(
|
||
'event',
|
||
name='manager-add-admin-role-user',
|
||
user=self.request.user,
|
||
role=administered_role,
|
||
admin=user,
|
||
)
|
||
self.request.journal.record(
|
||
'manager.role.administrator.user.addition', role=administered_role, admin_user=user
|
||
)
|
||
return super().form_valid(form)
|
||
|
||
|
||
add_admin_user = RoleAddAdminUserView.as_view()
|
||
|
||
|
||
class RoleRemoveAdminUserView(
|
||
views.TitleMixin, views.AjaxFormViewMixin, SingleObjectMixin, views.PermissionMixin, TemplateView
|
||
):
|
||
title = _('Remove admin user')
|
||
model = Role
|
||
success_url = '../..'
|
||
template_name = 'authentic2/manager/role_remove_admin_user.html'
|
||
permissions = ['a2_rbac.change_role']
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
self.object = self.get_object()
|
||
self.user = get_user_model().objects.get(pk=kwargs['user_pk'])
|
||
return super().dispatch(request, *args, **kwargs)
|
||
|
||
def get_context_data(self, **kwargs):
|
||
ctx = super().get_context_data(**kwargs)
|
||
ctx['user'] = self.user
|
||
return ctx
|
||
|
||
def post(self, request, *args, **kwargs):
|
||
self.object.get_admin_role().members.remove(self.user)
|
||
hooks.call_hooks(
|
||
'event',
|
||
name='remove-remove-admin-role-user',
|
||
user=self.request.user,
|
||
role=self.object,
|
||
admin=self.user,
|
||
)
|
||
self.request.journal.record(
|
||
'manager.role.administrator.user.removal', role=self.object, admin_user=self.user
|
||
)
|
||
return redirect(self.request, self.success_url)
|
||
|
||
|
||
remove_admin_user = RoleRemoveAdminUserView.as_view()
|
||
|
||
|
||
class RolesImportView(
|
||
views.PermissionMixin, views.TitleMixin, views.MediaMixin, views.FormNeedsRequest, FormView
|
||
):
|
||
form_class = forms.RolesImportForm
|
||
model = Role
|
||
template_name = 'authentic2/manager/import_form.html'
|
||
title = _('Roles Import')
|
||
|
||
def get_initial(self):
|
||
initial = super().get_initial()
|
||
search_ou = self.request.GET.get('search-ou')
|
||
if search_ou:
|
||
initial['ou'] = search_ou
|
||
return initial
|
||
|
||
def post(self, request, *args, **kwargs):
|
||
if not self.can_add:
|
||
raise PermissionDenied
|
||
return super().post(request, *args, **kwargs)
|
||
|
||
def form_valid(self, form):
|
||
self.ou = form.cleaned_data['ou']
|
||
try:
|
||
context = data_transfer.ImportContext(
|
||
import_ous=False, set_ou=self.ou, allowed_ous=set(form.fields['ou'].queryset)
|
||
)
|
||
with transaction.atomic():
|
||
data_transfer.import_site(form.cleaned_data['site_json'], context)
|
||
except ValidationError as e:
|
||
form.add_error('site_json', e)
|
||
return self.form_invalid(form)
|
||
|
||
return super().form_valid(form)
|
||
|
||
def get_success_url(self):
|
||
if self.ou:
|
||
message = _('Roles have been successfully imported inside "%s" organizational unit.') % self.ou
|
||
querystring = '?search-ou=%s' % self.ou.pk
|
||
else:
|
||
message = _('Roles have been successfully imported.')
|
||
querystring = ''
|
||
|
||
messages.success(self.request, message)
|
||
return reverse('a2-manager-roles') + querystring
|
||
|
||
|
||
roles_import = RolesImportView.as_view()
|
||
|
||
|
||
class RolesCsvImportView(
|
||
views.PermissionMixin, views.TitleMixin, views.MediaMixin, views.FormNeedsRequest, FormView
|
||
):
|
||
form_class = forms.RolesCsvImportForm
|
||
model = Role
|
||
template_name = 'authentic2/manager/roles_csv_import_form.html'
|
||
title = _('Roles CSV Import')
|
||
|
||
def get_initial(self):
|
||
initial = super().get_initial()
|
||
search_ou = self.request.GET.get('search-ou')
|
||
if search_ou:
|
||
initial['ou'] = search_ou
|
||
return initial
|
||
|
||
def post(self, request, *args, **kwargs):
|
||
if not self.can_add:
|
||
raise PermissionDenied
|
||
return super().post(request, *args, **kwargs)
|
||
|
||
def form_valid(self, form):
|
||
self.ou = form.cleaned_data['ou']
|
||
for role in form.roles:
|
||
role.save()
|
||
return super().form_valid(form)
|
||
|
||
def get_success_url(self):
|
||
messages.success(
|
||
self.request,
|
||
_('Roles have been successfully imported inside "%s" organizational unit.') % self.ou,
|
||
)
|
||
return reverse('a2-manager-roles') + '?search-ou=%s' % self.ou.pk
|
||
|
||
|
||
roles_csv_import = RolesCsvImportView.as_view()
|
||
|
||
|
||
class RolesCsvImportSampleView(TemplateView):
|
||
template_name = 'authentic2/manager/sample_roles.txt'
|
||
content_type = 'text/csv'
|
||
|
||
|
||
roles_csv_import_sample = RolesCsvImportSampleView.as_view()
|
||
|
||
|
||
class RoleJournal(views.PermissionMixin, JournalViewWithContext, BaseJournalView):
|
||
template_name = 'authentic2/manager/role_journal.html'
|
||
permissions = ['a2_rbac.view_role']
|
||
title = _('Journal')
|
||
|
||
@cached_property
|
||
def context(self):
|
||
return get_object_or_404(Role, 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 = RoleJournal.as_view()
|
||
|
||
|
||
class RolesJournal(views.SearchOUMixin, views.PermissionMixin, JournalViewWithContext, BaseJournalView):
|
||
template_name = 'authentic2/manager/roles_journal.html'
|
||
permissions = ['a2_rbac.view_role']
|
||
title = _('Journal')
|
||
|
||
@cached_property
|
||
def context(self):
|
||
return Role
|
||
|
||
|
||
roles_journal = RolesJournal.as_view()
|
||
|
||
|
||
class UserOrRoleSelect2View(DetailView):
|
||
form_class = forms.ChooseUserOrRoleForm
|
||
model = Role
|
||
|
||
def get(self, request, *args, **kwargs):
|
||
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'filter_by_perm'):
|
||
raise Http404('Invalid user')
|
||
|
||
role = self.get_object()
|
||
|
||
field_id = self.kwargs.get('field_id', self.request.GET.get('field_id', None))
|
||
try:
|
||
crypto.loads(field_id)
|
||
except (crypto.SignatureExpired, crypto.BadSignature):
|
||
raise Http404('Invalid or expired signature.')
|
||
|
||
search_term = request.GET.get('term', '')
|
||
try:
|
||
page_number = int(request.GET.get('page', 1))
|
||
except ValueError:
|
||
page_number = 1
|
||
|
||
role_qs = self.form_class.get_role_queryset(self.request.user, role)
|
||
children = role.children(annotate=True)
|
||
children = children.annotate(is_direct=Cast('direct', output_field=BooleanField()))
|
||
role_qs = role_qs.exclude(pk__in=children.filter(is_direct=True))
|
||
role_qs = self.filter_queryset(role_qs, search_term, ['name', 'service__name', 'ou__name'])
|
||
|
||
role_paginator = Paginator(role_qs, 10)
|
||
try:
|
||
role_page = role_paginator.page(page_number)
|
||
except EmptyPage:
|
||
role_page = []
|
||
has_next = False
|
||
else:
|
||
has_next = role_page.has_next()
|
||
|
||
user_page = []
|
||
if not has_next:
|
||
user_qs = self.form_class.get_user_queryset(self.request.user, role)
|
||
user_qs = user_qs.exclude(roles=role)
|
||
user_qs = self.filter_queryset(
|
||
user_qs, search_term, ['username', 'first_name', 'last_name', 'email']
|
||
)
|
||
|
||
page_number = page_number - role_paginator.num_pages + 1
|
||
user_paginator = Paginator(user_qs, 10)
|
||
try:
|
||
user_page = user_paginator.page(page_number)
|
||
except EmptyPage:
|
||
has_next = False
|
||
else:
|
||
has_next = user_page.has_next()
|
||
|
||
return JsonResponse(
|
||
{
|
||
'results': [self.get_choice(obj) for obj in list(role_page) + list(user_page)],
|
||
'more': has_next,
|
||
}
|
||
)
|
||
|
||
@staticmethod
|
||
def filter_queryset(qs, search_term, search_fields):
|
||
lookups = Q()
|
||
for term in [term for term in search_term.split() if not term == '']:
|
||
lookups &= reduce(Q.__or__, (Q(**{'%s__icontains' % field: term}) for field in search_fields))
|
||
return qs.filter(lookups)
|
||
|
||
def get_choice(self, obj):
|
||
if isinstance(obj, Role):
|
||
text = label_from_role(obj)
|
||
key = 'role-%s'
|
||
elif isinstance(obj, User):
|
||
text = label_from_user(obj)
|
||
key = 'user-%s'
|
||
return {'id': key % obj.pk, 'text': text}
|
||
|
||
|
||
user_or_role_select2 = UserOrRoleSelect2View.as_view()
|