From 37099abb8a18cdfcb02745a921efb0220b356e7e Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Wed, 11 Dec 2019 17:03:52 +0100 Subject: [PATCH] base: grant endpoint permissions using roles (#38365) --- passerelle/base/forms.py | 19 +++++++--- .../migrations/0017_auto_20191211_1509.py | 35 +++++++++++++++++++ passerelle/base/models.py | 27 ++++++++++++-- passerelle/base/templatetags/passerelle.py | 5 ++- passerelle/base/urls.py | 7 +++- passerelle/base/views.py | 29 +++++++++++---- .../includes/access-rights-table.html | 11 ++++++ passerelle/utils/__init__.py | 13 +++++++ 8 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 passerelle/base/migrations/0017_auto_20191211_1509.py diff --git a/passerelle/base/forms.py b/passerelle/base/forms.py index 2ac0605d..d5afcc6a 100644 --- a/passerelle/base/forms.py +++ b/passerelle/base/forms.py @@ -1,6 +1,6 @@ from django import forms -from .models import ApiUser, AccessRight, AvailabilityParameters +from .models import ApiUser, AccessRight, RoleAccessRight, AvailabilityParameters class ApiUserForm(forms.ModelForm): @@ -9,10 +9,9 @@ class ApiUserForm(forms.ModelForm): exclude = [] -class AccessRightForm(forms.ModelForm): +class BaseAccessRightForm(forms.ModelForm): class Meta: - model = AccessRight - exclude = [] + abstract = True widgets = { 'codename': forms.HiddenInput(), 'resource_type': forms.HiddenInput(), @@ -20,6 +19,18 @@ class AccessRightForm(forms.ModelForm): } +class AccessRightForm(BaseAccessRightForm): + class Meta(BaseAccessRightForm.Meta): + model = AccessRight + fields = ['apiuser', 'codename', 'resource_type', 'resource_pk'] + + +class RoleAccessRightForm(BaseAccessRightForm): + class Meta(BaseAccessRightForm.Meta): + model = RoleAccessRight + fields = ['role', 'codename', 'resource_type', 'resource_pk'] + + class AvailabilityParametersForm(forms.ModelForm): class Meta: model = AvailabilityParameters diff --git a/passerelle/base/migrations/0017_auto_20191211_1509.py b/passerelle/base/migrations/0017_auto_20191211_1509.py new file mode 100644 index 00000000..c7296888 --- /dev/null +++ b/passerelle/base/migrations/0017_auto_20191211_1509.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2019-12-11 14:09 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('auth', '0008_alter_user_username_max_length'), + ('base', '0016_auto_20191002_1443'), + ] + + operations = [ + migrations.CreateModel( + name='RoleAccessRight', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('codename', models.CharField(max_length=100, verbose_name=b'codename')), + ('resource_pk', models.PositiveIntegerField()), + ('resource_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group', verbose_name='Role')), + ], + options={ + 'permissions': (('view_accessright', 'Can view access right'),), + }, + ), + migrations.AlterUniqueTogether( + name='roleaccessright', + unique_together=set([('codename', 'resource_type', 'resource_pk', 'role')]), + ), + ] diff --git a/passerelle/base/models.py b/passerelle/base/models.py index b143757e..6e54933f 100644 --- a/passerelle/base/models.py +++ b/passerelle/base/models.py @@ -12,6 +12,7 @@ import itertools from django.apps import apps from django.conf import settings +from django.contrib.auth.models import Group from django.core.exceptions import ValidationError, ObjectDoesNotExist, PermissionDenied from django.core.urlresolvers import reverse from django.db import connection, models, transaction @@ -565,23 +566,43 @@ class BaseResource(models.Model): exc_info=exc_info) -class AccessRight(models.Model): +class BaseAccessRight(models.Model): codename = models.CharField(max_length=100, verbose_name='codename') resource_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) resource_pk = models.PositiveIntegerField() resource = fields.GenericForeignKey('resource_type', 'resource_pk') - apiuser = models.ForeignKey(ApiUser, verbose_name=_('API User'), on_delete=models.CASCADE) class Meta: + abstract = True permissions = ( ('view_accessright', 'Can view access right'), ) + + +class AccessRight(BaseAccessRight): + apiuser = models.ForeignKey(ApiUser, verbose_name=_('API User'), on_delete=models.CASCADE) + + class Meta(BaseAccessRight.Meta): unique_together = ( ('codename', 'resource_type', 'resource_pk', 'apiuser'), ) def __unicode__(self): - return '%s (on %s <%s>) (for %s)' % (self.codename, self.resource_type, self.resource_pk, self.apiuser) + return '%s (on %s <%s>) (for %s)' % (self.codename, self.resource_type, + self.resource_pk, self.apiuser) + + +class RoleAccessRight(BaseAccessRight): + role = models.ForeignKey(Group, verbose_name=_('Role'), on_delete=models.CASCADE) + + class Meta(BaseAccessRight.Meta): + unique_together = ( + ('codename', 'resource_type', 'resource_pk', 'role'), + ) + + def __unicode__(self): + return '%s (on %s <%s>) (for role %s)' % (self.codename, self.resource_type, + self.resource_pk, self.role.name) class LoggingParameters(models.Model): diff --git a/passerelle/base/templatetags/passerelle.py b/passerelle/base/templatetags/passerelle.py index cd65bd1f..cc0ef7e9 100644 --- a/passerelle/base/templatetags/passerelle.py +++ b/passerelle/base/templatetags/passerelle.py @@ -29,7 +29,7 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.template.defaultfilters import stringfilter from passerelle.utils import get_trusted_services -from ..models import AccessRight, ResourceLog +from ..models import AccessRight, RoleAccessRight, ResourceLog register = template.Library() @@ -38,8 +38,11 @@ register = template.Library() def access_rights_table(context, resource, permission): resource_type = ContentType.objects.get_for_model(resource) rights = AccessRight.objects.filter(resource_type=resource_type, resource_pk=resource.id, codename=permission) + role_rights = RoleAccessRight.objects.filter(resource_type=resource_type, resource_pk=resource.id, + codename=permission) context['permission'] = permission context['access_rights_list'] = rights + context['role_access_rights_list'] = role_rights context['resource_type'] = resource_type.id context['resource_pk'] = resource.id context['trusted_services'] = get_trusted_services() diff --git a/passerelle/base/urls.py b/passerelle/base/urls.py index 33536a31..cef886a1 100644 --- a/passerelle/base/urls.py +++ b/passerelle/base/urls.py @@ -2,7 +2,8 @@ from django.conf.urls import url from .views import ApiUserCreateView, ApiUserUpdateView, ApiUserDeleteView, \ ApiUserListView, AccessRightDeleteView, AccessRightCreateView, \ - LoggingParametersUpdateView, ManageAvailabilityView + LoggingParametersUpdateView, ManageAvailabilityView, \ + RoleAccessRightDeleteView, RoleAccessRightCreateView access_urlpatterns = [ url(r'^$', ApiUserListView.as_view(), name='apiuser-list'), @@ -14,6 +15,10 @@ access_urlpatterns = [ name='access-right-remove'), url(r'^accessright/add/(?P[\w,-]+)/(?P[\w,-]+)/(?P[\w,-]+)/', AccessRightCreateView.as_view(), name='access-right-add'), + url(r'^(?P[\w,-]+)/roleremove$', RoleAccessRightDeleteView.as_view(), + name='role-access-right-remove'), + url(r'^roleaccessright/add/(?P[\w,-]+)/(?P[\w,-]+)/(?P[\w,-]+)/', + RoleAccessRightCreateView.as_view(), name='role-access-right-add'), url(r'logging/parameters/(?P[\w,-]+)/(?P[\w,-]+)/$', LoggingParametersUpdateView.as_view(), name='logging-parameters'), url(r'manage/availability/(?P[\w,-]+)/(?P[\w,-]+)/$', diff --git a/passerelle/base/views.py b/passerelle/base/views.py index c9470928..995f1692 100644 --- a/passerelle/base/views.py +++ b/passerelle/base/views.py @@ -30,8 +30,8 @@ from django.http import Http404 from django.utils.timezone import make_aware from django.utils.translation import ugettext_lazy as _ -from .models import ApiUser, AccessRight, LoggingParameters, ResourceStatus, Job -from .forms import ApiUserForm, AccessRightForm, AvailabilityParametersForm +from .models import ApiUser, AccessRight, RoleAccessRight, LoggingParameters, ResourceStatus, Job +from .forms import ApiUserForm, AccessRightForm, RoleAccessRightForm, AvailabilityParametersForm from ..views import GenericConnectorMixin from ..utils import get_trusted_services @@ -97,12 +97,11 @@ class ApiUserListView(ListView): return context -class AccessRightDeleteView(DeleteView): - model = AccessRight +class BaseAccessRightDeleteView(DeleteView): template_name = 'passerelle/manage/accessright_confirm_delete.html' def get_object(self): - object = super(AccessRightDeleteView, self).get_object() + object = super(BaseAccessRightDeleteView, self).get_object() self.resource = object.resource return object @@ -110,9 +109,15 @@ class AccessRightDeleteView(DeleteView): return self.resource.get_absolute_url() -class AccessRightCreateView(CreateView): +class AccessRightDeleteView(BaseAccessRightDeleteView): model = AccessRight - form_class = AccessRightForm + + +class RoleAccessRightDeleteView(BaseAccessRightDeleteView): + model = RoleAccessRight + + +class BaseAccessRightCreateView(CreateView): template_name = 'passerelle/manage/accessright_form.html' def get_initial(self): @@ -126,6 +131,16 @@ class AccessRightCreateView(CreateView): return self.object.resource.get_absolute_url() +class AccessRightCreateView(BaseAccessRightCreateView): + model = AccessRight + form_class = AccessRightForm + + +class RoleAccessRightCreateView(BaseAccessRightCreateView): + model = RoleAccessRight + form_class = RoleAccessRightForm + + class LoggingParametersUpdateView(FormView): template_name = 'passerelle/manage/logging_parameters_form.html' diff --git a/passerelle/templates/passerelle/includes/access-rights-table.html b/passerelle/templates/passerelle/includes/access-rights-table.html index 5c41442e..890d5e43 100644 --- a/passerelle/templates/passerelle/includes/access-rights-table.html +++ b/passerelle/templates/passerelle/includes/access-rights-table.html @@ -23,6 +23,16 @@ {% endif %} {% endfor %} +{% for object in role_access_rights_list %} + + {{ object.role.name }} + - + - + {% if perms.base.delete_accessright %} + + {% endif %} + +{% endfor %} {% for trusted_service in trusted_services %} {{ trusted_service.title }} ({{ trusted_service.verif_orig }}) @@ -37,5 +47,6 @@ {% if perms.base.add_accessright %}

{% trans 'Add' %} +{% trans 'Add role' %}

{% endif %} diff --git a/passerelle/utils/__init__.py b/passerelle/utils/__init__.py index 582e34ec..f79cdcda 100644 --- a/passerelle/utils/__init__.py +++ b/passerelle/utils/__init__.py @@ -121,12 +121,25 @@ def is_trusted(request): return False +def has_perm(user, obj, resource_type, perm): + from passerelle.base.models import RoleAccessRight + + if user.is_anonymous: + return False + rights = RoleAccessRight.objects.filter(resource_type=resource_type, + resource_pk=obj.id, codename=perm) + role_ids = [x.role.id for x in rights] + return user.groups.filter(id__in=role_ids).exists() + + def is_authorized(request, obj, perm): from passerelle.base.models import AccessRight if is_trusted(request): return True resource_type = ContentType.objects.get_for_model(obj) + if not request.user.is_anonymous and has_perm(request.user, obj, resource_type, perm): + return True rights = AccessRight.objects.filter(resource_type=resource_type, resource_pk=obj.id, codename=perm) users = [x.apiuser for x in rights] return set(users).intersection(get_request_users(request)) -- 2.20.1