Skip to content

Commit

Permalink
Task/WC-119: Use a Django group to manage project admins (#1451)
Browse files Browse the repository at this point in the history
* Use a Django group to manage project admins

* update test settings
  • Loading branch information
jarosenb authored Dec 3, 2024
1 parent a2acb3d commit 4babd86
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 136 deletions.
17 changes: 13 additions & 4 deletions designsafe/apps/api/datafiles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from boxsdk.exception import BoxOAuthException
from django.http import JsonResponse
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.conf import settings
from designsafe.libs.common.utils import check_group_membership
from designsafe.apps.api.datafiles.handlers import datafiles_get_handler, datafiles_post_handler, datafiles_put_handler, resource_unconnected_handler, resource_expired_handler
from designsafe.apps.api.datafiles.operations.transfer_operations import transfer, transfer_folder
from designsafe.apps.api.datafiles.notifications import notify
Expand All @@ -20,8 +22,12 @@
logger = logging.getLogger(__name__)
metrics = logging.getLogger('metrics')

def check_project_admin_group(user):
"""Check whether a user belongs to the Project Admin group"""
return check_group_membership(user, settings.PROJECT_ADMIN_GROUP)

def get_client(user, api):

def get_client(user, api, system=""):
client_mappings = {
'agave': 'tapis_oauth',
'tapis': 'tapis_oauth',
Expand All @@ -30,6 +36,9 @@ def get_client(user, api):
'box': 'box_user_token',
'dropbox': 'dropbox_user_token'
}
if api == 'tapis' and system.startswith("project-") and check_project_admin_group(user):
# Project admin users have full access to project systems.
return service_account()
return getattr(user, client_mappings[api]).client


Expand All @@ -55,7 +64,7 @@ def get(self, request, api, operation=None, scheme='private', system=None, path=

if request.user.is_authenticated:
try:
client = get_client(request.user, api)
client = get_client(request.user, api, system)
except AttributeError:
raise resource_unconnected_handler(api)
elif api in ('agave', 'tapis') and system in (settings.COMMUNITY_SYSTEM,
Expand Down Expand Up @@ -99,7 +108,7 @@ def put(self, request, api, operation=None, scheme='private', system=None, path=
client = None
if request.user.is_authenticated:
try:
client = get_client(request.user, api)
client = get_client(request.user, api, system)
except AttributeError:
raise resource_unconnected_handler(api)

Expand Down Expand Up @@ -131,7 +140,7 @@ def post(self, request, api, operation=None, scheme='private', system=None, path

if request.user.is_authenticated:
try:
client = get_client(request.user, api)
client = get_client(request.user, api, system)
except AttributeError:
raise resource_unconnected_handler(api)

Expand Down
153 changes: 48 additions & 105 deletions designsafe/apps/api/projects_v2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import json
import networkx as nx
from django.http import HttpRequest, JsonResponse
from django.conf import settings
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.db import models
from designsafe.libs.common.utils import check_group_membership
from designsafe.apps.api.views import BaseApiView, ApiException
from designsafe.apps.api.projects_v2.models.project_metadata import ProjectMetadata
from designsafe.apps.api.projects_v2.schema_models.base import BaseProject
Expand Down Expand Up @@ -53,6 +55,31 @@
metrics = logging.getLogger("metrics")


def check_project_admin_group(user) -> bool:
"""Check whether a user belongs to the Project Admin group"""
return check_group_membership(user, settings.PROJECT_ADMIN_GROUP)


def get_project_for_user(project_id, user) -> ProjectMetadata:
"""
Return a project with the specified project_id if the user is authorized to retrieve
it; otherwise throw a 403 error.
"""
if check_project_admin_group(user):
return ProjectMetadata.objects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)

try:
return user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc


def get_search_filter(query_string):
"""
Construct a search filter for projects.
Expand Down Expand Up @@ -92,9 +119,15 @@ def get(self, request: HttpRequest):
raise ApiException("Unauthenticated user", status=401)

projects = user.projects.order_by("-last_updated")

if check_project_admin_group(user):
projects = ProjectMetadata.objects.filter(
name="designsafe.project"
).order_by("-last_updated")

if query_string:
projects = projects.filter(get_search_filter(query_string))
total = user.projects.count()
total = projects.count()

project_json = {
"result": [
Expand Down Expand Up @@ -165,14 +198,7 @@ def get(self, request: HttpRequest, project_id: str):
if not request.user.is_authenticated:
raise ApiException("Unauthenticated user", status=401)

try:
project = user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc
project = get_project_for_user(project_id, user)

entities = ProjectMetadata.objects.filter(base_project=project)
return JsonResponse(
Expand All @@ -191,14 +217,7 @@ def put(self, request: HttpRequest, project_id: str):
if not request.user.is_authenticated:
raise ApiException("Unauthenticated user", status=401)

try:
project: ProjectMetadata = user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc
project = get_project_for_user(project_id, user)

# Get the new value from the request data
req_body = json.loads(request.body)
Expand Down Expand Up @@ -242,14 +261,7 @@ def patch(self, request: HttpRequest, project_id: str):
if not request.user.is_authenticated:
raise ApiException("Unauthenticated user", status=401)

try:
project: ProjectMetadata = user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc
project = get_project_for_user(project_id, user)

request_body = json.loads(request.body).get("patchMetadata", {})

Expand Down Expand Up @@ -290,10 +302,7 @@ def patch(self, request: HttpRequest, entity_uuid: str):
raise ApiException("Unauthenticated user", status=401)

entity_meta = ProjectMetadata.objects.get(uuid=entity_uuid)
if user not in entity_meta.base_project.users.all():
raise ApiException(
"User does not have access to the requested project", status=403
)
get_project_for_user(entity_meta.base_project.project_id, user)

request_body = json.loads(request.body).get("patchMetadata", {})

Expand Down Expand Up @@ -332,10 +341,7 @@ def delete(self, request: HttpRequest, entity_uuid: str):
)

entity_meta = ProjectMetadata.objects.get(uuid=entity_uuid)
if user not in entity_meta.base_project.users.all():
raise ApiException(
"User does not have access to the requested project", status=403
)
get_project_for_user(entity_meta.base_project.project_id, user)

remove_nodes_for_entity(entity_meta.project_id, entity_uuid)
delete_entity(entity_uuid)
Expand Down Expand Up @@ -389,14 +395,7 @@ def get(self, request: HttpRequest, project_id: str):
if not request.user.is_authenticated:
raise ApiException("Unauthenticated user", status=401)

try:
project = user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc
project = get_project_for_user(project_id, user)
entities = ProjectMetadata.objects.filter(base_project=project)
preview_tree = add_values_to_tree(project.project_id)
return JsonResponse(
Expand Down Expand Up @@ -436,14 +435,7 @@ def put(self, request: HttpRequest, project_id: str):
if not request.user.is_authenticated:
raise ApiException("Unauthenticated user", status=401)

try:
project = user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc
project = get_project_for_user(project_id, user)

project_id = project.project_id
reorder_project_nodes(project_id, node_id, order)
Expand Down Expand Up @@ -482,14 +474,7 @@ def post(self, request: HttpRequest, project_id, node_id):
if not request.user.is_authenticated:
raise ApiException("Unauthenticated user", status=401)

try:
project = user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc
project = get_project_for_user(project_id, user)

entity_meta = ProjectMetadata.objects.get(
uuid=entity_uuid, base_project=project
Expand Down Expand Up @@ -519,14 +504,7 @@ def delete(self, request: HttpRequest, project_id, node_id):
if not request.user.is_authenticated:
raise ApiException("Unauthenticated user", status=401)

try:
project = user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc
project = get_project_for_user(project_id, user)

remove_nodes_from_project(project.project_id, node_ids=[node_id])

Expand Down Expand Up @@ -575,14 +553,7 @@ def patch(self, request: HttpRequest, project_id, entity_uuid):
if not request.user.is_authenticated:
raise ApiException("Unauthenticated user", status=401)

try:
project = user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc
project = get_project_for_user(project_id, user)

try:
ProjectMetadata.objects.get(uuid=entity_uuid, base_project=project)
Expand Down Expand Up @@ -633,14 +604,7 @@ def put(self, request: HttpRequest, project_id, entity_uuid):
if not request.user.is_authenticated:
raise ApiException("Unauthenticated user", status=401)

try:
project = user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc
project = get_project_for_user(project_id, user)

try:
ProjectMetadata.objects.get(uuid=entity_uuid, base_project=project)
Expand Down Expand Up @@ -678,14 +642,7 @@ def delete(self, request: HttpRequest, project_id, entity_uuid, file_path):
if not request.user.is_authenticated:
raise ApiException("Unauthenticated user", status=401)

try:
project = user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc
project = get_project_for_user(project_id, user)

try:
ProjectMetadata.objects.get(uuid=entity_uuid, base_project=project)
Expand Down Expand Up @@ -729,14 +686,7 @@ def put(self, request: HttpRequest, project_id, entity_uuid, file_path):
if not request.user.is_authenticated:
raise ApiException("Unauthenticated user", status=401)

try:
project = user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc
project = get_project_for_user(project_id, user)

try:
ProjectMetadata.objects.get(uuid=entity_uuid, base_project=project)
Expand Down Expand Up @@ -773,14 +723,7 @@ def post(self, request: HttpRequest, project_id):
if not request.user.is_authenticated:
raise ApiException("Unauthenticated user", status=401)

try:
project: ProjectMetadata = user.projects.get(
models.Q(uuid=project_id) | models.Q(value__projectId=project_id)
)
except ProjectMetadata.DoesNotExist as exc:
raise ApiException(
"User does not have access to the requested project", status=403
) from exc
project = get_project_for_user(project_id, user)

entities: list[str] = json.loads(request.body).get("entityUuids", None)

Expand Down
Loading

0 comments on commit 4babd86

Please sign in to comment.