Skip to content

Commit

Permalink
Allow JWT-authed requests for filemeta routes (#1229)
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanfranklin authored May 6, 2024
1 parent 820d67b commit b644dea
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 11 deletions.
32 changes: 32 additions & 0 deletions designsafe/apps/api/filemeta/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 3 additions & 5 deletions designsafe/apps/api/filemeta/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand Down
17 changes: 11 additions & 6 deletions designsafe/apps/api/views.py
Original file line number Diff line number Diff line change
@@ -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__)

Expand Down Expand Up @@ -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):
Expand Down
14 changes: 14 additions & 0 deletions designsafe/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest
import os
import json
from unittest.mock import patch
from django.conf import settings
from designsafe.apps.auth.models import TapisOAuthToken

Expand Down Expand Up @@ -37,6 +38,19 @@ def regular_user(django_user_model, mock_tapis_client):
yield user


@pytest.fixture
def regular_user_using_jwt(regular_user, client):
"""Fixture for regular user who is using jwt for authenticated requests"""
with patch('designsafe.apps.api.decorators.Tapis') as mock_tapis:
# Mock the Tapis's validate_token method within the tapis_jwt_login decorator
mock_validate_token = mock_tapis.return_value.validate_token
mock_validate_token.return_value = {"tapis/username": regular_user.username}

client.defaults['HTTP_X_TAPIS_TOKEN'] = 'fake_token_string'

yield client


@pytest.fixture
def project_admin_user(django_user_model):
django_user_model.objects.create_user(
Expand Down

0 comments on commit b644dea

Please sign in to comment.