diff --git a/designsafe/apps/api/filemeta/tests.py b/designsafe/apps/api/filemeta/tests.py index b80fedafa8..aea44dd3a1 100644 --- a/designsafe/apps/api/filemeta/tests.py +++ b/designsafe/apps/api/filemeta/tests.py @@ -85,6 +85,23 @@ def test_get_file_meta( } +@pytest.mark.django_db +def test_get_file_meta_using_jwt( + regular_user_using_jwt, client, filemeta_db_mock, mock_access_success +): + system_id, path, file_meta = filemeta_db_mock + response = client.get(f"/api/filemeta/{system_id}/{path}") + assert response.status_code == 200 + + assert response.json() == { + "value": file_meta.value, + "name": "designsafe.file", + "lastUpdated": file_meta.last_updated.isoformat( + timespec="milliseconds" + ).replace("+00:00", "Z"), + } + + @pytest.mark.django_db def test_create_file_meta_no_access( client, authenticated_user, filemeta_value_mock, mock_access_failure @@ -122,6 +139,21 @@ def test_create_file_meta( assert file_meta.value == filemeta_value_mock +@pytest.mark.django_db +def test_create_file_meta_using_jwt( + client, regular_user_using_jwt, filemeta_value_mock, mock_access_success +): + response = client.post( + "/api/filemeta/", + data=json.dumps(filemeta_value_mock), + content_type="application/json", + ) + assert response.status_code == 200 + + file_meta = FileMetaModel.objects.first() + assert file_meta.value == filemeta_value_mock + + @pytest.mark.django_db def test_create_file_meta_update_existing_entry( client, diff --git a/designsafe/apps/api/filemeta/views.py b/designsafe/apps/api/filemeta/views.py index f80d04da9e..904fd96f0c 100644 --- a/designsafe/apps/api/filemeta/views.py +++ b/designsafe/apps/api/filemeta/views.py @@ -5,7 +5,7 @@ from designsafe.apps.api.datafiles.operations.tapis_operations import listing from designsafe.apps.api.exceptions import ApiException from designsafe.apps.api.filemeta.models import FileMetaModel -from designsafe.apps.api.views import AuthenticatedApiView +from designsafe.apps.api.views import AuthenticatedAllowJwtApiView logger = logging.getLogger(__name__) @@ -36,8 +36,7 @@ def check_access(request, system_id: str, path: str, check_for_writable_access=F raise ApiException("User forbidden to access metadata", status=403) from exc -# TODO_V3 update to allow JWT access DES-2706: https://github.com/DesignSafe-CI/portal/pull/1192 -class FileMetaView(AuthenticatedApiView): +class FileMetaView(AuthenticatedAllowJwtApiView): """View for creating and getting file metadata""" def get(self, request: HttpRequest, system_id: str, path: str): @@ -64,8 +63,7 @@ def get(self, request: HttpRequest, system_id: str, path: str): return JsonResponse(result, safe=False) -# TODO_V3 update to allow JWT access DES-2706: https://github.com/DesignSafe-CI/portal/pull/1192 -class CreateFileMetaView(AuthenticatedApiView): +class CreateFileMetaView(AuthenticatedAllowJwtApiView): """View for creating (and updating) file metadata""" def post(self, request: HttpRequest): diff --git a/designsafe/apps/api/views.py b/designsafe/apps/api/views.py index a6870fd7ba..73996febe2 100644 --- a/designsafe/apps/api/views.py +++ b/designsafe/apps/api/views.py @@ -1,11 +1,14 @@ +from django.views.decorators.csrf import csrf_exempt from django.http.response import HttpResponse, HttpResponseForbidden, JsonResponse from django.views.generic import View from django.http import JsonResponse +from django.utils.decorators import method_decorator from requests.exceptions import ConnectionError, HTTPError from .exceptions import ApiException import logging from logging import getLevelName import json +from designsafe.apps.api.decorators import tapis_jwt_login logger = logging.getLogger(__name__) @@ -59,14 +62,16 @@ def dispatch(self, request, *args, **kwargs): return super(AuthenticatedApiView, self).dispatch(request, *args, **kwargs) -class AuthenticatedApiView(BaseApiView): +class AuthenticatedAllowJwtApiView(AuthenticatedApiView): + """ + Extends AuthenticatedApiView to also allow JWT access in addition to django session cookie + """ + @method_decorator(csrf_exempt, name="dispatch") + @method_decorator(tapis_jwt_login) def dispatch(self, request, *args, **kwargs): - """Returns 401 if user is not authenticated.""" - - if not request.user.is_authenticated: - return JsonResponse({"message": "Unauthenticated user"}, status=401) - return super(AuthenticatedApiView, self).dispatch(request, *args, **kwargs) + """Returns 401 if user is not authenticated like AuthenticatedApiView but allows JWT access.""" + return super(AuthenticatedAllowJwtApiView, self).dispatch(request, *args, **kwargs) class LoggerApi(BaseApiView): diff --git a/designsafe/apps/workspace/templates/designsafe/apps/workspace/app_card.html b/designsafe/apps/workspace/templates/designsafe/apps/workspace/app_card.html index e31df8ea1f..fa08217502 100644 --- a/designsafe/apps/workspace/templates/designsafe/apps/workspace/app_card.html +++ b/designsafe/apps/workspace/templates/designsafe/apps/workspace/app_card.html @@ -3,11 +3,15 @@

{{app

{{app.description}}

+ {% if app.tags|length > 0 %} + {% else %} + + {% endif %}