Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guardian setup #750

Merged
merged 5 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions server/apps/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from apps.profile.models import Profile, RcrainfoProfile, RcrainfoSiteAccess

from .models import Permission, Role, TrakUser
from .models import GroupPermission, TrakUser, UserPermission


class HiddenListView(admin.ModelAdmin):
Expand Down Expand Up @@ -73,5 +73,5 @@ def api_user(self, profile: RcrainfoProfile) -> bool:

admin.site.register(Profile)
admin.site.unregister(DRFToken)
admin.site.register(Permission)
admin.site.register(Role)
admin.site.register(UserPermission)
admin.site.register(GroupPermission)
47 changes: 47 additions & 0 deletions server/apps/core/migrations/0004_grouppermission_userpermission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Generated by Django 5.0.6 on 2024-07-22 19:25

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('contenttypes', '0002_remove_content_type_name'),
('core', '0003_role'),
]

operations = [
migrations.CreateModel(
name='GroupPermission',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_pk', models.CharField(max_length=255, verbose_name='object ID')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')),
('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.permission')),
],
options={
'abstract': False,
'indexes': [models.Index(fields=['content_type', 'object_pk'], name='core_groupp_content_9384ee_idx')],
'unique_together': {('group', 'permission', 'object_pk')},
},
),
migrations.CreateModel(
name='UserPermission',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_pk', models.CharField(max_length=255, verbose_name='object ID')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.permission')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
'indexes': [models.Index(fields=['content_type', 'object_pk'], name='core_userpe_content_92a909_idx')],
'unique_together': {('user', 'permission', 'object_pk')},
},
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.0.6 on 2024-07-22 20:03

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('core', '0004_grouppermission_userpermission'),
]

operations = [
migrations.RemoveField(
model_name='role',
name='permissions',
),
migrations.DeleteModel(
name='Permission',
),
migrations.DeleteModel(
name='Role',
),
]
55 changes: 17 additions & 38 deletions server/apps/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.contrib.auth.models import AbstractUser, Group
from django.contrib.auth.models import Permission as DjangoPermission
from django.db import models
from django.utils.translation import gettext_lazy as _
from guardian.models import GroupObjectPermissionAbstract, UserObjectPermissionAbstract


class TrakUser(AbstractUser):
Expand All @@ -21,44 +21,23 @@ class Meta:
)


class Permission(DjangoPermission):
"""Haztrak proxy permission model used for our custom object level permissions."""

class Meta:
proxy = True
verbose_name = "Permission"
verbose_name_plural = "Permissions"
ordering = ["name"]

@property
def app_label(self):
return self.content_type.app_label

@property
def model_name(self):
return self.content_type.model

def __str__(self):
return f"{self.content_type.name} | {self.name}"
class UserPermission(UserObjectPermissionAbstract):
"""
User object permission model for Haztrak.
access via guardian.utils.get_user_obj_perms_model()
We define this class if we need to customize User object level permissions later.
"""

class Meta(UserObjectPermissionAbstract.Meta):
abstract = False

class Role(models.Model):
"""A job/function within the system that can assigned to users to grant them permissions."""

class Meta:
verbose_name = _("Role")
verbose_name_plural = _("Roles")
ordering = ["name"]

name = models.CharField(
_("name"),
max_length=150,
unique=True,
)
permissions = models.ManyToManyField(
Permission,
verbose_name=_("permissions"),
)
class GroupPermission(GroupObjectPermissionAbstract):
"""
Group object permission model for Haztrak.
access via guardian.utils get_group_obj_perms_model()
We define this class if we need to customize Group object level permissions later.
"""

def __str__(self):
return f"{self.name}"
class Meta(GroupObjectPermissionAbstract.Meta):
abstract = False
22 changes: 0 additions & 22 deletions server/apps/core/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
from typing import Dict, List, Optional

import pytest
from django.contrib.contenttypes.models import ContentType
from faker import Faker

from apps.core.models import Permission
from apps.rcrasite.models import RcraSiteType


Expand Down Expand Up @@ -42,23 +40,3 @@ def create_quicker_sign(
}

return create_quicker_sign


@pytest.fixture
def permission_factory(faker: Faker):
"""
Factory for creating dynamic permission data
"""

def create_permission(
name: str = faker.word(),
content_type_id: int = faker.random_int(min=1),
) -> Permission:
content_type = ContentType.objects.create(app_label=faker.word(), model=faker.word())
return Permission.objects.create(
name=name,
content_type=content_type,
content_type_id=content_type_id,
)

return create_permission
42 changes: 0 additions & 42 deletions server/apps/core/tests/test_models.py

This file was deleted.

23 changes: 22 additions & 1 deletion server/apps/org/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from django.contrib import admin
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from guardian.admin import GuardedModelAdmin

from apps.org.models import Org, OrgAccess
from apps.org.models import Org, OrgAccess, OrgGroupObjectPermission, OrgUserObjectPermission
from apps.site.models import Site

admin.site.register(OrgAccess)
Expand All @@ -19,3 +22,21 @@ def rcrainfo_integrated(self, obj):

def number_of_sites(self, org: Org):
return Site.objects.filter(org=org).count()


@admin.register(OrgUserObjectPermission)
class OrgUserObjectPermissionAdmin(GuardedModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "permission":
content_type = ContentType.objects.get_for_model(Site)
kwargs["queryset"] = Permission.objects.filter(content_type=content_type)
return super().formfield_for_foreignkey(db_field, request, **kwargs)


@admin.register(OrgGroupObjectPermission)
class OrgGroupObjectPermissionAdmin(GuardedModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "permission":
content_type = ContentType.objects.get_for_model(Site)
kwargs["queryset"] = Permission.objects.filter(content_type=content_type)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by Django 5.0.6 on 2024-07-22 19:25

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('org', '0002_rename_trakorg_org_rename_trakorgaccess_orgaccess'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='OrgGroupObjectPermission',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='org.org')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')),
('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.permission')),
],
options={
'abstract': False,
'unique_together': {('group', 'permission', 'content_object')},
},
),
migrations.CreateModel(
name='OrgUserObjectPermission',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='org.org')),
('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.permission')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
'unique_together': {('user', 'permission', 'content_object')},
},
),
]
13 changes: 13 additions & 0 deletions server/apps/org/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.conf import settings
from django.db import models
from guardian.models.models import GroupObjectPermissionBase, UserObjectPermissionBase

from apps.profile.models import RcrainfoProfile

Expand Down Expand Up @@ -86,3 +87,15 @@ class Meta:

def __str__(self):
return f"{self.user} - {self.org}"


class OrgUserObjectPermission(UserObjectPermissionBase):
"""Org object level permission."""

content_object = models.ForeignKey(Org, on_delete=models.CASCADE)


class OrgGroupObjectPermission(GroupObjectPermissionBase):
"""Org object level Group."""

content_object = models.ForeignKey(Org, on_delete=models.CASCADE)
23 changes: 22 additions & 1 deletion server/apps/site/admin.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
from django.contrib import admin
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from guardian.admin import GuardedModelAdmin

from apps.site.models import Site
from apps.site.models import Site, SiteGroupObjectPermission, SiteUserObjectPermission


@admin.register(Site)
class HaztrakSiteAdmin(admin.ModelAdmin):
list_display = ["__str__", "last_rcrainfo_manifest_sync"]
readonly_fields = ["rcra_site"]
search_fields = ["rcra_site__epa_id"]


@admin.register(SiteUserObjectPermission)
class SiteUserObjectPermissionAdmin(GuardedModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "permission":
content_type = ContentType.objects.get_for_model(Site)
kwargs["queryset"] = Permission.objects.filter(content_type=content_type)
return super().formfield_for_foreignkey(db_field, request, **kwargs)


@admin.register(SiteGroupObjectPermission)
class SiteGroupObjectPermissionAdmin(GuardedModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "permission":
content_type = ContentType.objects.get_for_model(Site)
kwargs["queryset"] = Permission.objects.filter(content_type=content_type)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
Loading
Loading