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 2019/05/31 18:18:16 UTC

[airavata-django-portal] 01/05: AIRAVATA-3040 Persist changes to user group membership

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

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

commit f447a126ff4f1fbb731af09d7660736e1d461a79
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu May 30 11:34:19 2019 -0400

    AIRAVATA-3040 Persist changes to user group membership
---
 .../src/components/users/UserDetailsContainer.vue  |  44 ++++++
 .../components/users/UserGroupMembershipEditor.vue |  38 ++++--
 .../components/users/UserManagementContainer.vue   |  27 +++-
 django_airavata/apps/api/serializers.py            |  18 +--
 .../api/static/django_airavata_api/js/index.js     |   2 +
 django_airavata/apps/api/views.py                  |  11 ++
 django_airavata/settings_local_seagrid.py          | 147 +++++++++++++++++++++
 7 files changed, 265 insertions(+), 22 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserDetailsContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserDetailsContainer.vue
new file mode 100644
index 0000000..f47101b
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserDetailsContainer.vue
@@ -0,0 +1,44 @@
+<template>
+  <user-group-membership-editor
+    v-model="localManagedUserProfile.groups"
+    :editable-groups="editableGroups"
+    @input="groupsUpdated"
+  />
+</template>
+<script>
+import { models } from "django-airavata-api";
+import UserGroupMembershipEditor from "./UserGroupMembershipEditor";
+
+export default {
+  name: "user-details-container",
+  props: {
+    managedUserProfile: {
+      type: models.ManagedUserProfile,
+      required: true
+    },
+    editableGroups: {
+      type: Array,
+      required: true
+    }
+  },
+  components: {
+    UserGroupMembershipEditor
+  },
+  data() {
+    return {
+      localManagedUserProfile: this.managedUserProfile.clone()
+    };
+  },
+  watch: {
+    managedUserProfile(newValue) {
+      this.localManagedUserProfile = newValue.clone();
+    }
+  },
+  methods: {
+    groupsUpdated() {
+      this.$emit("groups-updated", this.localManagedUserProfile);
+    }
+  }
+};
+</script>
+
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserGroupMembershipEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserGroupMembershipEditor.vue
index 494f862..ba8a940 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserGroupMembershipEditor.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserGroupMembershipEditor.vue
@@ -3,16 +3,19 @@
     <b-form-checkbox-group
       :checked="selected"
       :options="options"
+      @input="groupsUpdated"
       stacked
     ></b-form-checkbox-group>
   </b-form-group>
 </template>
 
 <script>
+import { mixins } from "django-airavata-common-ui";
 export default {
   name: "user-group-membership-editor",
+  mixins: [mixins.VModelMixin],
   props: {
-    groups: {
+    value: {
       type: Array,
       required: true
     },
@@ -21,21 +24,17 @@ export default {
       required: true
     }
   },
-  data() {
-    return {
-    }
-  },
   computed: {
     selected() {
-      return this.groups.map(g => g.id);
+      return this.data.map(g => g.id);
     },
     options() {
-      const options = this.groups.map(g => {
+      const options = this.data.map(g => {
         return {
           text: g.name,
           value: g.id,
           disabled: true
-        }
+        };
       });
       this.editableGroups.forEach(g => {
         const editableOption = options.find(opt => opt.value === g.id);
@@ -46,11 +45,30 @@ export default {
             text: g.name,
             value: g.id,
             disabled: false
-          })
+          });
         }
-      })
+      });
+      // TODO: sort the options?
       return options;
     }
+  },
+  methods: {
+    groupsUpdated(checkedGroups) {
+      // Check for added groups
+      for (const checkedGroupId of checkedGroups) {
+        if (!this.data.find(g => g.id === checkedGroupId)) {
+          const addedGroup = this.editableGroups.find(g => g.id === checkedGroupId);
+          this.data.push(addedGroup);
+        }
+      }
+      // Check for removed groups
+      for (const group of this.data) {
+        if (!checkedGroups.find(groupId => groupId === group.id)) {
+          const groupIndex = this.data.findIndex(g => g.id === group.id);
+          this.data.splice(groupIndex, 1);
+        }
+      }
+    }
   }
 };
 </script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserManagementContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserManagementContainer.vue
index dbbcb13..49ca5db 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserManagementContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserManagementContainer.vue
@@ -29,7 +29,11 @@
                 slot="row-details"
                 slot-scope="data"
               >
-                <user-group-membership-editor :groups="data.item.groups" :editableGroups="editableGroups" />
+                <user-details-container
+                  :managed-user-profile="data.item"
+                  :editable-groups="editableGroups"
+                  @groups-updated="groupsUpdated"
+                />
               </template>
             </b-table>
             <pager
@@ -47,7 +51,7 @@
 <script>
 import { services } from "django-airavata-api";
 import { components } from "django-airavata-common-ui";
-import UserGroupMembershipEditor from "./UserGroupMembershipEditor.vue";
+import UserDetailsContainer from "./UserDetailsContainer.vue";
 
 export default {
   name: "user-management-container",
@@ -59,7 +63,7 @@ export default {
   },
   components: {
     pager: components.Pager,
-    UserGroupMembershipEditor
+    UserDetailsContainer
   },
   created() {
     services.ManagedUserProfileService.list({ limit: 10 }).then(
@@ -109,6 +113,9 @@ export default {
       return this.allGroups
         ? this.allGroups.filter(g => g.isAdmin || g.isOwner)
         : [];
+    },
+    currentOffset() {
+      return this.usersPaginator ? this.usersPaginator.offset : 0;
     }
   },
   methods: {
@@ -117,6 +124,20 @@ export default {
     },
     previous() {
       this.usersPaginator.previous();
+    },
+    groupsUpdated(managedUserProfile) {
+      services.ManagedUserProfileService.update({
+        lookup: managedUserProfile.userId,
+        data: managedUserProfile
+      }).finally(() => {
+        this.reloadUserProfiles();
+      });
+    },
+    reloadUserProfiles() {
+      services.ManagedUserProfileService.list({
+        limit: 10,
+        offset: this.currentOffset
+      }).then(users => (this.usersPaginator = users));
     }
   }
 };
diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index bbb66a8..1589702 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -592,15 +592,6 @@ class GroupResourceProfileSerializer(
             ResourcePermissionType.WRITE)
 
 
-class SharedGroups(serializers.Serializer):
-    groupList = serializers.ListField(child=serializers.CharField())
-    entityId = serializers.CharField()
-
-    def update(self, instance, validated_data):
-        instance["groupList"] = validated_data["groupList"]
-        return instance
-
-
 class UserPermissionSerializer(serializers.Serializer):
     user = UserProfileSerializer()
     permissionType = serializers.IntegerField()
@@ -830,3 +821,12 @@ class ManagedUserProfile(serializers.Serializer):
         view_name='django_airavata_api:managed-user-profile-detail',
         lookup_field='userId',
         lookup_url_kwarg='user_id')
+
+    def update(self, instance, validated_data):
+        existing_group_ids = [group.id for group in instance['groups']]
+        new_group_ids = [group['id'] for group in validated_data['groups']]
+        instance['_added_group_ids'] = list(
+            set(new_group_ids) - set(existing_group_ids))
+        instance['_removed_group_ids'] = list(
+            set(existing_group_ids) - set(new_group_ids))
+        return instance
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/index.js b/django_airavata/apps/api/static/django_airavata_api/js/index.js
index 49b8f75..67a26d5 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/index.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/index.js
@@ -22,6 +22,7 @@ import GroupComputeResourcePreference from "./models/GroupComputeResourcePrefere
 import GroupPermission from "./models/GroupPermission";
 import GroupResourceProfile from "./models/GroupResourceProfile";
 import InputDataObjectType from "./models/InputDataObjectType";
+import ManagedUserProfile from "./models/ManagedUserProfile";
 import OutputDataObjectType from "./models/OutputDataObjectType";
 import ParallelismType from "./models/ParallelismType";
 import Project from "./models/Project";
@@ -76,6 +77,7 @@ const models = {
   GroupPermission,
   GroupResourceProfile,
   InputDataObjectType,
+  ManagedUserProfile,
   OutputDataObjectType,
   ParallelismType,
   Project,
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index c42d304..b0797ec 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -1406,6 +1406,17 @@ class ManagedUserViewSet(mixins.CreateModelMixin,
         return self._convert_user_profile(
             iam_admin_client.get_user(lookup_value))
 
+    def perform_update(self, serializer):
+        managed_user_profile = serializer.save()
+        group_manager_client = self.request.profile_service['group_manager']
+        user_id = managed_user_profile['airavataInternalUserId']
+        for group_id in managed_user_profile['_added_group_ids']:
+            group_manager_client.addUsersToGroup(
+                self.authz_token, [user_id], group_id)
+        for group_id in managed_user_profile['_removed_group_ids']:
+            group_manager_client.removeUsersFromGroup(
+                self.authz_token, [user_id], group_id)
+
     def _convert_user_profile(self, user_profile):
         user_profile_client = self.request.profile_service['user_profile']
         group_manager_client = self.request.profile_service['group_manager']
diff --git a/django_airavata/settings_local_seagrid.py b/django_airavata/settings_local_seagrid.py
new file mode 100644
index 0000000..c193a3a
--- /dev/null
+++ b/django_airavata/settings_local_seagrid.py
@@ -0,0 +1,147 @@
+"""
+Override default Django settings for a particular instance.
+
+Copy this file to settings_local.py and modify as appropriate. This file will
+be imported into settings.py last of all so settings in this file override any
+defaults specified in settings.py.
+"""
+
+import os
+
+from . import webpack_loader_util
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# Django configuration
+
+DEBUG = True
+ALLOWED_HOSTS = ['localhost']
+
+# Django - Email Settings
+# Uncomment and specify the following for sending emails (default email backend
+# just prints to the console)
+# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
+# EMAIL_HOST = 'smtp.gmail.com'
+# EMAIL_PORT = 587
+# EMAIL_HOST_USER = 'pga.airavata@gmail.com'
+# EMAIL_HOST_PASSWORD = 'airavata12'
+# EMAIL_USE_TLS = True
+ADMINS = [('Marcus Christie', 'machrist@iu.edu')]
+SERVER_EMAIL = 'pga.airavata@gmail.com'
+
+# Keycloak Configuration
+KEYCLOAK_CLIENT_ID = 'pga'
+KEYCLOAK_CLIENT_SECRET = '5d2dc66a-f54e-4fa9-b78f-80d33aa862c1'
+KEYCLOAK_AUTHORIZE_URL = 'https://iamdev.scigap.org/auth/realms/seagrid/protocol/openid-connect/auth'
+KEYCLOAK_TOKEN_URL = 'https://iamdev.scigap.org/auth/realms/seagrid/protocol/openid-connect/token'
+KEYCLOAK_USERINFO_URL = 'https://iamdev.scigap.org/auth/realms/seagrid/protocol/openid-connect/userinfo'
+KEYCLOAK_LOGOUT_URL = 'https://iamdev.scigap.org/auth/realms/seagrid/protocol/openid-connect/logout'
+KEYCLOAK_CA_CERTFILE = os.path.join(
+    BASE_DIR,
+    "django_airavata",
+    "resources",
+    "incommon_rsa_server_ca.pem")
+KEYCLOAK_VERIFY_SSL = True
+
+AUTHENTICATION_OPTIONS = {
+    'password': {
+        'name': 'SEAGrid'
+    },
+    'external': [
+        {
+            'idp_alias': 'oidc',
+            'name': 'CILogon',
+        },
+        # {
+        #     'idp_alias': 'iucas',
+        #     'name': 'IU CAS',
+        # },
+    ]
+}
+
+# Airavata API Configuration
+GATEWAY_ID = 'seagrid'
+AIRAVATA_API_HOST = 'apidev.scigap.org'
+AIRAVATA_API_PORT = 9930
+AIRAVATA_API_SECURE = True
+# AIRAVATA_API_HOST = 'localhost'
+# AIRAVATA_API_PORT = 8930
+# AIRAVATA_API_SECURE = False
+GATEWAY_DATA_STORE_RESOURCE_ID = 'pgadev.scigap.org_7ddf28fd-d503-4ff8-bbc5-3279a7c3b99e'
+GATEWAY_DATA_STORE_DIR = '/tmp/experiment-data-dir'
+GATEWAY_DATA_STORE_HOSTNAME = 'localhost'
+
+# Profile Service Configuration
+PROFILE_SERVICE_HOST = AIRAVATA_API_HOST
+PROFILE_SERVICE_PORT = 8962
+PROFILE_SERVICE_SECURE = False
+
+# Sharing API Configuration
+SHARING_API_HOST = AIRAVATA_API_HOST
+SHARING_API_PORT = 7878
+SHARING_API_SECURE = False
+
+# Portal settings
+PORTAL_TITLE = 'Django Airavata Gateway'
+
+# Logging configuration
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': False,
+    'formatters': {
+        'verbose': {
+            'format': '[%(asctime)s %(name)s:%(lineno)d %(levelname)s] %(message)s'
+        },
+    },
+    'filters': {
+        'require_debug_true': {
+            '()': 'django.utils.log.RequireDebugTrue',
+        },
+    },
+    'handlers': {
+        'console': {
+            'filters': ['require_debug_true'],
+            'class': 'logging.StreamHandler',
+            'formatter': 'verbose',
+            'level': 'DEBUG',
+        },
+        # 'file': {
+        #     'class': 'logging.FileHandler',
+        #     'filename': 'django_airavata.log',
+        #     'formatter': 'verbose',
+        # },
+    },
+    'loggers': {
+        'django': {
+            'handlers': ['console'],
+            'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
+        },
+        'airavata': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+        },
+        'django_airavata': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+        },
+        'django_airavata.utils': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+        },
+        'thrift_connector': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+        },
+        'simccs_maptool': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+        },
+        # 'requests_oauthlib': {
+        #     'handlers': ['console'],
+        #     'level': 'DEBUG',
+        # },
+    },
+}
+
+# WEBPACK_LOADER = webpack_loader_util.create_webpack_loader_config("/tmp")