556 lines
22 KiB
Python
556 lines
22 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 django.core.exceptions import PermissionDenied, ValidationError
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from django.urls import reverse
|
|
from django.views.generic import FormView, TemplateView
|
|
from django.views.generic.detail import SingleObjectMixin
|
|
from django.contrib import messages
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.db import transaction
|
|
from django.db.models.query import Q, Prefetch
|
|
from django.db.models import Count, F
|
|
from django.contrib.auth import get_user_model
|
|
|
|
from django_rbac.utils import get_role_model, get_permission_model, get_ou_model
|
|
|
|
from authentic2.utils import redirect
|
|
from authentic2 import hooks, data_transfer
|
|
|
|
from . import tables, views, resources, forms, app_settings
|
|
from .utils import has_show_username
|
|
|
|
|
|
class RolesMixin(object):
|
|
service_roles = True
|
|
admin_roles = False
|
|
|
|
def get_queryset(self):
|
|
qs = super(RolesMixin, self).get_queryset()
|
|
qs = qs.select_related('ou')
|
|
Permission = get_permission_model()
|
|
permission_ct = ContentType.objects.get_for_model(Permission)
|
|
ct_ct = ContentType.objects.get_for_model(ContentType)
|
|
ou_ct = ContentType.objects.get_for_model(get_ou_model())
|
|
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.HideOUColumnMixin, RolesMixin, views.BaseTableView):
|
|
template_name = 'authentic2/manager/roles.html'
|
|
model = get_role_model()
|
|
table_class = tables.RoleTable
|
|
search_form_class = forms.RoleSearchForm
|
|
permissions = ['a2_rbac.search_role']
|
|
title = _('Roles')
|
|
|
|
def get_queryset(self):
|
|
qs = super(RolesView, self).get_queryset()
|
|
qs = qs.annotate(member_count=Count('members'))
|
|
return qs
|
|
|
|
def get_search_form_kwargs(self):
|
|
kwargs = super(RolesView, self).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 = get_role_model()
|
|
title = _('Add role')
|
|
success_view_name = 'a2-manager-role-members'
|
|
|
|
def get_form_class(self):
|
|
return forms.get_role_form_class()
|
|
|
|
def form_valid(self, form):
|
|
response = super(RoleAddView, self).form_valid(form)
|
|
hooks.call_hooks('event', name='manager-add-role', user=self.request.user,
|
|
instance=form.instance, form=form)
|
|
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(RolesExportView, self).get(request, *args, **kwargs)
|
|
|
|
|
|
export = RolesExportView.as_view()
|
|
|
|
|
|
class RoleViewMixin(RolesMixin):
|
|
model = get_role_model()
|
|
|
|
|
|
class RoleEditView(RoleViewMixin, views.BaseEditView):
|
|
template_name = 'authentic2/manager/role_edit.html'
|
|
title = _('Edit role description')
|
|
|
|
def get_form_class(self):
|
|
return forms.get_role_form_class()
|
|
|
|
def form_valid(self, form):
|
|
response = super(RoleEditView, self).form_valid(form)
|
|
hooks.call_hooks('event', name='manager-edit-role', user=self.request.user,
|
|
instance=form.instance, form=form)
|
|
return response
|
|
|
|
edit = RoleEditView.as_view()
|
|
|
|
|
|
class RoleMembersView(views.HideOUColumnMixin, RoleViewMixin, views.BaseSubTableView):
|
|
template_name = 'authentic2/manager/role_members.html'
|
|
table_class = tables.RoleMembersTable
|
|
form_class = forms.ChooseUserForm
|
|
success_url = '.'
|
|
search_form_class = forms.UserSearchForm
|
|
permissions = ['a2_rbac.view_role']
|
|
|
|
@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 get_table_queryset(self):
|
|
children = self.object.children(include_self=False)
|
|
via_prefetch = Prefetch('roles', queryset=children, to_attr='via')
|
|
return self.object.all_members().prefetch_related(via_prefetch)
|
|
|
|
def form_valid(self, form):
|
|
user = form.cleaned_data['user']
|
|
action = form.cleaned_data['action']
|
|
if self.can_manage_members:
|
|
if action == 'add':
|
|
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)
|
|
elif action == 'remove':
|
|
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)
|
|
else:
|
|
messages.warning(self.request, _('You are not authorized'))
|
|
return super(RoleMembersView, self).form_valid(form)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super(RoleMembersView, self).get_form_kwargs()
|
|
# if role's members can only be from the same OU we filter user based on the role's OU
|
|
if app_settings.ROLE_MEMBERS_FROM_OU:
|
|
kwargs['ou'] = self.object.ou
|
|
return kwargs
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super(RoleMembersView, self).get_context_data(**kwargs)
|
|
ctx['children'] = views.filter_view(self.request,
|
|
self.object.children(include_self=False, annotate=True))
|
|
ctx['parents'] = views.filter_view(self.request, self.object.parents(
|
|
include_self=False, annotate=True).order_by(F('ou').asc(nulls_first=True), 'name'))
|
|
ctx['has_multiple_ou'] = get_ou_model().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(RoleDeleteView, self).post(request, *args, **kwargs)
|
|
|
|
def get_success_url(self):
|
|
return reverse('a2-manager-roles')
|
|
|
|
def form_valid(self, form):
|
|
response = super(RoleDeleteView, self).form_valid(form)
|
|
hooks.call_hooks('event', name='manager-delete-role', user=self.request.user,
|
|
role=form.instance)
|
|
return response
|
|
|
|
delete = RoleDeleteView.as_view()
|
|
|
|
|
|
class RolePermissionsView(RoleViewMixin, views.BaseSubTableView):
|
|
template_name = 'authentic2/manager/role_permissions.html'
|
|
table_class = tables.PermissionTable
|
|
form_class = forms.ChoosePermissionForm
|
|
success_url = '.'
|
|
permissions = ['a2_rbac.admin_permission']
|
|
title = _('Permissions')
|
|
|
|
def get_table_queryset(self):
|
|
return self.object.permissions.all()
|
|
|
|
def form_valid(self, form):
|
|
if self.can_change:
|
|
operation = form.cleaned_data.get('operation')
|
|
ou = form.cleaned_data.get('ou')
|
|
target = form.cleaned_data.get('target')
|
|
action = form.cleaned_data.get('action')
|
|
Permission = get_permission_model()
|
|
if action == 'add' and operation and target:
|
|
perm, created = Permission.objects \
|
|
.get_or_create(operation=operation, ou=ou,
|
|
target_ct=ContentType.objects.get_for_model(
|
|
target),
|
|
target_id=target.pk)
|
|
self.object.permissions.add(perm)
|
|
hooks.call_hooks('event', name='manager-add-permission', user=self.request.user,
|
|
role=self.object, permission=perm)
|
|
elif action == 'remove':
|
|
try:
|
|
permission_id = int(self.request.POST.get('permission', ''))
|
|
perm = Permission.objects.get(id=permission_id)
|
|
except (ValueError, Permission.DoesNotExist):
|
|
pass
|
|
else:
|
|
if self.object.permissions.filter(id=permission_id).exists():
|
|
self.object.permissions.remove(perm)
|
|
hooks.call_hooks('event', name='manager-remove-permission',
|
|
user=self.request.user, role=self.object, permission=perm)
|
|
else:
|
|
messages.warning(self.request, _('You are not authorized'))
|
|
return super(RolePermissionsView, self).form_valid(form)
|
|
|
|
permissions = RolePermissionsView.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 RoleAddChildView(views.AjaxFormViewMixin, views.TitleMixin,
|
|
views.PermissionMixin, views.FormNeedsRequest,
|
|
SingleObjectMixin, FormView):
|
|
title = _('Add child role')
|
|
model = get_role_model()
|
|
form_class = forms.RolesForm
|
|
success_url = '..'
|
|
template_name = 'authentic2/manager/form.html'
|
|
permissions = ['a2_rbac.manage_members_role']
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
return super(RoleAddChildView, self).dispatch(request, *args, **kwargs)
|
|
|
|
def form_valid(self, form):
|
|
parent = self.get_object()
|
|
for role in form.cleaned_data['roles']:
|
|
parent.add_child(role)
|
|
hooks.call_hooks('event', name='manager-add-child-role', user=self.request.user,
|
|
parent=parent, child=role)
|
|
return super(RoleAddChildView, self).form_valid(form)
|
|
|
|
add_child = RoleAddChildView.as_view()
|
|
|
|
|
|
class RoleAddParentView(views.AjaxFormViewMixin, views.TitleMixin,
|
|
views.FormNeedsRequest, SingleObjectMixin, FormView):
|
|
title = _('Add parent role')
|
|
model = get_role_model()
|
|
form_class = forms.RoleParentsForm
|
|
success_url = '..'
|
|
template_name = 'authentic2/manager/form.html'
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
if self.object.is_internal():
|
|
raise PermissionDenied
|
|
return super(RoleAddParentView, self).dispatch(request, *args, **kwargs)
|
|
|
|
def form_valid(self, form):
|
|
child = self.get_object()
|
|
for role in form.cleaned_data['roles']:
|
|
child.add_parent(role)
|
|
hooks.call_hooks('event', name='manager-add-child-role', user=self.request.user,
|
|
parent=role, child=child)
|
|
return super(RoleAddParentView, self).form_valid(form)
|
|
|
|
add_parent = RoleAddParentView.as_view()
|
|
|
|
|
|
class RoleRemoveChildView(views.AjaxFormViewMixin, SingleObjectMixin,
|
|
views.PermissionMixin, TemplateView):
|
|
title = _('Remove child role')
|
|
model = get_role_model()
|
|
success_url = '../..'
|
|
template_name = 'authentic2/manager/role_remove_child.html'
|
|
permissions = ['a2_rbac.manage_members_role']
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
self.child = self.get_queryset().get(pk=kwargs['child_pk'])
|
|
return super(RoleRemoveChildView, self).dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super(RoleRemoveChildView, self).get_context_data(**kwargs)
|
|
ctx['child'] = self.child
|
|
return ctx
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
self.object.remove_child(self.child)
|
|
hooks.call_hooks('event', name='manager-remove-child-role', user=self.request.user,
|
|
parent=self.object, child=self.child)
|
|
return redirect(self.request, self.success_url)
|
|
|
|
remove_child = RoleRemoveChildView.as_view()
|
|
|
|
|
|
class RoleRemoveParentView(views.AjaxFormViewMixin, SingleObjectMixin,
|
|
TemplateView):
|
|
title = _('Remove parent role')
|
|
model = get_role_model()
|
|
success_url = '../..'
|
|
template_name = 'authentic2/manager/role_remove_parent.html'
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
if self.object.is_internal():
|
|
raise PermissionDenied
|
|
self.parent = self.get_queryset().get(pk=kwargs['parent_pk'])
|
|
return super(RoleRemoveParentView, self).dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super(RoleRemoveParentView, self).get_context_data(**kwargs)
|
|
ctx['parent'] = self.parent
|
|
return ctx
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
if not self.request.user.has_perm('a2_rbac.manage_members_role', self.parent):
|
|
raise PermissionDenied
|
|
self.object.remove_parent(self.parent)
|
|
hooks.call_hooks('event', name='manager-remove-child-role', user=self.request.user,
|
|
parent=self.parent, child=self.object)
|
|
return redirect(self.request, self.success_url)
|
|
|
|
remove_parent = RoleRemoveParentView.as_view()
|
|
|
|
|
|
class RoleAddAdminRoleView(views.AjaxFormViewMixin, views.TitleMixin,
|
|
views.PermissionMixin, views.FormNeedsRequest,
|
|
SingleObjectMixin, FormView):
|
|
title = _('Add admin role')
|
|
model = get_role_model()
|
|
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(RoleAddAdminRoleView, self).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)
|
|
return super(RoleAddAdminRoleView, self).form_valid(form)
|
|
|
|
add_admin_role = RoleAddAdminRoleView.as_view()
|
|
|
|
|
|
class RoleRemoveAdminRoleView(views.TitleMixin, views.AjaxFormViewMixin,
|
|
SingleObjectMixin, views.PermissionMixin,
|
|
TemplateView):
|
|
title = _('Remove admin role')
|
|
model = get_role_model()
|
|
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(RoleRemoveAdminRoleView, self).dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super(RoleRemoveAdminRoleView, self).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)
|
|
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 = get_role_model()
|
|
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(RoleAddAdminUserView, self).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)
|
|
return super(RoleAddAdminUserView, self).form_valid(form)
|
|
|
|
add_admin_user = RoleAddAdminUserView.as_view()
|
|
|
|
|
|
class RoleRemoveAdminUserView(views.TitleMixin, views.AjaxFormViewMixin,
|
|
SingleObjectMixin, views.PermissionMixin,
|
|
TemplateView):
|
|
title = _('Remove admin user')
|
|
model = get_role_model()
|
|
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(RoleRemoveAdminUserView, self).dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super(RoleRemoveAdminUserView, self).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)
|
|
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 = get_role_model()
|
|
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)
|
|
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):
|
|
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_import = RolesImportView.as_view()
|