Skip to content

Commit

Permalink
Merge branch 'feat/Tapis-v3-redesign' into task/DES-2705-configure-sy…
Browse files Browse the repository at this point in the history
…stems-during-login
  • Loading branch information
jarosenb authored May 6, 2024
2 parents 2689282 + b644dea commit 970558e
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 142 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ <h3 class="c-app-card__title"><i class="ds-icon ds-icon-{{app.icon}}"></i> {{app

<p class="c-app-card__desc">{{app.description}}</p>

{% if app.tags|length > 0 %}
<ul class="c-app-card__types">
{% for tag in app.tags %}
<li>{{tag}}</li>
{% endfor %}
</ul>
{% else %}
<!-- No tags -->
{% endif %}

<ul class="c-app-card__flags">
{% if app.is_popular %}
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
149 changes: 27 additions & 122 deletions designsafe/static/styles/DesignSafe-Icons.css
Original file line number Diff line number Diff line change
@@ -1,125 +1,30 @@
@font-face {
font-family: 'DesignSafe-Icons';
src: url('/static/fonts/DesignSafe-Icons.eot?nvbv6c');
src: url('/static/fonts/DesignSafe-Icons.eot?nvbv6c#iefix') format('embedded-opentype'),
url('/static/fonts/DesignSafe-Icons.ttf?nvbv6c') format('truetype'),
url('/static/fonts/DesignSafe-Icons.woff2') format('woff2'),
url('/static/fonts/DesignSafe-Icons.woff?nvbv6c') format('woff'),
url('/static/fonts/DesignSafe-Icons.svg?nvbv6c#DesignSafe-Icons') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}

[class^='ds-icon-'],
[class*=' ds-icon-'] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'DesignSafe-Icons' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
@import url('./DesignSafe-Icons.font.css');

/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* TODO: Consider isolating and importing main.css & main-ef.css .ds-icon's */

.ds-icon-Contract:before {
content: '\e91d';
}
.ds-icon-Expand:before {
content: '\e923';
}
.ds-icon-Job-Status:before {
content: '\e929';
}
.ds-icon-All-Hazards:before {
content: '\e908';
}
.ds-icon-New-Tab:before {
content: '\ea7e';
}
.ds-icon-NGL-without-text:before {
content: '\e903';
}
.ds-icon-Potree:before {
content: '\e906';
}
.ds-icon-Generic-Vis:before {
content: '\e907';
}
.ds-icon-HVSR:before {
content: '\e90d';
}
.ds-icon-MPM:before {
content: '\e90e';
}
.ds-icon-Generic-App:before {
content: '\e960';
}
.ds-icon-GiD:before {
content: '\e90f';
}
.ds-icon-Earth:before {
content: '\e910';
}
.ds-icon-Water:before {
content: '\e912';
}
.ds-icon-Wind:before {
content: '\e915';
}
.ds-icon-rWHALE:before {
content: '\e905';
}
.ds-icon-Extract:before {
content: '\e959';
}
.ds-icon-Hazmapper:before {
content: '\e904';
}
.ds-icon-Compress:before {
content: '\e958';
}
.ds-icon-STKO:before {
content: '\e91c';
}
.ds-icon-OpenFOAM:before {
content: '\e900';
}
.ds-icon-Blender:before {
content: '\e901';
}
.ds-icon-MATLAB:before {
content: '\e902';
}
.ds-icon-Paraview:before {
content: '\e911';
}
.ds-icon-Jupyter:before {
content: '\e913';
}
.ds-icon-QGIS:before {
content: '\e914';
}
.ds-icon-OpenSees:before {
content: '\e916';
}
.ds-icon-LS-DYNA:before {
content: '\e917';
}
.ds-icon-Dakota:before {
content: '\e918';
}
.ds-icon-Clawpack:before {
content: '\e919';
}
.ds-icon-Ansys:before {
content: '\e91a';
}
.ds-icon-SWBatch:before {
content: '\e91b';
/* To globally adjust sizes of specific font icons */
.ds-icon-All-Hazards::before,
.ds-icon-Earth::before,
.ds-icon-Water::before,
.ds-icon-Wind::before,
.ds-icon-OpenSees::before {
display: block;
}
.ds-icon-All-Hazards::before,
.ds-icon-Earth::before,
.ds-icon-Water::before,
.ds-icon-Wind::before {
font-size: 1.2em;
/* To restore alignment after font-size changes it */
margin-top: -0.15em; /* FAQ: -0.2em did not re-align it perfectly */
}
.ds-icon-OpenSees::before {
font-size: 1.5em;
/* To restore alignment after font-size changes it */
margin-top: -0.3em; /* FAQ: -0.2em did not re-align it perfectly */
}
.ds-icon-All-Hazards::before,
.ds-icon-OpenSees::before {
/* To move icon down */
translate: 0 0.25em;
}
Loading

0 comments on commit 970558e

Please sign in to comment.