Add ArrayField on ObjectPermission to store actions

This commit is contained in:
Jeremy Stretch 2020-05-29 11:18:22 -04:00
parent 90828cedae
commit 02687453f2
7 changed files with 120 additions and 78 deletions

View File

@ -203,7 +203,7 @@ class ObjectPermissionViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_view=True actions=['view']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -227,7 +227,7 @@ class ObjectPermissionViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_view=True actions=['view']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -261,8 +261,7 @@ class ObjectPermissionViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_view=True, actions=['view', 'add']
can_add=True
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -309,8 +308,7 @@ class ObjectPermissionViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_view=True, actions=['view', 'change']
can_change=True
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -353,8 +351,7 @@ class ObjectPermissionViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_view=True, actions=['view', 'delete']
can_delete=True
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -402,7 +399,7 @@ class ObjectPermissionViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_add=True actions=['add']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -451,7 +448,7 @@ class ObjectPermissionViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_change=True actions=['change']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -495,8 +492,7 @@ class ObjectPermissionViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_view=True, actions=['view', 'delete']
can_delete=True
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -567,7 +563,7 @@ class ObjectPermissionAPIViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_view=True actions=['view']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -594,7 +590,7 @@ class ObjectPermissionAPIViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_view=True actions=['view']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -621,7 +617,7 @@ class ObjectPermissionAPIViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_add=True actions=['add']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -650,7 +646,7 @@ class ObjectPermissionAPIViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_change=True actions=['change']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -685,7 +681,7 @@ class ObjectPermissionAPIViewTestCase(TestCase):
# Assign object permission # Assign object permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'site__name': 'Site 1'}, attrs={'site__name': 'Site 1'},
can_delete=True actions=['delete']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)

View File

@ -2,9 +2,10 @@ from django import forms
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as UserAdmin_ from django.contrib.auth.admin import UserAdmin as UserAdmin_
from django.contrib.auth.models import Group as StockGroup, User as StockUser from django.contrib.auth.models import Group as StockGroup, User as StockUser
from django.core.exceptions import FieldError, ValidationError
from extras.admin import order_content_types from extras.admin import order_content_types
from .models import Group, User, ObjectPermission, Token, UserConfig from .models import AdminGroup, AdminUser, ObjectPermission, Token, UserConfig
# #
@ -16,7 +17,7 @@ admin.site.unregister(StockGroup)
admin.site.unregister(StockUser) admin.site.unregister(StockUser)
@admin.register(Group) @admin.register(AdminGroup)
class GroupAdmin(admin.ModelAdmin): class GroupAdmin(admin.ModelAdmin):
fields = ('name',) fields = ('name',)
list_display = ('name', 'user_count') list_display = ('name', 'user_count')
@ -34,7 +35,7 @@ class UserConfigInline(admin.TabularInline):
verbose_name = 'Preferences' verbose_name = 'Preferences'
@admin.register(User) @admin.register(AdminUser)
class UserAdmin(UserAdmin_): class UserAdmin(UserAdmin_):
list_display = [ list_display = [
'username', 'email', 'first_name', 'last_name', 'is_superuser', 'is_staff', 'is_active' 'username', 'email', 'first_name', 'last_name', 'is_superuser', 'is_staff', 'is_active'
@ -92,10 +93,41 @@ class ObjectPermissionForm(forms.ModelForm):
order_content_types(self.fields['content_types']) order_content_types(self.fields['content_types'])
self.fields['content_types'].choices.insert(0, ('', '---------')) self.fields['content_types'].choices.insert(0, ('', '---------'))
def clean(self):
content_types = self.cleaned_data['content_types']
attrs = self.cleaned_data['attrs']
# Validate the specified model attributes by attempting to execute a query. We don't care whether the query
# returns anything; we just want to make sure the specified attributes are valid.
if attrs:
for ct in content_types:
model = ct.model_class()
try:
model.objects.filter(**attrs).exists()
except FieldError as e:
raise ValidationError({
'attrs': f'Invalid attributes for {model}: {e}'
})
@admin.register(ObjectPermission) @admin.register(ObjectPermission)
class ObjectPermissionAdmin(admin.ModelAdmin): class ObjectPermissionAdmin(admin.ModelAdmin):
form = ObjectPermissionForm form = ObjectPermissionForm
list_display = [ list_display = [
'model', 'can_view', 'can_add', 'can_change', 'can_delete' 'list_models', 'list_users', 'list_groups', 'actions', 'attrs',
] ]
def get_queryset(self, request):
return super().get_queryset(request).prefetch_related('content_types', 'users', 'groups')
def list_models(self, obj):
return ', '.join([f"{ct}" for ct in obj.content_types.all()])
list_models.short_description = 'Models'
def list_users(self, obj):
return ', '.join([u.username for u in obj.users.all()])
list_users.short_description = 'Users'
def list_groups(self, obj):
return ', '.join([g.name for g in obj.groups.all()])
list_groups.short_description = 'Groups'

View File

@ -14,7 +14,7 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Group', name='AdminGroup',
fields=[ fields=[
], ],
options={ options={
@ -28,7 +28,7 @@ class Migration(migrations.Migration):
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='User', name='AdminUser',
fields=[ fields=[
], ],
options={ options={

View File

@ -0,0 +1,33 @@
# Generated by Django 3.0.6 on 2020-05-29 14:59
from django.conf import settings
import django.contrib.postgres.fields
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('auth', '0011_update_proxy_permissions'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('users', '0007_proxy_group_user'),
]
operations = [
migrations.CreateModel(
name='ObjectPermission',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('attrs', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)),
('actions', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=30), size=None)),
('content_types', models.ManyToManyField(limit_choices_to={'app_label__in': ['circuits', 'dcim', 'extras', 'ipam', 'secrets', 'tenancy', 'virtualization']}, related_name='object_permissions', to='contenttypes.ContentType')),
('groups', models.ManyToManyField(blank=True, related_name='object_permissions', to='auth.Group')),
('users', models.ManyToManyField(blank=True, related_name='object_permissions', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Permission',
},
),
]

View File

@ -1,10 +1,9 @@
import binascii import binascii
import os import os
from django.contrib.auth.models import Group as Group_, User as User_ from django.contrib.auth.models import Group, User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import ArrayField, JSONField
from django.core.exceptions import FieldError, ValidationError
from django.core.validators import MinLengthValidator from django.core.validators import MinLengthValidator
from django.db import models from django.db import models
from django.db.models.signals import post_save from django.db.models.signals import post_save
@ -25,7 +24,7 @@ __all__ = (
# Proxy models for admin # Proxy models for admin
# #
class Group(Group_): class AdminGroup(Group):
""" """
Proxy contrib.auth.models.Group for the admin UI Proxy contrib.auth.models.Group for the admin UI
""" """
@ -33,7 +32,7 @@ class Group(Group_):
proxy = True proxy = True
class User(User_): class AdminUser(User):
""" """
Proxy contrib.auth.models.User for the admin UI Proxy contrib.auth.models.User for the admin UI
""" """
@ -256,31 +255,13 @@ class ObjectPermission(models.Model):
null=True, null=True,
verbose_name='Attributes' verbose_name='Attributes'
) )
can_view = models.BooleanField( actions = ArrayField(
default=False base_field=models.CharField(max_length=30),
) help_text="The list of actions granted by this permission"
can_add = models.BooleanField(
default=False
)
can_change = models.BooleanField(
default=False
)
can_delete = models.BooleanField(
default=False
) )
class Meta:
verbose_name = "Permission"
def __str__(self): def __str__(self):
return "Object permission" return "Object permission"
def clean(self):
# Validate the specified model attributes by attempting to execute a query. We don't care whether the query
# returns anything; we just want to make sure the specified attributes are valid.
if self.attrs:
model = self.model.model_class()
try:
model.objects.filter(**self.attrs).exists()
except FieldError as e:
raise ValidationError({
'attrs': f'Invalid attributes for {model}: {e}'
})

View File

@ -32,13 +32,12 @@ class ObjectPermissionBackend(ModelBackend):
perms = dict() perms = dict()
for obj_perm in object_permissions: for obj_perm in object_permissions:
for content_type in obj_perm.content_types.all(): for content_type in obj_perm.content_types.all():
for action in ['view', 'add', 'change', 'delete']: for action in obj_perm.actions:
if getattr(obj_perm, f"can_{action}"): perm_name = f"{content_type.app_label}.{action}_{content_type.model}"
perm_name = f"{content_type.app_label}.{action}_{content_type.model}" if perm_name in perms:
if perm_name in perms: perms[perm_name].append(obj_perm.attrs)
perms[perm_name].append(obj_perm.attrs) else:
else: perms[perm_name] = [obj_perm.attrs]
perms[perm_name] = [obj_perm.attrs]
return perms return perms
@ -123,7 +122,8 @@ class RemoteUserBackend(_RemoteUserBackend):
for permission_name in settings.REMOTE_AUTH_DEFAULT_PERMISSIONS: for permission_name in settings.REMOTE_AUTH_DEFAULT_PERMISSIONS:
try: try:
content_type, action = resolve_permission(permission_name) content_type, action = resolve_permission(permission_name)
obj_perm = ObjectPermission(**{f'can_{action}': True}) # TODO: Merge multiple actions into a single ObjectPermission per content type
obj_perm = ObjectPermission(actions=[action])
obj_perm.save() obj_perm.save()
obj_perm.users.add(user) obj_perm.users.add(user)
obj_perm.content_types.add(content_type) obj_perm.content_types.add(content_type)

View File

@ -34,7 +34,7 @@ class TestCase(_TestCase):
""" """
for name in names: for name in names:
ct, action = resolve_permission(name) ct, action = resolve_permission(name)
obj_perm = ObjectPermission(**{f'can_{action}': True}) obj_perm = ObjectPermission(actions=[action])
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
obj_perm.content_types.add(ct) obj_perm.content_types.add(ct)
@ -165,7 +165,7 @@ class ViewTestCases:
# Add model-level permission # Add model-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
can_view=True actions=['view']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -181,7 +181,7 @@ class ViewTestCases:
# Add object-level permission # Add object-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'pk': instance1.pk}, attrs={'pk': instance1.pk},
can_view=True actions=['view']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -221,7 +221,7 @@ class ViewTestCases:
# Assign model-level permission # Assign model-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
can_add=True actions=['add']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -246,7 +246,7 @@ class ViewTestCases:
# Assign object-level permission # Assign object-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'pk__gt': 0}, # Dummy permission to allow all attrs={'pk__gt': 0}, # Dummy permission to allow all
can_add=True actions=['add']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -305,7 +305,7 @@ class ViewTestCases:
# Assign model-level permission # Assign model-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
can_change=True actions=['change']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -329,7 +329,7 @@ class ViewTestCases:
# Assign object-level permission # Assign object-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'pk': instance1.pk}, attrs={'pk': instance1.pk},
can_change=True actions=['change']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -382,7 +382,7 @@ class ViewTestCases:
# Assign model-level permission # Assign model-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
can_delete=True actions=['delete']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -407,7 +407,7 @@ class ViewTestCases:
# Assign object-level permission # Assign object-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'pk': instance1.pk}, attrs={'pk': instance1.pk},
can_delete=True actions=['delete']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -459,7 +459,7 @@ class ViewTestCases:
# Add model-level permission # Add model-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
can_view=True actions=['view']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -481,7 +481,7 @@ class ViewTestCases:
# Add object-level permission # Add object-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'pk': instance1.pk}, attrs={'pk': instance1.pk},
can_view=True actions=['view']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -512,7 +512,7 @@ class ViewTestCases:
self.assertHttpStatus(self.client.post(**request), 403) self.assertHttpStatus(self.client.post(**request), 403)
# Assign object-level permission # Assign object-level permission
obj_perm = ObjectPermission(can_add=True) obj_perm = ObjectPermission(actions=['add'])
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
obj_perm.content_types.add(ContentType.objects.get_for_model(self.model)) obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
@ -557,7 +557,7 @@ class ViewTestCases:
# Assign model-level permission # Assign model-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
can_add=True actions=['add']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -580,7 +580,7 @@ class ViewTestCases:
# Assign object-level permission # Assign object-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'pk__gt': 0}, # Dummy permission to allow all attrs={'pk__gt': 0}, # Dummy permission to allow all
can_add=True actions=['add']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -627,7 +627,7 @@ class ViewTestCases:
# Assign model-level permission # Assign model-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
can_change=True actions=['change']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -652,7 +652,7 @@ class ViewTestCases:
# Assign object-level permission # Assign object-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'pk__in': list(pk_list)}, attrs={'pk__in': list(pk_list)},
can_change=True actions=['change']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -697,7 +697,7 @@ class ViewTestCases:
# Assign model-level permission # Assign model-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
can_delete=True actions=['delete']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
@ -719,7 +719,7 @@ class ViewTestCases:
# Assign object-level permission # Assign object-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
attrs={'pk__in': list(pk_list)}, attrs={'pk__in': list(pk_list)},
can_delete=True actions=['delete']
) )
obj_perm.save() obj_perm.save()
obj_perm.users.add(self.user) obj_perm.users.add(self.user)