You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2022/10/20 22:26:59 UTC

[airavata-django-portal] branch develop updated (aec22827 -> d12ad44a)

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a change to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git


    from aec22827 Fixing formatting with copy to clipboard snippets from ui tutorial
     new faeb92a0 AIRAVATA-3647 Special display for unauthenticated errors, directing users to re-authenticate
     new 04bae481 AIRAVATA-3647 update tutorial to use @api_view
     new c4a735ac AIRAVATA-3647 Documenting utility methods
     new d12ad44a AIRAVATA-3647 Unit test for handling unauthenticated REST error responses

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 django_airavata/apps/api/exceptions.py             |  6 +++
 .../django_airavata_api/js/errors/ErrorUtils.js    | 34 +++++++++++++++
 .../js/errors/UnhandledError.js                    |  6 +++
 .../js/errors/UnhandledErrorDispatcher.js          | 14 ++++++-
 .../django_airavata_api/js/utils/FetchUtils.js     |  4 +-
 django_airavata/apps/api/tests/test_views.py       | 23 ++++++++++
 .../common/js/components/NotificationsDisplay.vue  | 49 +++++++++++++++++-----
 docs/tutorial/custom_ui_tutorial.md                |  5 ++-
 tests/settings.py                                  |  5 ++-
 9 files changed, 131 insertions(+), 15 deletions(-)


[airavata-django-portal] 02/04: AIRAVATA-3647 update tutorial to use @api_view

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 04bae481a639f9d0ff9a922170848f472710c0f6
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Oct 19 18:09:05 2022 -0400

    AIRAVATA-3647 update tutorial to use @api_view
---
 docs/tutorial/custom_ui_tutorial.md | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/docs/tutorial/custom_ui_tutorial.md b/docs/tutorial/custom_ui_tutorial.md
index 9d2782b3..df883229 100644
--- a/docs/tutorial/custom_ui_tutorial.md
+++ b/docs/tutorial/custom_ui_tutorial.md
@@ -1199,7 +1199,7 @@ Now we'll create a REST endpoint in our custom Django app that will return
 greetings in several languages.
 
 1. In the `$HOME/custom_ui_tutorial_app/custom_ui_tutorial_app/views.py` file,
-   we add the following import:
+   we add the following imports:
 
 <button class="btn" data-clipboard-target="#jsonresponse">
     Copy to clipboard
@@ -1208,6 +1208,7 @@ greetings in several languages.
 
 ```python
 from django.http import JsonResponse
+from rest_framework.decorators import api_view
 ```
 
 </div>
@@ -1220,7 +1221,7 @@ from django.http import JsonResponse
 <div id="languages">
 
 ```python
-@login_required
+@api_view()
 def languages(request):
     return JsonResponse({'languages': [{
         'lang': 'French',


[airavata-django-portal] 01/04: AIRAVATA-3647 Special display for unauthenticated errors, directing users to re-authenticate

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit faeb92a062f0e3b881f5ce7a8be23109d48f6539
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Oct 7 12:20:14 2022 -0400

    AIRAVATA-3647 Special display for unauthenticated errors, directing users to re-authenticate
---
 django_airavata/apps/api/exceptions.py             |  6 +++
 .../django_airavata_api/js/errors/ErrorUtils.js    | 19 +++++++++
 .../js/errors/UnhandledError.js                    |  6 +++
 .../js/errors/UnhandledErrorDispatcher.js          | 14 ++++++-
 .../django_airavata_api/js/utils/FetchUtils.js     |  4 +-
 .../common/js/components/NotificationsDisplay.vue  | 49 +++++++++++++++++-----
 6 files changed, 86 insertions(+), 12 deletions(-)

diff --git a/django_airavata/apps/api/exceptions.py b/django_airavata/apps/api/exceptions.py
index 7caf17c7..0676c126 100644
--- a/django_airavata/apps/api/exceptions.py
+++ b/django_airavata/apps/api/exceptions.py
@@ -4,6 +4,7 @@ from airavata.api.error.ttypes import AuthorizationException
 from django.core.exceptions import ObjectDoesNotExist
 from django.http import JsonResponse
 from rest_framework import status
+from rest_framework.exceptions import NotAuthenticated
 from rest_framework.response import Response
 from rest_framework.views import exception_handler
 from thrift.Thrift import TException
@@ -43,6 +44,11 @@ def custom_exception_handler(exc, context):
             {'detail': str(exc)},
             status=status.HTTP_404_NOT_FOUND)
 
+    if isinstance(exc, NotAuthenticated):
+        log.debug("NotAuthenticated", exc_info=exc)
+        if response is not None:
+            response.data['is_authenticated'] = False
+
     # Generic handler
     if response is None:
         log.error("API exception", exc_info=exc)
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorUtils.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorUtils.js
index 78329930..b1213e42 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorUtils.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorUtils.js
@@ -22,4 +22,23 @@ export default {
   isNotFoundError(error) {
     return this.isAPIException(error) && error.details.status === 404;
   },
+  isUnauthenticatedError(error) {
+    return (
+      this.isAPIException(error) &&
+      [401, 403].includes(error.details.status) &&
+      "is_authenticated" in error.details.response &&
+      error.details.response.is_authenticated === false
+    );
+  },
+  buildLoginUrl(includeNextParameter = true) {
+    let loginUrl = "/auth/login";
+    if (includeNextParameter) {
+      let currentURL = window.location.pathname;
+      if (window.location.search) {
+        currentURL += window.location.search;
+      }
+      loginUrl += `?next=${encodeURIComponent(currentURL)}`;
+    }
+    return loginUrl;
+  },
 };
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledError.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledError.js
index fbde9a25..41ece6ae 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledError.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledError.js
@@ -1,3 +1,5 @@
+import ErrorUtils from "./ErrorUtils";
+
 let idSequence = 0;
 class UnhandledError {
   constructor({
@@ -15,6 +17,10 @@ class UnhandledError {
     this.suppressLogging = suppressLogging;
     this.createdDate = new Date();
   }
+
+  get isUnauthenticatedError() {
+    return ErrorUtils.isUnauthenticatedError(this.error);
+  }
 }
 
 export default UnhandledError;
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDispatcher.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDispatcher.js
index 35074fd1..10d75b1b 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDispatcher.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDispatcher.js
@@ -21,10 +21,22 @@ class UnhandledErrorDispatcher {
   }
 
   reportUnhandledError(unhandledError) {
+    // Ignore unauthenticated errors that have already been displayed
+    if (
+      unhandledError.isUnauthenticatedError &&
+      UnhandledErrorList.list.some((e) => e.isUnauthenticatedError)
+    ) {
+      return;
+    }
+
     if (!unhandledError.suppressDisplay) {
       UnhandledErrorList.add(unhandledError);
     }
-    if (!unhandledError.suppressLogging) {
+    if (
+      !unhandledError.suppressLogging &&
+      // Don't log unauthenticated errors
+      !unhandledError.isUnauthenticatedError
+    ) {
       ErrorReporter.reportUnhandledError(unhandledError);
     }
   }
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js b/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js
index 8a52802b..f11e7d12 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js
@@ -1,3 +1,4 @@
+import ErrorUtils from "../errors/ErrorUtils";
 import UnhandledErrorDispatcher from "../errors/UnhandledErrorDispatcher";
 import Cache from "./Cache";
 
@@ -266,7 +267,8 @@ export default {
         if (showSpinner) {
           decrementCount();
         }
-        if (!ignoreErrors) {
+        // Always report unauthenticated errors so user knows they need to re-authenticate
+        if (!ignoreErrors || ErrorUtils.isUnauthenticatedError(error)) {
           this.reportError(error);
         }
         throw error;
diff --git a/django_airavata/static/common/js/components/NotificationsDisplay.vue b/django_airavata/static/common/js/components/NotificationsDisplay.vue
index 19542187..5d7a8f6d 100644
--- a/django_airavata/static/common/js/components/NotificationsDisplay.vue
+++ b/django_airavata/static/common/js/components/NotificationsDisplay.vue
@@ -1,16 +1,36 @@
 <template>
   <div id="notifications-display">
     <transition-group name="fade" tag="div">
-      <b-alert
-        v-for="unhandledError in unhandledErrors"
-        variant="danger"
-        :key="unhandledError.id"
-        show
-        dismissible
-        @dismissed="dismissUnhandledError(unhandledError)"
-      >
-        {{ unhandledError.message }}
-      </b-alert>
+      <template v-for="unhandledError in unhandledErrors">
+        <b-alert
+          v-if="isUnauthenticatedError(unhandledError.error)"
+          variant="danger"
+          :key="unhandledError.id"
+          show
+          dismissible
+          @dismissed="dismissUnhandledError(unhandledError)"
+        >
+          Your login session has expired. Please
+          <b-link class="alert-link" :href="loginLinkWithNext"
+            >log in again</b-link
+          >. You can also
+          <b-link class="alert-link" :href="loginLink" target="_blank"
+            >login in a separate tab
+            <i class="fa fa-external-link-alt" aria-hidden="true"></i
+          ></b-link>
+          and then return to this tab and try again.
+        </b-alert>
+        <b-alert
+          v-else
+          variant="danger"
+          :key="unhandledError.id"
+          show
+          dismissible
+          @dismissed="dismissUnhandledError(unhandledError)"
+        >
+          {{ unhandledError.message }}
+        </b-alert>
+      </template>
       <b-alert
         v-for="notification in notifications"
         :variant="variant(notification)"
@@ -86,6 +106,9 @@ export default {
       }.bind(this);
       setTimeout(pollAPIServerStatus.bind(this), this.pollingDelay);
     },
+    isUnauthenticatedError(error) {
+      return errors.ErrorUtils.isUnauthenticatedError(error);
+    },
   },
   computed: {
     apiServerDown() {
@@ -130,6 +153,12 @@ export default {
         : false;
       return notificationsApiServerDown || unhandledErrorsApiServerDown;
     },
+    loginLinkWithNext() {
+      return errors.ErrorUtils.buildLoginUrl();
+    },
+    loginLink() {
+      return errors.ErrorUtils.buildLoginUrl(false);
+    },
   },
   watch: {
     /*


[airavata-django-portal] 03/04: AIRAVATA-3647 Documenting utility methods

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit c4a735ac25a64b90a13ce8d91665f6c2fe4ff0e7
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Oct 20 18:10:43 2022 -0400

    AIRAVATA-3647 Documenting utility methods
---
 .../static/django_airavata_api/js/errors/ErrorUtils.js    | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorUtils.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorUtils.js
index b1213e42..d69f70f4 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorUtils.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorUtils.js
@@ -22,6 +22,14 @@ export default {
   isNotFoundError(error) {
     return this.isAPIException(error) && error.details.status === 404;
   },
+  /**
+   * Return true if the error is an unauthenticated error, i.e., the user needs
+   * to log in again.
+   *
+   * @param {Error} error
+   * @returns
+   * @see {@link buildLoginUrl} for utility to build re-login url
+   */
   isUnauthenticatedError(error) {
     return (
       this.isAPIException(error) &&
@@ -30,6 +38,13 @@ export default {
       error.details.response.is_authenticated === false
     );
   },
+  /**
+   * Build a url that takes the user to the login page.
+   *
+   * @param {boolean} includeNextParameter - Add a 'next' url to the login url
+   *   that will take the user back to this page after login
+   * @returns
+   */
   buildLoginUrl(includeNextParameter = true) {
     let loginUrl = "/auth/login";
     if (includeNextParameter) {


[airavata-django-portal] 04/04: AIRAVATA-3647 Unit test for handling unauthenticated REST error responses

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit d12ad44a0d1f5615882429d86b5eb9134c20ce83
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Oct 20 18:23:49 2022 -0400

    AIRAVATA-3647 Unit test for handling unauthenticated REST error responses
---
 django_airavata/apps/api/tests/test_views.py | 23 +++++++++++++++++++++++
 tests/settings.py                            |  5 ++++-
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/django_airavata/apps/api/tests/test_views.py b/django_airavata/apps/api/tests/test_views.py
index 22d30e79..201538dc 100644
--- a/django_airavata/apps/api/tests/test_views.py
+++ b/django_airavata/apps/api/tests/test_views.py
@@ -468,3 +468,26 @@ class IAMUserViewSetTests(TestCase):
         group_manager_mock.getGroup.assert_not_called()
         group_manager_mock.addUsersToGroup.assert_not_called()
         user_added_to_group_handler.assert_not_called()
+
+
+@override_settings(
+    GATEWAY_ID=GATEWAY_ID,
+    PORTAL_ADMINS=PORTAL_ADMINS
+)
+class ExceptionHandlingTest(TestCase):
+
+    def setUp(self):
+        self.user = User.objects.create_user('testuser')
+        self.factory = APIRequestFactory()
+
+    def test_unauthenticated_request(self):
+
+        url = reverse('django_airavata_api:group-list')
+        data = {}
+        request = self.factory.post(url, data)
+        # Deliberately not authenticating user for request
+        group_create = views.GroupViewSet.as_view({'post': 'create'})
+        response = group_create(request)
+        self.assertEquals(403, response.status_code)
+        self.assertIn('is_authenticated', response.data)
+        self.assertFalse(response.data['is_authenticated'])
diff --git a/tests/settings.py b/tests/settings.py
index 7bc12f00..e65a3bcd 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -10,7 +10,10 @@ BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': os.path.join(BASEDIR, 'db.sqlite3'),
+        'TEST': {
+            'ENGINE': 'django.db.backends.sqlite3',
+            'NAME': os.path.join(BASEDIR, 'test-db.sqlite3'),
+        }
     }
 }