From e5067feda9958d6c388a4e993a1b036ea723d71c Mon Sep 17 00:00:00 2001 From: Emily <44536222+emi-hi@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:10:50 -0800 Subject: [PATCH] cthub-167 chore: replaces whitelisted users table with user, permissions, and user_permissions tables. updated code to reflect changes, only those with 'uploader' permission can upload datasets, using idir to identify users (#171) --- django/api/decorators/permission.py | 22 +++++++ django/api/decorators/whitelisted_users.py | 15 ----- django/api/fixtures/0009_add_permissions.json | 2 + django/api/keycloak_authentication.py | 2 +- .../api/migrations/0019_auto_20240223_1820.py | 60 +++++++++++++++++++ django/api/models/__init__.py | 4 +- django/api/models/permission.py | 15 +++++ .../models/{whitelisted_users.py => user.py} | 7 ++- django/api/models/user_permission.py | 18 ++++++ django/api/viewsets/minio.py | 5 +- django/api/viewsets/upload.py | 4 +- 11 files changed, 129 insertions(+), 25 deletions(-) create mode 100644 django/api/decorators/permission.py delete mode 100644 django/api/decorators/whitelisted_users.py create mode 100644 django/api/fixtures/0009_add_permissions.json create mode 100644 django/api/migrations/0019_auto_20240223_1820.py create mode 100644 django/api/models/permission.py rename django/api/models/{whitelisted_users.py => user.py} (55%) create mode 100644 django/api/models/user_permission.py diff --git a/django/api/decorators/permission.py b/django/api/decorators/permission.py new file mode 100644 index 00000000..f94256a0 --- /dev/null +++ b/django/api/decorators/permission.py @@ -0,0 +1,22 @@ +from rest_framework.response import Response +from rest_framework import status +from api.models.user import User +from api.models.user_permission import UserPermission +from api.models.permission import Permission +def check_upload_permission(): + def wrapper(func): + def wrapped(request, *args, **kwargs): + user = User.objects.filter(idir=request.user).first() + user_permission = UserPermission.objects.filter(user_id=user.id) + permissions = [] + if user_permission: + for each in user_permission: + permission = Permission.objects.get(id=each.permission_id) + permissions.append(permission.description) + if 'uploader' not in permissions: + return Response( + 'You do not have permission to upload data.', status=status.HTTP_403_FORBIDDEN + ) + return func(request, *args, **kwargs) + return wrapped + return wrapper diff --git a/django/api/decorators/whitelisted_users.py b/django/api/decorators/whitelisted_users.py deleted file mode 100644 index 35efa337..00000000 --- a/django/api/decorators/whitelisted_users.py +++ /dev/null @@ -1,15 +0,0 @@ -from rest_framework import exceptions -from api.models.whitelisted_users import WhitelistedUsers - -def check_whitelist(): - def wrapper(func): - def wrapped(request, *args, **kwargs): - user = request.user - whitelisted_users = WhitelistedUsers.objects.filter(user=user) - if not whitelisted_users: - raise exceptions.PermissionDenied( - 'You do not have permission to upload data.' - ) - return func(request, *args, **kwargs) - return wrapped - return wrapper diff --git a/django/api/fixtures/0009_add_permissions.json b/django/api/fixtures/0009_add_permissions.json new file mode 100644 index 00000000..b6be2b29 --- /dev/null +++ b/django/api/fixtures/0009_add_permissions.json @@ -0,0 +1,2 @@ +[{"model": "api.permission", "pk": 1, "fields": {"create_timestamp": "2024-02-22T00:00:00Z", "create_user": "user", "update_timestamp": null, "update_user": null, "description": "admin"}}, +{"model": "api.permission", "pk": 2, "fields": {"create_timestamp": "2024-02-22T00:00:00Z", "create_user": "user", "update_timestamp": null, "update_user": null, "description": "uploader"}}] diff --git a/django/api/keycloak_authentication.py b/django/api/keycloak_authentication.py index 2d1ded9a..0edbd69d 100644 --- a/django/api/keycloak_authentication.py +++ b/django/api/keycloak_authentication.py @@ -68,7 +68,7 @@ def authenticate(self, request): raise exceptions.AuthenticationFailed( 'Invalid Token' ) - return user_info.get('preferred_username'), None + return user_info.get('idir_username'), None # user = None # if 'user_id' not in user_info: diff --git a/django/api/migrations/0019_auto_20240223_1820.py b/django/api/migrations/0019_auto_20240223_1820.py new file mode 100644 index 00000000..c4c78315 --- /dev/null +++ b/django/api/migrations/0019_auto_20240223_1820.py @@ -0,0 +1,60 @@ +# Generated by Django 3.1.6 on 2024-02-23 18:20 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0018_auto_20231201_2301'), + ] + + operations = [ + migrations.CreateModel( + name='Permission', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_timestamp', models.DateTimeField(auto_now_add=True, null=True)), + ('create_user', models.CharField(default='SYSTEM', max_length=130)), + ('update_timestamp', models.DateTimeField(auto_now=True, null=True)), + ('update_user', models.CharField(max_length=130, null=True)), + ('description', models.CharField(max_length=100, unique=True)), + ], + options={ + 'db_table': 'permission', + }, + ), + migrations.CreateModel( + name='User', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_timestamp', models.DateTimeField(auto_now_add=True, null=True)), + ('create_user', models.CharField(default='SYSTEM', max_length=130)), + ('update_timestamp', models.DateTimeField(auto_now=True, null=True)), + ('update_user', models.CharField(max_length=130, null=True)), + ('idir', models.CharField(max_length=100, unique=True)), + ], + options={ + 'db_table': 'user', + }, + ), + migrations.CreateModel( + name='UserPermission', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_timestamp', models.DateTimeField(auto_now_add=True, null=True)), + ('create_user', models.CharField(default='SYSTEM', max_length=130)), + ('update_timestamp', models.DateTimeField(auto_now=True, null=True)), + ('update_user', models.CharField(max_length=130, null=True)), + ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='permission', to='api.permission')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user', to='api.user')), + ], + options={ + 'db_table': 'user_permission', + }, + ), + migrations.DeleteModel( + name='WhitelistedUsers', + ), + ] diff --git a/django/api/models/__init__.py b/django/api/models/__init__.py index d8c3a3e8..c9882bfa 100644 --- a/django/api/models/__init__.py +++ b/django/api/models/__init__.py @@ -16,6 +16,8 @@ from . import hydrogen_fueling from . import hydrogen_fleets from . import data_fleets -from . import whitelisted_users from . import arc_project_tracking from . import scrap_it +from . import user +from . import permission +from . import user_permission \ No newline at end of file diff --git a/django/api/models/permission.py b/django/api/models/permission.py new file mode 100644 index 00000000..a9483873 --- /dev/null +++ b/django/api/models/permission.py @@ -0,0 +1,15 @@ +from django.db import models +from auditable.models import Auditable + +class Permission(Auditable): + description = models.CharField( + blank=False, + null=False, + unique=True, + max_length=100, + ) + + class Meta: + db_table = 'permission' + db_table_comment = "Contains the list of permissions to grant access to " \ + "certain actions of areas for the system." \ No newline at end of file diff --git a/django/api/models/whitelisted_users.py b/django/api/models/user.py similarity index 55% rename from django/api/models/whitelisted_users.py rename to django/api/models/user.py index eaee419c..e62538b5 100644 --- a/django/api/models/whitelisted_users.py +++ b/django/api/models/user.py @@ -1,8 +1,8 @@ from django.db import models from auditable.models import Auditable -class WhitelistedUsers(Auditable): - user = models.CharField( +class User(Auditable): + idir = models.CharField( blank=False, null=False, unique=True, @@ -10,4 +10,5 @@ class WhitelistedUsers(Auditable): ) class Meta: - db_table = 'whitelisted_users' + db_table = 'user' + db_table_comment = "Contains the list of users in the system " \ No newline at end of file diff --git a/django/api/models/user_permission.py b/django/api/models/user_permission.py new file mode 100644 index 00000000..57cfc706 --- /dev/null +++ b/django/api/models/user_permission.py @@ -0,0 +1,18 @@ +from django.db import models +from auditable.models import Auditable + +class UserPermission(Auditable): + user = models.ForeignKey( + 'User', + related_name='user', + on_delete=models.CASCADE + ) + permission = models.ForeignKey( + 'Permission', + related_name='permission', + on_delete=models.CASCADE + ) + + class Meta: + db_table = 'user_permission' + db_table_comment = "Contains the relationship between user and permission tables " \ No newline at end of file diff --git a/django/api/viewsets/minio.py b/django/api/viewsets/minio.py index a6cfca1c..833f7b5c 100644 --- a/django/api/viewsets/minio.py +++ b/django/api/viewsets/minio.py @@ -5,16 +5,15 @@ from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet from django.utils.decorators import method_decorator -from api.decorators.whitelisted_users import check_whitelist +from api.decorators.permission import check_upload_permission from api.services.minio import minio_put_object - class MinioViewSet(GenericViewSet): permission_classes = (AllowAny,) http_method_names = ['get'] @action(detail=False, methods=['get']) - @method_decorator(check_whitelist()) + @method_decorator(check_upload_permission()) def put(self, request): object_name = uuid.uuid4().hex url = minio_put_object(object_name) diff --git a/django/api/viewsets/upload.py b/django/api/viewsets/upload.py index 2a2d6a49..0c8bfbb6 100644 --- a/django/api/viewsets/upload.py +++ b/django/api/viewsets/upload.py @@ -7,7 +7,7 @@ from rest_framework.viewsets import GenericViewSet from django.core.exceptions import ValidationError from django.utils.decorators import method_decorator -from api.decorators.whitelisted_users import check_whitelist +from api.decorators.permission import check_upload_permission from api.models.datasets import Datasets from api.models.ldv_rebates import LdvRebates from api.models.public_charging import PublicCharging @@ -50,7 +50,7 @@ def datasets_list(self, request): return Response(serializer.data) @action(detail=False, methods=['post']) - @method_decorator(check_whitelist()) + @method_decorator(check_upload_permission()) def import_data(self, request): filename = request.data.get('filename') dataset_selected = request.data.get('datasetSelected')