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 2023/02/21 21:40:40 UTC

[airavata-django-portal] branch AIRAVATA-3682 created (now 8fe3e7ac)

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

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


      at 8fe3e7ac AIRAVATA-3682 Make text editor readonly when file is in shared dir

This branch includes the following new commits:

     new 751cef0b AIRAVATA-3682 Secure shared dir in PUT /api/data-products
     new a786ef7e AIRAVATA-3682 Set admin group attributes when authenticating with token
     new bb64ad24 AIRAVATA-3682 Make shared directory work with remote user storage API
     new 8fe3e7ac AIRAVATA-3682 Make text editor readonly when file is in shared dir

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.



[airavata-django-portal] 01/04: AIRAVATA-3682 Secure shared dir in PUT /api/data-products

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

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

commit 751cef0b0a515899be8e16b65f880bac825e821e
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Feb 21 10:05:20 2023 -0500

    AIRAVATA-3682 Secure shared dir in PUT /api/data-products
---
 django_airavata/apps/api/view_utils.py | 2 +-
 django_airavata/apps/api/views.py      | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/django_airavata/apps/api/view_utils.py b/django_airavata/apps/api/view_utils.py
index c7f4e35b..bda38db6 100644
--- a/django_airavata/apps/api/view_utils.py
+++ b/django_airavata/apps/api/view_utils.py
@@ -271,7 +271,7 @@ class BaseSharedDirPermission(permissions.BasePermission):
 
 class DataProductSharedDirPermission(BaseSharedDirPermission):
     def get_path(self, request, view) -> str:
-        data_product_uri = request.GET.get('data-product-uri', '')
+        data_product_uri = request.query_params.get('data-product-uri', request.query_params.get('product-uri', ''))
         file_metadata = user_storage.get_data_product_metadata(request, data_product_uri=data_product_uri)
         return file_metadata["path"]
 
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index 5701a901..b8f22d87 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -815,6 +815,7 @@ class LocalDataMovementView(APIView):
 class DataProductView(APIView):
 
     serializer_class = serializers.DataProductSerializer
+    permission_classes = [IsAuthenticated, DataProductSharedDirPermission]
 
     def get(self, request, format=None):
         data_product_uri = request.query_params['product-uri']


[airavata-django-portal] 03/04: AIRAVATA-3682 Make shared directory work with remote user storage API

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

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

commit bb64ad244ed54f3b4b6de3f089609ab46452b61a
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Feb 21 13:56:56 2023 -0500

    AIRAVATA-3682 Make shared directory work with remote user storage API
---
 django_airavata/apps/api/serializers.py | 23 +++++++++++++++++++++++
 django_airavata/apps/api/view_utils.py  |  3 ++-
 2 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index b259c716..030b4a8b 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -2,6 +2,7 @@ import copy
 import datetime
 import json
 import logging
+from pathlib import Path
 from urllib.parse import quote
 
 from airavata.model.appcatalog.appdeployment.ttypes import (
@@ -944,6 +945,26 @@ class UserHasWriteAccessToPathSerializer(serializers.Serializer):
 
     def get_userHasWriteAccess(self, instance):
         request = self.context['request']
+        # Special handling when using remote API to access user data storage
+        if hasattr(settings, 'GATEWAY_DATA_STORE_REMOTE_API'):
+            if "userHasWriteAccess" in instance:
+                return instance["userHasWriteAccess"]
+            elif instance.get("isDir", False):
+                path = Path(instance.get("path", ""))
+                if path != Path(""):
+                    # get parent directory listing and use that to figure out if
+                    # there is write access to this directory
+                    directories, _ = user_storage.listdir(request, path.parent)
+                    for d in directories:
+                        if Path(d["path"]) == path:
+                            return d.get("userHasWriteAccess", False)
+                    return False
+                else:
+                    # User always has write access on home directory
+                    return True
+            else:
+                return False
+
         is_shared_dir = view_utils.is_shared_dir(instance["path"])
         is_shared_path = view_utils.is_shared_path(instance["path"])
         if is_shared_dir:
@@ -984,6 +1005,8 @@ class UserStorageDirectorySerializer(UserHasWriteAccessToPathSerializer):
     isSharedDir = serializers.SerializerMethodField()
 
     def get_isSharedDir(self, directory):
+        if "isSharedDir" in directory:
+            return directory["isSharedDir"]
         return view_utils.is_shared_dir(directory["path"])
 
 
diff --git a/django_airavata/apps/api/view_utils.py b/django_airavata/apps/api/view_utils.py
index bda38db6..a827434f 100644
--- a/django_airavata/apps/api/view_utils.py
+++ b/django_airavata/apps/api/view_utils.py
@@ -2,6 +2,7 @@ import logging
 import os
 from collections.__init__ import OrderedDict
 from datetime import datetime
+from pathlib import Path
 
 import pytz
 from airavata_django_portal_sdk import user_storage
@@ -232,7 +233,7 @@ class ReadOnly(permissions.BasePermission):
 
 def is_shared_dir(path):
     shared_dirs: dict = getattr(settings, 'GATEWAY_DATA_SHARED_DIRECTORIES', {})
-    return any(map(lambda n: n == path, shared_dirs.keys()))
+    return any(map(lambda n: Path(n) == Path(path), shared_dirs.keys()))
 
 
 def is_shared_path(path):


[airavata-django-portal] 02/04: AIRAVATA-3682 Set admin group attributes when authenticating with token

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

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

commit a786ef7e839a99e61ecf83fcb92425a7c1348ef2
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Feb 21 11:07:04 2023 -0500

    AIRAVATA-3682 Set admin group attributes when authenticating with token
---
 django_airavata/apps/api/authentication.py |  4 ++++
 django_airavata/apps/auth/middleware.py    | 26 +++++++++++++++-----------
 2 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/django_airavata/apps/api/authentication.py b/django_airavata/apps/api/authentication.py
index 9e12b941..226b5e58 100644
--- a/django_airavata/apps/api/authentication.py
+++ b/django_airavata/apps/api/authentication.py
@@ -3,6 +3,8 @@ import logging
 from django.contrib.auth import authenticate
 from rest_framework import authentication, exceptions
 
+from django_airavata.apps.auth.middleware import set_admin_group_attributes
+
 logger = logging.getLogger(__name__)
 
 
@@ -18,6 +20,8 @@ class OAuthAuthentication(authentication.BaseAuthentication):
                 _, token = request.META.get('HTTP_AUTHORIZATION').split()
 
                 logger.debug(f"OAuthAuthentication authenticated user {user}")
+                # Set request attributes that are normally set by middleware
+                set_admin_group_attributes(request)
                 return (user, token)
             except Exception as e:
                 raise exceptions.AuthenticationFailed(
diff --git a/django_airavata/apps/auth/middleware.py b/django_airavata/apps/auth/middleware.py
index 8e374051..46a0d08a 100644
--- a/django_airavata/apps/auth/middleware.py
+++ b/django_airavata/apps/auth/middleware.py
@@ -31,6 +31,20 @@ def authz_token_middleware(get_response):
     return middleware
 
 
+def set_admin_group_attributes(request, gateway_groups=None):
+    """Set is_gateway_admin and is_read_only_gateway_admin request attrs."""
+    if gateway_groups is None:
+        gateway_groups = request.airavata_client.getGatewayGroups(request.authz_token)
+    admins_group_id = gateway_groups['adminsGroupId']
+    read_only_admins_group_id = gateway_groups['readOnlyAdminsGroupId']
+    group_manager_client = request.profile_service['group_manager']
+    group_memberships = group_manager_client.getAllGroupsUserBelongs(
+        request.authz_token, request.user.username + "@" + settings.GATEWAY_ID)
+    group_ids = [group.id for group in group_memberships]
+    request.is_gateway_admin = admins_group_id in group_ids
+    request.is_read_only_gateway_admin = read_only_admins_group_id in group_ids
+
+
 def gateway_groups_middleware(get_response):
     """Add 'is_gateway_admin' and 'is_read_only_gateway_admin' to request."""
     def middleware(request):
@@ -52,17 +66,7 @@ def gateway_groups_middleware(get_response):
                     request.authz_token)
                 gateway_groups_dict = copy.deepcopy(gateway_groups.__dict__)
                 request.session['GATEWAY_GROUPS'] = gateway_groups_dict
-            gateway_groups = request.session['GATEWAY_GROUPS']
-            admins_group_id = gateway_groups['adminsGroupId']
-            read_only_admins_group_id = gateway_groups['readOnlyAdminsGroupId']
-            group_manager_client = request.profile_service[
-                'group_manager']
-            group_memberships = group_manager_client.getAllGroupsUserBelongs(
-                request.authz_token, request.user.username + "@" + settings.GATEWAY_ID)
-            group_ids = [group.id for group in group_memberships]
-            request.is_gateway_admin = admins_group_id in group_ids
-            request.is_read_only_gateway_admin = \
-                read_only_admins_group_id in group_ids
+            set_admin_group_attributes(request, request.session.get("GATEWAY_GROUPS"))
             # Gateway Admins are made 'superuser' in Django so they can edit
             # pages in the CMS
             if request.is_gateway_admin and (


[airavata-django-portal] 04/04: AIRAVATA-3682 Make text editor readonly when file is in shared dir

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

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

commit 8fe3e7ac4b650be9a00840864a45bede707eb395
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Feb 21 16:36:43 2023 -0500

    AIRAVATA-3682 Make text editor readonly when file is in shared dir
---
 django_airavata/apps/api/serializers.py                 | 14 ++++++++++++++
 .../static/django_airavata_api/js/models/DataProduct.js |  1 +
 .../storage/storage-edit/UserStorageTextEditViewer.vue  | 17 ++++++++++++++---
 .../js/containers/UserStorageContainer.vue              |  1 +
 4 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index 030b4a8b..618075f2 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -549,6 +549,7 @@ class DataProductSerializer(
     downloadURL = serializers.SerializerMethodField()
     isInputFileUpload = serializers.SerializerMethodField()
     filesize = serializers.SerializerMethodField()
+    userHasWriteAccess = serializers.SerializerMethodField()
 
     def get_downloadURL(self, data_product):
         """Getter for downloadURL field. Returns None if file is not available."""
@@ -572,6 +573,19 @@ class DataProductSerializer(
         else:
             return 0
 
+    def get_userHasWriteAccess(self, data_product: DataProductModel):
+        request = self.context['request']
+        file_metadata = user_storage.get_data_product_metadata(request, data_product_uri=data_product.productUri)
+        if "userHasWriteAccess" in file_metadata:
+            return file_metadata["userHasWriteAccess"]
+        else:
+            path = file_metadata["path"]
+            shared_path = view_utils.is_shared_path(path)
+            if shared_path:
+                # Only admins can edit files/directories in a shared directory
+                return request.is_gateway_admin
+            return True
+
 
 # TODO move this into airavata_sdk?
 class FullExperiment:
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/DataProduct.js b/django_airavata/apps/api/static/django_airavata_api/js/models/DataProduct.js
index 34c1e143..507d5bdb 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/DataProduct.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/DataProduct.js
@@ -29,6 +29,7 @@ const FIELDS = [
   "downloadURL",
   "isInputFileUpload",
   "filesize",
+  "userHasWriteAccess",
 ];
 
 const FILENAME_REGEX = /[^/]+$/;
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/storage-edit/UserStorageTextEditViewer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/storage-edit/UserStorageTextEditViewer.vue
index a78a528e..aa8ff0c1 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/storage-edit/UserStorageTextEditViewer.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/storage-edit/UserStorageTextEditViewer.vue
@@ -2,8 +2,12 @@
   <div>
     <div class="user-storage-file-edit-viewer-status">
       <div class="user-storage-file-edit-viewer-status-message">
-        <span v-if="editAvailable && saved">All the changes are saved.</span>
-        <span v-if="editAvailable && !saved">Changes are not saved.</span>
+        <span v-if="editAvailable && !readOnly && saved"
+          >All the changes are saved.</span
+        >
+        <span v-if="editAvailable && !readOnly && !saved"
+          >Changes are not saved.</span
+        >
       </div>
       <div class="user-storage-file-edit-viewer-status-actions">
         <user-storage-download-button
@@ -11,7 +15,7 @@
           :file-name="fileName"
         />
         <b-button
-          v-if="editAvailable"
+          v-if="editAvailable && !readOnly"
           :disabled="saved"
           @click="fileContentChanged"
           >Save</b-button
@@ -76,6 +80,12 @@ export default {
     editAvailable() {
       return !this.dataProduct || this.dataProduct.filesize < MAX_EDIT_FILESIZE;
     },
+    userHasWriteAccess() {
+      return this.dataProduct && this.dataProduct.userHasWriteAccess;
+    },
+    readOnly() {
+      return !this.userHasWriteAccess;
+    },
   },
   methods: {
     fileContentChanged() {
@@ -124,6 +134,7 @@ export default {
         scrollbarStyle: "native",
         extraKeys: { "Ctrl-Space": "autocomplete" },
         value: value,
+        readOnly: this.readOnly,
       });
       this.editor.on("change", () => {
         this.saved = false;
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/UserStorageContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/UserStorageContainer.vue
index bd0e83da..919e0ab6 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/UserStorageContainer.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/UserStorageContainer.vue
@@ -87,6 +87,7 @@ export default {
                   mimeType: dataProduct.productMetadata["mime-type"],
                   name: dataProduct.productName,
                   size: dataProduct.productSize,
+                  userHasWriteAccess: dataProduct.userHasWriteAccess,
                 },
               ],
               parts: [],