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

Task/WC-119: Use a Django group to manage project admins #1451

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
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 @@ -51,6 +53,31 @@
logger = logging.getLogger(__name__)


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 @@ -78,9 +105,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 @@ -126,14 +159,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 @@ -152,14 +178,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 @@ -190,14 +209,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", {})
prev_metadata = BaseProject.model_validate(project.value)
Expand Down Expand Up @@ -225,10 +237,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", {})
logger.debug(request_body)
Expand All @@ -242,10 +251,7 @@ def delete(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)

remove_nodes_for_entity(entity_meta.project_id, entity_uuid)
delete_entity(entity_uuid)
Expand Down Expand Up @@ -286,14 +292,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 @@ -321,14 +320,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 All @@ -351,14 +343,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 All @@ -375,14 +360,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 @@ -415,14 +393,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 @@ -457,14 +428,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 All @@ -486,14 +450,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 @@ -521,14 +478,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 All @@ -550,14 +500,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
Loading