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/06/20 15:21:04 UTC

[airavata-django-portal] branch master updated (ba08177 -> 2bf6153)

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

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


    from ba08177  AIRAVATA-2990 Switch to experiment tab, reuse
     new cab1814  AIRAVATA-3047 Separate service to load users with unverified emails
     new 36ee0fc  AIRAVATA-3048 Only allow Admins to use user mgmt APIs
     new 972ca02  AIRAVATA-3080 Allow admin to manually enable user
     new ff0ea1a  AIRAVATA-3440 Regenerating thrift stubs
     new 414caca  AIRAVATA-3440 Allow admins to delete unverified users
     new 2bf6153  AIRAVATA-3079 Summary display of group memberships

The 6 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:
 .../iam/admin/services/cpi/IamAdminServices-remote |   7 +
 .../iam/admin/services/cpi/IamAdminServices.py     | 233 +++++++++++++++++++++
 .../src/components/users/DeleteUserPanel.vue       |  33 +++
 .../src/components/users/EnableUserPanel.vue       |  31 +++
 .../components/users/GroupMembershipDisplay.vue    |  60 ++++++
 ... => IdentityServiceUserManagementContainer.vue} |  59 ++++--
 .../UnverifiedEmailUserManagementContainer.vue     | 145 +++++++++++++
 .../src/components/users/UserDetailsContainer.vue  |  44 ++--
 .../components/users/UserGroupMembershipEditor.vue |  42 ++--
 .../components/users/UserManagementContainer.vue   | 207 ++----------------
 .../static/django_airavata_admin/src/router.js     |  12 +-
 django_airavata/apps/api/serializers.py            |  19 +-
 .../api/static/django_airavata_api/js/index.js     |   9 +-
 .../{ManagedUserProfile.js => IAMUserProfile.js}   |   2 +-
 ...serProfile.js => UnverifiedEmailUserProfile.js} |  11 +-
 .../django_airavata_api/js/service_config.js       |  23 +-
 django_airavata/apps/api/urls.py                   |   6 +-
 django_airavata/apps/api/view_utils.py             |   9 +-
 django_airavata/apps/api/views.py                  |  96 +++++++--
 django_airavata/apps/auth/iam_admin_client.py      |   5 +
 .../js/group_components/GroupListItem.vue          |   8 +-
 .../common/js/components/GatewayGroupsBadge.vue    |  42 ++++
 django_airavata/static/common/js/index.js          |   2 +
 23 files changed, 832 insertions(+), 273 deletions(-)
 create mode 100644 django_airavata/apps/admin/static/django_airavata_admin/src/components/users/DeleteUserPanel.vue
 create mode 100644 django_airavata/apps/admin/static/django_airavata_admin/src/components/users/EnableUserPanel.vue
 create mode 100644 django_airavata/apps/admin/static/django_airavata_admin/src/components/users/GroupMembershipDisplay.vue
 copy django_airavata/apps/admin/static/django_airavata_admin/src/components/users/{UserManagementContainer.vue => IdentityServiceUserManagementContainer.vue} (77%)
 create mode 100644 django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UnverifiedEmailUserManagementContainer.vue
 copy django_airavata/apps/api/static/django_airavata_api/js/models/{ManagedUserProfile.js => IAMUserProfile.js} (87%)
 rename django_airavata/apps/api/static/django_airavata_api/js/models/{ManagedUserProfile.js => UnverifiedEmailUserProfile.js} (53%)
 create mode 100644 django_airavata/static/common/js/components/GatewayGroupsBadge.vue


[airavata-django-portal] 06/06: AIRAVATA-3079 Summary display of group memberships

Posted by ma...@apache.org.
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 2bf61532c79b6deee29e3e472cf194d937d7b139
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Jun 20 11:09:14 2019 -0400

    AIRAVATA-3079 Summary display of group memberships
---
 .../components/users/GroupMembershipDisplay.vue    | 60 ++++++++++++++++++++++
 .../IdentityServiceUserManagementContainer.vue     | 14 ++++-
 .../components/users/UserGroupMembershipEditor.vue | 42 ++++++++++-----
 .../js/group_components/GroupListItem.vue          |  8 +--
 .../common/js/components/GatewayGroupsBadge.vue    | 42 +++++++++++++++
 django_airavata/static/common/js/index.js          |  2 +
 6 files changed, 151 insertions(+), 17 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/GroupMembershipDisplay.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/GroupMembershipDisplay.vue
new file mode 100644
index 0000000..63785dd
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/GroupMembershipDisplay.vue
@@ -0,0 +1,60 @@
+<template>
+  <span>
+    <gateway-groups-badge
+      v-if="adminsGroup"
+      :group="adminsGroup"
+    />
+    <gateway-groups-badge
+      v-else-if="readOnlyAdminsGroup"
+      :group="readOnlyAdminsGroup"
+    />
+    <gateway-groups-badge
+      v-else-if="defaultUsersGroup"
+      :group="defaultUsersGroup"
+    />
+    <b-badge
+      v-for="group in nonGatewayGroups"
+      :key="group.id"
+    >{{ group.name }}</b-badge>
+  </span>
+</template>
+
+<script>
+import { components } from "django-airavata-common-ui";
+export default {
+  name: "group-membership-display",
+  props: {
+    groups: {
+      type: Array,
+      required: true
+    }
+  },
+  components: {
+    "gateway-groups-badge": components.GatewayGroupsBadge
+  },
+  computed: {
+    adminsGroup() {
+      return this.groups.find(g => g.isGatewayAdminsGroup);
+    },
+    readOnlyAdminsGroup() {
+      return this.groups.find(g => g.isReadOnlyGatewayAdminsGroup);
+    },
+    defaultUsersGroup() {
+      return this.groups.find(g => g.isDefaultGatewayUsersGroup);
+    },
+    nonGatewayGroups() {
+      return this.groups.filter(g => {
+        return (
+          !g.isGatewayAdminsGroup &&
+          !g.isReadOnlyGatewayAdminsGroup &&
+          !g.isDefaultGatewayUsersGroup
+        );
+      });
+    },
+    nonGatewayGroupNames() {
+      return this.nonGatewayGroups.map(g => g.name).join(", ");
+    }
+  }
+};
+</script>
+
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
index 3f33a95..974468d 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
@@ -38,6 +38,12 @@
                 <human-date :date="data.value" />
               </template>
               <template
+                slot="groups"
+                slot-scope="data"
+              >
+                <group-membership-display :groups="data.item.groups" />
+              </template>
+              <template
                 slot="action"
                 slot-scope="data"
               >
@@ -74,6 +80,7 @@
 import { services } from "django-airavata-api";
 import { components } from "django-airavata-common-ui";
 import UserDetailsContainer from "./UserDetailsContainer.vue";
+import GroupMembershipDisplay from "./GroupMembershipDisplay";
 
 export default {
   name: "user-management-container",
@@ -88,7 +95,8 @@ export default {
   components: {
     pager: components.Pager,
     "human-date": components.HumanDate,
-    UserDetailsContainer
+    UserDetailsContainer,
+    GroupMembershipDisplay
   },
   created() {
     services.IAMUserProfileService.list({ limit: 10 }).then(
@@ -126,6 +134,10 @@ export default {
           key: "emailVerified"
         },
         {
+          label: "Groups",
+          key: "groups"
+        },
+        {
           label: "Created",
           key: "creationTime"
         },
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 a8572cf..ca2f4e8 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
@@ -11,19 +11,22 @@
           v-if="gatewayUsersGroupOption"
           :value="gatewayUsersGroupOption.value"
           :disabled="gatewayUsersGroupOption.disabled"
-        >{{ gatewayUsersGroupOption.text }} <b-badge>Default</b-badge>
+        >{{ gatewayUsersGroupOption.text }}
+          <gateway-groups-badge :group="gatewayUsersGroup" />
         </b-form-checkbox>
         <b-form-checkbox
           v-if="adminsGroupOption"
           :value="adminsGroupOption.value"
           :disabled="adminsGroupOption.disabled"
-        >{{ adminsGroupOption.text }} <b-badge>Admins</b-badge>
+        >{{ adminsGroupOption.text }}
+          <gateway-groups-badge :group="adminsGroup" />
         </b-form-checkbox>
         <b-form-checkbox
           v-if="readOnlyAdminsGroupOption"
           :value="readOnlyAdminsGroupOption.value"
           :disabled="readOnlyAdminsGroupOption.disabled"
-        >{{ readOnlyAdminsGroupOption.text }} <b-badge>Read Only Admins</b-badge>
+        >{{ readOnlyAdminsGroupOption.text }}
+          <gateway-groups-badge :group="readOnlyAdminsGroup" />
         </b-form-checkbox>
       </template>
     </b-form-checkbox-group>
@@ -32,7 +35,7 @@
 
 <script>
 import { utils } from "django-airavata-api";
-import { mixins } from "django-airavata-common-ui";
+import { components, mixins } from "django-airavata-common-ui";
 export default {
   name: "user-group-membership-editor",
   mixins: [mixins.VModelMixin],
@@ -50,6 +53,9 @@ export default {
       required: true
     }
   },
+  components: {
+    "gateway-groups-badge": components.GatewayGroupsBadge
+  },
   computed: {
     selected() {
       return this.data.map(g => g.id);
@@ -78,19 +84,27 @@ export default {
       );
       return utils.StringUtils.sortIgnoreCase(options, o => o.text);
     },
+    gatewayUsersGroup() {
+      return this.combinedGroups.find(g => g.isDefaultGatewayUsersGroup);
+    },
     gatewayUsersGroupOption() {
-      const group = this.combinedGroups.find(g => g.isDefaultGatewayUsersGroup);
-      return group ? this.createGroupOption(group) : null;
+      return this.gatewayUsersGroup
+        ? this.createGroupOption(this.gatewayUsersGroup)
+        : null;
+    },
+    adminsGroup() {
+      return this.combinedGroups.find(g => g.isGatewayAdminsGroup);
     },
     adminsGroupOption() {
-      const group = this.combinedGroups.find(g => g.isGatewayAdminsGroup);
-      return group ? this.createGroupOption(group) : null;
+      return this.adminsGroup ? this.createGroupOption(this.adminsGroup) : null;
+    },
+    readOnlyAdminsGroup() {
+      return this.combinedGroups.find(g => g.isReadOnlyGatewayAdminsGroup);
     },
     readOnlyAdminsGroupOption() {
-      const group = this.combinedGroups.find(
-        g => g.isReadOnlyGatewayAdminsGroup
-      );
-      return group ? this.createGroupOption(group) : null;
+      return this.readOnlyAdminsGroup
+        ? this.createGroupOption(this.readOnlyAdminsGroup)
+        : null;
     }
   },
   methods: {
@@ -116,7 +130,9 @@ export default {
       return {
         text: group.name,
         value: group.id,
-        disabled: !group.userHasWriteAccess || group.ownerId === this.airavataInternalUserId
+        disabled:
+          !group.userHasWriteAccess ||
+          group.ownerId === this.airavataInternalUserId
       };
     }
   }
diff --git a/django_airavata/apps/groups/static/django_airavata_groups/js/group_components/GroupListItem.vue b/django_airavata/apps/groups/static/django_airavata_groups/js/group_components/GroupListItem.vue
index 5cfcdb7..e20ee40 100644
--- a/django_airavata/apps/groups/static/django_airavata_groups/js/group_components/GroupListItem.vue
+++ b/django_airavata/apps/groups/static/django_airavata_groups/js/group_components/GroupListItem.vue
@@ -1,9 +1,7 @@
 <template>
     <tr>
         <td>{{ group.name }}
-            <b-badge v-if="group.isGatewayAdminsGroup">Admins</b-badge>
-            <b-badge v-if="group.isReadOnlyGatewayAdminsGroup">Read Only Admins</b-badge>
-            <b-badge v-if="group.isDefaultGatewayUsersGroup">Default</b-badge>
+          <gateway-groups-badge :group="group" v-if="group.isGatewayAdminsGroup || group.isReadOnlyGatewayAdminsGroup || group.isDefaultGatewayUsersGroup" />
         </td>
         <td>{{ ownerUsername }}</td>
         <td>{{ group.description }}</td>
@@ -30,6 +28,7 @@
 <script>
 
 import { services } from 'django-airavata-api'
+import { components } from "django-airavata-common-ui";
 
 export default {
     name: 'group-list-item',
@@ -46,6 +45,9 @@ export default {
       }
     },
     props: ['group'],
+    components: {
+      "gateway-groups-badge": components.GatewayGroupsBadge
+    },
     computed: {
         deleteable: function() {
             return this.group.isOwner
diff --git a/django_airavata/static/common/js/components/GatewayGroupsBadge.vue b/django_airavata/static/common/js/components/GatewayGroupsBadge.vue
new file mode 100644
index 0000000..12d8e04
--- /dev/null
+++ b/django_airavata/static/common/js/components/GatewayGroupsBadge.vue
@@ -0,0 +1,42 @@
+<template>
+  <b-badge :variant="variant">{{ name }}</b-badge>
+</template>
+
+<script>
+import { models } from "django-airavata-api";
+
+export default {
+  name: "gateway-groups-badge",
+  props: {
+    group: {
+      type: models.Group,
+      required: true
+    }
+  },
+  computed: {
+    variant() {
+      if (this.group.isGatewayAdminsGroup) {
+        return "danger";
+      } else if (this.group.isReadOnlyGatewayAdminsGroup) {
+        return "warning";
+      } else if (this.group.isDefaultGatewayUsersGroup) {
+        return "primary";
+      } else {
+        return "secondary";
+      }
+    },
+    name() {
+      if (this.group.isGatewayAdminsGroup) {
+        return "Admins";
+      } else if (this.group.isReadOnlyGatewayAdminsGroup) {
+        return "Read Only Admins";
+      } else if (this.group.isDefaultGatewayUsersGroup) {
+        return "Default";
+      } else {
+        return this.group.name;
+      }
+    }
+  }
+};
+</script>
+
diff --git a/django_airavata/static/common/js/index.js b/django_airavata/static/common/js/index.js
index 48a040c..b48c033 100644
--- a/django_airavata/static/common/js/index.js
+++ b/django_airavata/static/common/js/index.js
@@ -9,6 +9,7 @@ import DataProductViewer from "./components/DataProductViewer";
 import DeleteButton from "./components/DeleteButton.vue";
 import DeleteLink from "./components/DeleteLink.vue";
 import ExperimentStatusBadge from "./components/ExperimentStatusBadge";
+import GatewayGroupsBadge from "./components/GatewayGroupsBadge";
 import HumanDate from "./components/HumanDate.vue";
 import MainLayout from "./components/MainLayout.vue";
 import Pager from "./components/Pager.vue";
@@ -45,6 +46,7 @@ const components = {
   DeleteButton,
   DeleteLink,
   ExperimentStatusBadge,
+  GatewayGroupsBadge,
   HumanDate,
   MainLayout,
   ShareButton,


[airavata-django-portal] 05/06: AIRAVATA-3440 Allow admins to delete unverified users

Posted by ma...@apache.org.
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 414cacad2d133d6420650378f76681d82ce9c8bc
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Jun 19 08:57:02 2019 -0400

    AIRAVATA-3440 Allow admins to delete unverified users
---
 .../src/components/users/DeleteUserPanel.vue       | 33 ++++++++++++++++++++++
 .../IdentityServiceUserManagementContainer.vue     |  6 ++++
 .../UnverifiedEmailUserManagementContainer.vue     | 17 +++++++++--
 .../src/components/users/UserDetailsContainer.vue  |  9 +++++-
 .../api/static/django_airavata_api/js/index.js     |  2 --
 django_airavata/apps/api/views.py                  |  7 +++--
 django_airavata/apps/auth/iam_admin_client.py      |  5 ++++
 7 files changed, 71 insertions(+), 8 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/DeleteUserPanel.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/DeleteUserPanel.vue
new file mode 100644
index 0000000..30fc748
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/DeleteUserPanel.vue
@@ -0,0 +1,33 @@
+<template>
+  <b-card header="Delete User">
+    <p class="card-text">
+      This will remove {{ username }} from the identity service.
+    </p>
+    <delete-button @delete="deleteUser">
+      Are you sure you want to delete {{ username }}?
+    </delete-button>
+  </b-card>
+</template>
+
+<script>
+import { components } from "django-airavata-common-ui";
+
+export default {
+  name: "delete-user-panel",
+  props: {
+    username: {
+      type: String,
+      required: true
+    }
+  },
+  components: {
+    "delete-button": components.DeleteButton
+  },
+  methods: {
+    deleteUser() {
+      this.$emit("delete-user", this.username);
+    }
+  }
+};
+</script>
+
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
index 1a40b3f..3f33a95 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
@@ -54,6 +54,7 @@
                   :editable-groups="editableGroups"
                   @groups-updated="groupsUpdated"
                   @enable-user="enableUser"
+                  @delete-user="deleteUser"
                 />
               </template>
             </b-table>
@@ -199,6 +200,11 @@ export default {
       services.IAMUserProfileService.enable({ lookup: username }).finally(() =>
         this.reloadUserProfiles()
       );
+    },
+    deleteUser(username) {
+      services.IAMUserProfileService.delete({ lookup: username }).finally(() =>
+        this.reloadUserProfiles()
+      );
     }
   }
 };
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UnverifiedEmailUserManagementContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UnverifiedEmailUserManagementContainer.vue
index 8c71046..8b066ff 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UnverifiedEmailUserManagementContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UnverifiedEmailUserManagementContainer.vue
@@ -33,6 +33,11 @@
                   :email="data.item.email"
                   @enable-user="enableUser"
                 />
+                <delete-user-panel
+                  v-if="!data.item.enabled && !data.item.emailVerified"
+                  :username="data.item.userId"
+                  @delete-user="deleteUser"
+                />
               </template>
             </b-table>
             <pager
@@ -118,6 +123,11 @@ export default {
         this.loadUnverifiedEmailUsers()
       );
     },
+    deleteUser(username) {
+      services.IAMUserProfileService.delete({ lookup: username }).finally(() =>
+        this.loadUnverifiedEmailUsers()
+      );
+    },
     loadUnverifiedEmailUsers() {
       return services.UnverifiedEmailUserProfileService.list({
         limit: 10
@@ -125,9 +135,10 @@ export default {
     },
     toggleDetails(row) {
       row.toggleDetails();
-      this.showingDetails[row.item.userId] = !this
-        .showingDetails[row.item.userId];
-    },
+      this.showingDetails[row.item.userId] = !this.showingDetails[
+        row.item.userId
+      ];
+    }
   }
 };
 </script>
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
index 8bd6bd8..6f6e5a8 100644
--- 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
@@ -13,12 +13,18 @@
       :email="iamUserProfile.email"
       @enable-user="$emit('enable-user', $event)"
     />
+    <delete-user-panel
+      v-if="!iamUserProfile.enabled && !iamUserProfile.emailVerified"
+      :username="iamUserProfile.userId"
+      @delete-user="$emit('delete-user', $event)"
+    />
   </div>
 </template>
 <script>
 import { models } from "django-airavata-api";
 import UserGroupMembershipEditor from "./UserGroupMembershipEditor";
 import EnableUserPanel from "./EnableUserPanel";
+import DeleteUserPanel from "./DeleteUserPanel";
 
 export default {
   name: "user-details-container",
@@ -34,7 +40,8 @@ export default {
   },
   components: {
     UserGroupMembershipEditor,
-    EnableUserPanel
+    EnableUserPanel,
+    DeleteUserPanel
   },
   data() {
     return {
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 9edc7d9..c704fb4 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
@@ -24,7 +24,6 @@ import GroupResourceProfile from "./models/GroupResourceProfile";
 import IAMUserProfile from "./models/IAMUserProfile";
 import InputDataObjectType from "./models/InputDataObjectType";
 import JobState from "./models/JobState";
-import ManagedUserProfile from "./models/ManagedUserProfile";
 import OutputDataObjectType from "./models/OutputDataObjectType";
 import ParallelismType from "./models/ParallelismType";
 import Project from "./models/Project";
@@ -81,7 +80,6 @@ const models = {
   IAMUserProfile,
   InputDataObjectType,
   JobState,
-  ManagedUserProfile,
   OutputDataObjectType,
   ParallelismType,
   Project,
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index d3a1db3..c6f3ece 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -1386,10 +1386,10 @@ class WorkspacePreferencesView(APIView):
         return Response(serializer.data)
 
 
-class IAMUserViewSet(mixins.CreateModelMixin,
-                     mixins.RetrieveModelMixin,
+class IAMUserViewSet(mixins.RetrieveModelMixin,
                      mixins.UpdateModelMixin,
                      mixins.ListModelMixin,
+                     mixins.DestroyModelMixin,
                      GenericAPIBackedViewSet):
     serializer_class = serializers.IAMUserProfile
     pagination_class = APIResultPagination
@@ -1422,6 +1422,9 @@ class IAMUserViewSet(mixins.CreateModelMixin,
             group_manager_client.removeUsersFromGroup(
                 self.authz_token, [user_id], group_id)
 
+    def perform_destroy(self, instance):
+        iam_admin_client.delete_user(instance['userId'])
+
     @detail_route(methods=['post'])
     def enable(self, request, user_id=None):
         iam_admin_client.enable_user(user_id)
diff --git a/django_airavata/apps/auth/iam_admin_client.py b/django_airavata/apps/auth/iam_admin_client.py
index 05e3b2b..113d2a8 100644
--- a/django_airavata/apps/auth/iam_admin_client.py
+++ b/django_airavata/apps/auth/iam_admin_client.py
@@ -37,6 +37,11 @@ def enable_user(username):
     return iamadmin_client_pool.enableUser(authz_token, username)
 
 
+def delete_user(username):
+    authz_token = utils.get_service_account_authz_token()
+    return iamadmin_client_pool.deleteUser(authz_token, username)
+
+
 def is_user_exist(username):
     authz_token = utils.get_service_account_authz_token()
     return iamadmin_client_pool.isUserExist(authz_token, username)


[airavata-django-portal] 01/06: AIRAVATA-3047 Separate service to load users with unverified emails

Posted by ma...@apache.org.
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 cab1814f2c5da1938c06e753aba6727fcd0f2bc0
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Sat Jun 15 16:44:00 2019 -0400

    AIRAVATA-3047 Separate service to load users with unverified emails
---
 ... => IdentityServiceUserManagementContainer.vue} |  19 +-
 .../UnverifiedEmailUserManagementContainer.vue     |  93 +++++++++
 .../src/components/users/UserDetailsContainer.vue  |  16 +-
 .../components/users/UserManagementContainer.vue   | 207 ++-------------------
 .../static/django_airavata_admin/src/router.js     |  12 +-
 django_airavata/apps/api/serializers.py            |  19 +-
 .../api/static/django_airavata_api/js/index.js     |   7 +-
 .../{ManagedUserProfile.js => IAMUserProfile.js}   |   2 +-
 ...serProfile.js => UnverifiedEmailUserProfile.js} |  11 +-
 .../django_airavata_api/js/service_config.js       |  16 +-
 django_airavata/apps/api/urls.py                   |   6 +-
 django_airavata/apps/api/views.py                  |  80 ++++++--
 12 files changed, 249 insertions(+), 239 deletions(-)

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/IdentityServiceUserManagementContainer.vue
similarity index 91%
copy from django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserManagementContainer.vue
copy to django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
index 92e5c25..dba6213 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/IdentityServiceUserManagementContainer.vue
@@ -2,11 +2,6 @@
   <div>
     <div class="row">
       <div class="col">
-        <h1 class="h4 mb-4">Manage Users</h1>
-      </div>
-    </div>
-    <div class="row">
-      <div class="col">
         <div class="card">
           <div class="card-body">
             <b-input-group>
@@ -57,7 +52,7 @@
                 slot-scope="data"
               >
                 <user-details-container
-                  :managed-user-profile="data.item"
+                  :iam-user-profile="data.item"
                   :editable-groups="editableGroups"
                   @groups-updated="groupsUpdated"
                 />
@@ -96,7 +91,7 @@ export default {
     UserDetailsContainer
   },
   created() {
-    services.ManagedUserProfileService.list({ limit: 10 }).then(
+    services.IAMUserProfileService.list({ limit: 10 }).then(
       users => (this.usersPaginator = users)
     );
     services.GroupService.list({ limit: -1 }).then(
@@ -166,10 +161,10 @@ export default {
     previous() {
       this.usersPaginator.previous();
     },
-    groupsUpdated(managedUserProfile) {
-      services.ManagedUserProfileService.update({
-        lookup: managedUserProfile.userId,
-        data: managedUserProfile
+    groupsUpdated(user) {
+      services.IAMUserProfileService.update({
+        lookup: user.userId,
+        data: user
       }).finally(() => {
         this.reloadUserProfiles();
       });
@@ -182,7 +177,7 @@ export default {
       if (this.search) {
         params["search"] = this.search;
       }
-      services.ManagedUserProfileService.list(params).then(
+      services.IAMUserProfileService.list(params).then(
         users => (this.usersPaginator = users)
       );
     },
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UnverifiedEmailUserManagementContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UnverifiedEmailUserManagementContainer.vue
new file mode 100644
index 0000000..5cb7edd
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UnverifiedEmailUserManagementContainer.vue
@@ -0,0 +1,93 @@
+<template>
+  <div>
+    <div class="row">
+      <div class="col">
+        <div class="card">
+          <div class="card-body">
+            <b-table
+              hover
+              :fields="fields"
+              :items="items"
+            >
+              <template
+                slot="creationTime"
+                slot-scope="data">
+                <human-date :date="data.value"/>
+              </template>
+            </b-table>
+            <pager
+              v-bind:paginator="usersPaginator"
+              v-on:next="next"
+              v-on:previous="previous"
+            ></pager>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import { components } from "django-airavata-common-ui";
+import { services } from "django-airavata-api";
+export default {
+  name: "unverified-email-user-management-container",
+  data() {
+    return {
+      usersPaginator: null,
+    };
+  },
+  components: {
+    pager: components.Pager,
+    'human-date': components.HumanDate,
+  },
+  created() {
+    services.UnverifiedEmailUserProfileService.list({ limit: 10 }).then(
+      users => (this.usersPaginator = users)
+    );
+  },
+  computed: {
+    fields() {
+      return [
+        {
+          label: "First Name",
+          key: "firstName"
+        },
+        {
+          label: "Last Name",
+          key: "lastName"
+        },
+        {
+          label: "Username",
+          key: "userId"
+        },
+        {
+          label: "Email",
+          key: "email"
+        },
+        {
+          label: "Email Verified",
+          key: "emailVerified"
+        },
+        {
+          label: "Created",
+          key: "creationTime"
+        },
+      ];
+    },
+    items() {
+      return this.usersPaginator
+        ? this.usersPaginator.results
+        : [];
+    },
+  },
+  methods: {
+    next() {
+      this.usersPaginator.next();
+    },
+    previous() {
+      this.usersPaginator.previous();
+    },
+  }
+};
+</script>
+
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
index 33f96c5..18ce57f 100644
--- 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
@@ -1,8 +1,8 @@
 <template>
   <user-group-membership-editor
-    v-model="localManagedUserProfile.groups"
+    v-model="localIAMUserProfile.groups"
     :editable-groups="editableGroups"
-    :airavata-internal-user-id="managedUserProfile.airavataInternalUserId"
+    :airavata-internal-user-id="iamUserProfile.airavataInternalUserId"
     @input="groupsUpdated"
   />
 </template>
@@ -13,8 +13,8 @@ import UserGroupMembershipEditor from "./UserGroupMembershipEditor";
 export default {
   name: "user-details-container",
   props: {
-    managedUserProfile: {
-      type: models.ManagedUserProfile,
+    iamUserProfile: {
+      type: models.IAMUserProfile,
       required: true
     },
     editableGroups: {
@@ -27,17 +27,17 @@ export default {
   },
   data() {
     return {
-      localManagedUserProfile: this.managedUserProfile.clone()
+      localIAMUserProfile: this.iamUserProfile.clone()
     };
   },
   watch: {
-    managedUserProfile(newValue) {
-      this.localManagedUserProfile = newValue.clone();
+    iamUserProfile(newValue) {
+      this.localIAMUserProfile = newValue.clone();
     }
   },
   methods: {
     groupsUpdated() {
-      this.$emit("groups-updated", this.localManagedUserProfile);
+      this.$emit("groups-updated", this.localIAMUserProfile);
     }
   }
 };
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 92e5c25..be47876 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
@@ -1,205 +1,38 @@
 <template>
   <div>
     <div class="row">
-      <div class="col">
+      <div class="col-auto mr-auto">
         <h1 class="h4 mb-4">Manage Users</h1>
       </div>
-    </div>
-    <div class="row">
-      <div class="col">
-        <div class="card">
-          <div class="card-body">
-            <b-input-group>
-              <b-form-input
-                v-model="search"
-                placeholder="Search by name, email or username"
-                @keydown.native.enter="searchUsers"
-              />
-              <b-input-group-append>
-                <b-button @click="resetSearch">Reset</b-button>
-                <b-button
-                  variant="primary"
-                  @click="searchUsers"
-                >Search</b-button>
-              </b-input-group-append>
-            </b-input-group>
-          </div>
-        </div>
-      </div>
-    </div>
-    <div class="row">
-      <div class="col">
-        <div class="card">
-          <div class="card-body">
-            <b-table
-              hover
-              :fields="fields"
-              :items="items"
-            >
-              <template
-                slot="creationTime"
-                slot-scope="data">
-                <human-date :date="data.value"/>
-              </template>
-              <template
-                slot="action"
-                slot-scope="data"
-              >
-                <b-button
-                  v-if="data.item.airavataUserProfileExists"
-                  @click="toggleDetails(data)"
-                >
-                  Edit Groups
-                </b-button>
-              </template>
-              <template
-                slot="row-details"
-                slot-scope="data"
-              >
-                <user-details-container
-                  :managed-user-profile="data.item"
-                  :editable-groups="editableGroups"
-                  @groups-updated="groupsUpdated"
-                />
-              </template>
-            </b-table>
-            <pager
-              v-bind:paginator="usersPaginator"
-              v-on:next="next"
-              v-on:previous="previous"
-            ></pager>
-          </div>
-        </div>
+      <div class="col-auto">
+        <b-dropdown :text="menuText">
+          <b-dropdown-item
+            :to="{ name: 'identity-service-users'}"
+            :exact="true"
+          >Identity Service</b-dropdown-item>
+          <b-dropdown-item
+            :to="{ name: 'unverified-email-users'}"
+            :exact="true"
+          >Unverified Emails</b-dropdown-item>
+        </b-dropdown>
       </div>
     </div>
+    <router-view></router-view>
   </div>
 </template>
 
 <script>
-import { services } from "django-airavata-api";
-import { components } from "django-airavata-common-ui";
-import UserDetailsContainer from "./UserDetailsContainer.vue";
-
 export default {
   name: "user-management-container",
-  data() {
-    return {
-      usersPaginator: null,
-      allGroups: null,
-      showingDetails: {},
-      search: null
-    };
-  },
-  components: {
-    pager: components.Pager,
-    'human-date': components.HumanDate,
-    UserDetailsContainer
-  },
-  created() {
-    services.ManagedUserProfileService.list({ limit: 10 }).then(
-      users => (this.usersPaginator = users)
-    );
-    services.GroupService.list({ limit: -1 }).then(
-      groups => (this.allGroups = groups)
-    );
-  },
   computed: {
-    fields() {
-      return [
-        {
-          label: "First Name",
-          key: "firstName"
-        },
-        {
-          label: "Last Name",
-          key: "lastName"
-        },
-        {
-          label: "Username",
-          key: "userId"
-        },
-        {
-          label: "Email",
-          key: "email"
-        },
-        {
-          label: "Enabled",
-          key: "enabled"
-        },
-        {
-          label: "Email Verified",
-          key: "emailVerified"
-        },
-        {
-          label: "Created",
-          key: "creationTime"
-        },
-        {
-          label: "Action",
-          key: "action"
-        }
-      ];
-    },
-    items() {
-      return this.usersPaginator
-        ? this.usersPaginator.results.map(u => {
-            const user = u.clone();
-            user._showDetails =
-              this.showingDetails[u.airavataInternalUserId] || false;
-            return user;
-          })
-        : [];
-    },
-    editableGroups() {
-      return this.allGroups
-        ? this.allGroups.filter(g => g.isAdmin || g.isOwner)
-        : [];
-    },
-    currentOffset() {
-      return this.usersPaginator ? this.usersPaginator.offset : 0;
-    }
-  },
-  methods: {
-    next() {
-      this.usersPaginator.next();
-    },
-    previous() {
-      this.usersPaginator.previous();
-    },
-    groupsUpdated(managedUserProfile) {
-      services.ManagedUserProfileService.update({
-        lookup: managedUserProfile.userId,
-        data: managedUserProfile
-      }).finally(() => {
-        this.reloadUserProfiles();
-      });
-    },
-    reloadUserProfiles() {
-      const params = {
-        limit: 10,
-        offset: this.currentOffset
-      };
-      if (this.search) {
-        params["search"] = this.search;
+    menuText() {
+      if (this.$route.name === "identity-service-users") {
+        return "Identity Service";
+      } else if (this.$route.name === "unverified-email-users") {
+        return "Unverified Emails";
+      } else {
+        return "Menu";
       }
-      services.ManagedUserProfileService.list(params).then(
-        users => (this.usersPaginator = users)
-      );
-    },
-    toggleDetails(row) {
-      row.toggleDetails();
-      this.showingDetails[row.item.airavataInternalUserId] = !this
-        .showingDetails[row.item.airavataInternalUserId];
-    },
-    searchUsers() {
-      // Reset paginator when starting a search
-      this.usersPaginator = null;
-      this.reloadUserProfiles();
-    },
-    resetSearch() {
-      this.usersPaginator = null;
-      this.search = null;
-      this.reloadUserProfiles();
     }
   }
 };
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/router.js b/django_airavata/apps/admin/static/django_airavata_admin/src/router.js
index b4070d4..8cf1357 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/router.js
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/router.js
@@ -10,6 +10,8 @@ import ExperimentStatisticsContainer from "./components/statistics/ExperimentSta
 import CredentialStoreDashboard from "./components/dashboards/CredentialStoreDashboard";
 import GatewayResourceProfileEditorContainer from "./components/gatewayprofile/GatewayResourceProfileEditorContainer.vue";
 import GroupComputeResourcePreference from "./components/admin/group_resource_preferences/GroupComputeResourcePreference";
+import IdentityServiceUserManagementContainer from "./components/users/IdentityServiceUserManagementContainer.vue";
+import UnverifiedEmailUserManagementContainer from "./components/users/UnverifiedEmailUserManagementContainer.vue";
 import UserManagementContainer from "./components/users/UserManagementContainer.vue";
 import VueRouter from "vue-router";
 
@@ -122,7 +124,15 @@ const routes = [
   {
     path: "/users",
     component: UserManagementContainer,
-    name: "users"
+    name: "users",
+    children: [
+      { path: "", component: IdentityServiceUserManagementContainer, name: "identity-service-users" },
+      {
+        path: "unverified-email",
+        component: UnverifiedEmailUserManagementContainer,
+        name: "unverified-email-users"
+      }
+    ]
   },
   {
     path: "/experiment-statistics",
diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index a33287d..94a9f2b 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -828,7 +828,7 @@ class WorkspacePreferencesSerializer(serializers.ModelSerializer):
         exclude = ('username',)
 
 
-class ManagedUserProfile(serializers.Serializer):
+class IAMUserProfile(serializers.Serializer):
     airavataInternalUserId = serializers.CharField()
     userId = serializers.CharField()
     gatewayId = serializers.CharField()
@@ -841,7 +841,7 @@ class ManagedUserProfile(serializers.Serializer):
     creationTime = UTCPosixTimestampDateTimeField()
     groups = GroupSerializer(many=True)
     url = FullyEncodedHyperlinkedIdentityField(
-        view_name='django_airavata_api:managed-user-profile-detail',
+        view_name='django_airavata_api:iam-user-profile-detail',
         lookup_field='userId',
         lookup_url_kwarg='user_id')
 
@@ -863,3 +863,18 @@ class ExperimentStatisticsSerializer(
     cancelledExperiments = ExperimentSummarySerializer(many=True)
     createdExperiments = ExperimentSummarySerializer(many=True)
     runningExperiments = ExperimentSummarySerializer(many=True)
+
+
+class UnverifiedEmailUserProfile(serializers.Serializer):
+    userId = serializers.CharField()
+    gatewayId = serializers.CharField()
+    email = serializers.CharField()
+    firstName = serializers.CharField()
+    lastName = serializers.CharField()
+    enabled = serializers.BooleanField()
+    emailVerified = serializers.BooleanField()
+    creationTime = UTCPosixTimestampDateTimeField()
+    url = FullyEncodedHyperlinkedIdentityField(
+        view_name='django_airavata_api:unverified-email-user-profile-detail',
+        lookup_field='userId',
+        lookup_url_kwarg='user_id')
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 a43fecf..9edc7d9 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
@@ -21,6 +21,7 @@ import Group from "./models/Group";
 import GroupComputeResourcePreference from "./models/GroupComputeResourcePreference";
 import GroupPermission from "./models/GroupPermission";
 import GroupResourceProfile from "./models/GroupResourceProfile";
+import IAMUserProfile from "./models/IAMUserProfile";
 import InputDataObjectType from "./models/InputDataObjectType";
 import JobState from "./models/JobState";
 import ManagedUserProfile from "./models/ManagedUserProfile";
@@ -77,6 +78,7 @@ const models = {
   GroupComputeResourcePreference,
   GroupPermission,
   GroupResourceProfile,
+  IAMUserProfile,
   InputDataObjectType,
   JobState,
   ManagedUserProfile,
@@ -113,7 +115,7 @@ const services = {
   GroupResourceProfileService: ServiceFactory.service("GroupResourceProfiles"),
   GroupService: ServiceFactory.service("Groups"),
   LocaJobSubmissionService,
-  ManagedUserProfileService: ServiceFactory.service("ManagedUserProfiles"),
+  IAMUserProfileService: ServiceFactory.service("IAMUserProfiles"),
   ParserService: ServiceFactory.service("Parsers"),
   ProjectService: ServiceFactory.service("Projects"),
   SCPDataMovementService,
@@ -124,6 +126,9 @@ const services = {
   StorageResourceService: ServiceFactory.service("StorageResources"),
   UnicoreDataMovementService,
   UnicoreJobSubmissionService,
+  UnverifiedEmailUserProfileService: ServiceFactory.service(
+    "UnverifiedEmailUsers"
+  ),
   UserProfileService,
   UserStoragePathService: ServiceFactory.service("UserStoragePaths"),
   WorkspacePreferencesService: ServiceFactory.service("WorkspacePreferences")
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ManagedUserProfile.js b/django_airavata/apps/api/static/django_airavata_api/js/models/IAMUserProfile.js
similarity index 87%
copy from django_airavata/apps/api/static/django_airavata_api/js/models/ManagedUserProfile.js
copy to django_airavata/apps/api/static/django_airavata_api/js/models/IAMUserProfile.js
index 3c87783..2b2e6f5 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ManagedUserProfile.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/IAMUserProfile.js
@@ -23,7 +23,7 @@ const FIELDS = [
   }
 ];
 
-export default class ManagedUserProfile extends BaseModel {
+export default class IAMUserProfile extends BaseModel {
   constructor(data = {}) {
     super(FIELDS, data);
   }
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ManagedUserProfile.js b/django_airavata/apps/api/static/django_airavata_api/js/models/UnverifiedEmailUserProfile.js
similarity index 53%
rename from django_airavata/apps/api/static/django_airavata_api/js/models/ManagedUserProfile.js
rename to django_airavata/apps/api/static/django_airavata_api/js/models/UnverifiedEmailUserProfile.js
index 3c87783..1b1b59b 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ManagedUserProfile.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/UnverifiedEmailUserProfile.js
@@ -1,9 +1,6 @@
 import BaseModel from "./BaseModel";
-import Group from "./Group";
 
 const FIELDS = [
-  "userModelVersion",
-  "airavataInternalUserId",
   "userId",
   "gatewayId",
   "email",
@@ -11,19 +8,13 @@ const FIELDS = [
   "lastName",
   "enabled",
   "emailVerified",
-  "airavataUserProfileExists",
   {
     name: "creationTime",
     type: 'date',
   },
-  {
-    name: "groups",
-    type: Group,
-    list: true
-  }
 ];
 
-export default class ManagedUserProfile extends BaseModel {
+export default class UnverifiedEmailUserProfile extends BaseModel {
   constructor(data = {}) {
     super(FIELDS, data);
   }
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
index ef94c66..ed76e06 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
@@ -13,12 +13,13 @@ import FullExperiment from "./models/FullExperiment";
 import GatewayResourceProfile from "./models/GatewayResourceProfile";
 import Group from "./models/Group";
 import GroupResourceProfile from "./models/GroupResourceProfile";
-import ManagedUserProfile from "./models/ManagedUserProfile";
+import IAMUserProfile from "./models/IAMUserProfile";
 import Parser from "./models/Parser";
 import Project from "./models/Project";
 import SharedEntity from "./models/SharedEntity";
 import StoragePreference from "./models/StoragePreference";
 import StorageResourceDescription from "./models/StorageResourceDescription";
+import UnverifiedEmailUserProfile from "./models/UnverifiedEmailUserProfile";
 import UserProfile from "./models/UserProfile";
 import UserStoragePath from "./models/UserStoragePath";
 import WorkspacePreferences from "./models/WorkspacePreferences";
@@ -239,12 +240,12 @@ export default {
     queryParams: ["limit", "offset"],
     modelClass: Group
   },
-  ManagedUserProfiles: {
-    url: "/api/managed-user-profiles",
+  IAMUserProfiles: {
+    url: "/api/iam-user-profiles",
     viewSet: true,
     pagination: true,
     queryParams: ["limit", "offset", "search"],
-    modelClass: ManagedUserProfile
+    modelClass: IAMUserProfile
   },
   Parsers: {
     url: "/api/parsers",
@@ -297,6 +298,13 @@ export default {
     },
     modelClass: StorageResourceDescription
   },
+  UnverifiedEmailUsers: {
+    url: "/api/unverified-email-users",
+    viewSet: true,
+    pagination: true,
+    queryParams: ["limit", "offset"],
+    modelClass: UnverifiedEmailUserProfile
+  },
   UserProfiles: {
     url: "/api/user-profiles",
     viewSet: ["list"],
diff --git a/django_airavata/apps/api/urls.py b/django_airavata/apps/api/urls.py
index 267da92..18d66ea 100644
--- a/django_airavata/apps/api/urls.py
+++ b/django_airavata/apps/api/urls.py
@@ -41,8 +41,10 @@ router.register(r'storage-preferences',
                 views.StoragePreferenceViewSet,
                 base_name='storage-preference')
 router.register(r'parsers', views.ParserViewSet, base_name='parser')
-router.register(r'managed-user-profiles', views.ManagedUserViewSet,
-                base_name='managed-user-profile')
+router.register(r'iam-user-profiles', views.IAMUserViewSet,
+                base_name='iam-user-profile')
+router.register(r'unverified-email-users', views.UnverifiedEmailUserViewSet,
+                base_name='unverified-email-user-profile')
 
 app_name = 'django_airavata_api'
 urlpatterns = [
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index e13bdd6..135f112 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -40,6 +40,7 @@ from django_airavata.apps.api.view_utils import (
     GenericAPIBackedViewSet
 )
 from django_airavata.apps.auth import iam_admin_client
+from django_airavata.apps.auth.models import EmailVerification
 
 from . import (
     data_products_helper,
@@ -1384,12 +1385,12 @@ class WorkspacePreferencesView(APIView):
         return Response(serializer.data)
 
 
-class ManagedUserViewSet(mixins.CreateModelMixin,
-                         mixins.RetrieveModelMixin,
-                         mixins.UpdateModelMixin,
-                         mixins.ListModelMixin,
-                         GenericAPIBackedViewSet):
-    serializer_class = serializers.ManagedUserProfile
+class IAMUserViewSet(mixins.CreateModelMixin,
+                     mixins.RetrieveModelMixin,
+                     mixins.UpdateModelMixin,
+                     mixins.ListModelMixin,
+                     GenericAPIBackedViewSet):
+    serializer_class = serializers.IAMUserProfile
     pagination_class = APIResultPagination
     lookup_field = 'user_id'
 
@@ -1398,11 +1399,11 @@ class ManagedUserViewSet(mixins.CreateModelMixin,
 
         convert_user_profile = self._convert_user_profile
 
-        class ManagedUsersResultIterator(APIResultIterator):
+        class IAMUsersResultIterator(APIResultIterator):
             def get_results(self, limit=-1, offset=0):
                 return map(convert_user_profile,
                            iam_admin_client.get_users(offset, limit, search))
-        return ManagedUsersResultIterator()
+        return IAMUsersResultIterator()
 
     def get_instance(self, lookup_value):
         return self._convert_user_profile(
@@ -1435,9 +1436,9 @@ class ManagedUserViewSet(mixins.CreateModelMixin,
             'email': user_profile.emails[0],
             'firstName': user_profile.firstName,
             'lastName': user_profile.lastName,
-            # TODO: fix this to distinguish between enabled and emailVerified
-            'enabled': user_profile.State == Status.CONFIRMED,
-            'emailVerified': user_profile.State == Status.CONFIRMED,
+            'enabled': user_profile.State == Status.ACTIVE,
+            'emailVerified': (user_profile.State == Status.CONFIRMED or
+                              user_profile.State == Status.ACTIVE),
             'airavataUserProfileExists': airavata_user_profile_exists,
             'creationTime': user_profile.creationTime,
             'groups': groups
@@ -1469,3 +1470,60 @@ class ExperimentStatisticsView(APIView):
         serializer = self.serializer_class(
             statistics, context={'request': request})
         return Response(serializer.data)
+
+
+class UnverifiedEmailUserViewSet(mixins.ListModelMixin,
+                                 mixins.RetrieveModelMixin,
+                                 GenericAPIBackedViewSet):
+    serializer_class = serializers.UnverifiedEmailUserProfile
+    pagination_class = APIResultPagination
+    lookup_field = 'user_id'
+
+    def get_list(self):
+        get_users = self._get_unverified_email_user_profiles
+
+        class UnverifiedEmailUsersResultIterator(APIResultIterator):
+            def get_results(self, limit=-1, offset=0):
+                return get_users(limit, offset)
+        return UnverifiedEmailUsersResultIterator()
+
+    def get_instance(self, lookup_value):
+        users = self._get_unverified_email_user_profiles(
+            limit=1, username=lookup_value)
+        if len(users) == 0:
+            raise Http404("No unverified email record found for user {}"
+                          .format(lookup_value))
+        else:
+            return users[0]
+
+    def _get_unverified_email_user_profiles(
+            self, limit=-1, offset=0, username=None):
+        unverified_emails = EmailVerification.objects.filter(
+            verified=False).order_by('username').values('username').distinct()
+        if username is not None:
+            unverified_emails = unverified_emails.filter(username=username)
+        if limit > 0:
+            unverified_emails = unverified_emails[offset:offset+limit]
+        results = []
+        for unverified_email in unverified_emails:
+            username = unverified_email['username']
+            user_profile = iam_admin_client.get_user(username)
+            if (user_profile.State == Status.CONFIRMED or
+                    user_profile.State == Status.ACTIVE):
+                # TODO: test this
+                EmailVerification.objects.filter(
+                    username=username).update(
+                    verified=True)
+                continue
+            results.append({
+                'userId': user_profile.userId,
+                'gatewayId': user_profile.gatewayId,
+                'email': user_profile.emails[0],
+                'firstName': user_profile.firstName,
+                'lastName': user_profile.lastName,
+                'enabled': user_profile.State == Status.ACTIVE,
+                'emailVerified': (user_profile.State == Status.CONFIRMED or
+                                  user_profile.State == Status.ACTIVE),
+                'creationTime': user_profile.creationTime,
+            })
+        return results


[airavata-django-portal] 04/06: AIRAVATA-3440 Regenerating thrift stubs

Posted by ma...@apache.org.
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 ff0ea1a8f5be97dbc5bd8473a0a91dd959ae0f0d
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Jun 17 10:46:22 2019 -0400

    AIRAVATA-3440 Regenerating thrift stubs
---
 .../iam/admin/services/cpi/IamAdminServices-remote |   7 +
 .../iam/admin/services/cpi/IamAdminServices.py     | 233 +++++++++++++++++++++
 2 files changed, 240 insertions(+)

diff --git a/airavata/service/profile/iam/admin/services/cpi/IamAdminServices-remote b/airavata/service/profile/iam/admin/services/cpi/IamAdminServices-remote
index f4a3f2b..876c1a7 100755
--- a/airavata/service/profile/iam/admin/services/cpi/IamAdminServices-remote
+++ b/airavata/service/profile/iam/admin/services/cpi/IamAdminServices-remote
@@ -35,6 +35,7 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('  bool resetUserPassword(AuthzToken authzToken, string username, string newPassword)')
     print('   findUsers(AuthzToken authzToken, string email, string userId)')
     print('  void updateUserProfile(AuthzToken authzToken, UserProfile userDetails)')
+    print('  bool deleteUser(AuthzToken authzToken, string username)')
     print('  bool addRoleToUser(AuthzToken authzToken, string username, string roleName)')
     print('  bool removeRoleFromUser(AuthzToken authzToken, string username, string roleName)')
     print('   getUsersWithRole(AuthzToken authzToken, string roleName)')
@@ -184,6 +185,12 @@ elif cmd == 'updateUserProfile':
         sys.exit(1)
     pp.pprint(client.updateUserProfile(eval(args[0]), eval(args[1]),))
 
+elif cmd == 'deleteUser':
+    if len(args) != 2:
+        print('deleteUser requires 2 args')
+        sys.exit(1)
+    pp.pprint(client.deleteUser(eval(args[0]), args[1],))
+
 elif cmd == 'addRoleToUser':
     if len(args) != 3:
         print('addRoleToUser requires 3 args')
diff --git a/airavata/service/profile/iam/admin/services/cpi/IamAdminServices.py b/airavata/service/profile/iam/admin/services/cpi/IamAdminServices.py
index 1288c82..4b0701c 100644
--- a/airavata/service/profile/iam/admin/services/cpi/IamAdminServices.py
+++ b/airavata/service/profile/iam/admin/services/cpi/IamAdminServices.py
@@ -113,6 +113,14 @@ class Iface(airavata.base.api.BaseAPI.Iface):
         """
         pass
 
+    def deleteUser(self, authzToken, username):
+        """
+        Parameters:
+         - authzToken
+         - username
+        """
+        pass
+
     def addRoleToUser(self, authzToken, username, roleName):
         """
         Parameters:
@@ -565,6 +573,43 @@ class Client(airavata.base.api.BaseAPI.Client, Iface):
             raise result.ae
         return
 
+    def deleteUser(self, authzToken, username):
+        """
+        Parameters:
+         - authzToken
+         - username
+        """
+        self.send_deleteUser(authzToken, username)
+        return self.recv_deleteUser()
+
+    def send_deleteUser(self, authzToken, username):
+        self._oprot.writeMessageBegin('deleteUser', TMessageType.CALL, self._seqid)
+        args = deleteUser_args()
+        args.authzToken = authzToken
+        args.username = username
+        args.write(self._oprot)
+        self._oprot.writeMessageEnd()
+        self._oprot.trans.flush()
+
+    def recv_deleteUser(self):
+        iprot = self._iprot
+        (fname, mtype, rseqid) = iprot.readMessageBegin()
+        if mtype == TMessageType.EXCEPTION:
+            x = TApplicationException()
+            x.read(iprot)
+            iprot.readMessageEnd()
+            raise x
+        result = deleteUser_result()
+        result.read(iprot)
+        iprot.readMessageEnd()
+        if result.success is not None:
+            return result.success
+        if result.Idse is not None:
+            raise result.Idse
+        if result.ae is not None:
+            raise result.ae
+        raise TApplicationException(TApplicationException.MISSING_RESULT, "deleteUser failed: unknown result")
+
     def addRoleToUser(self, authzToken, username, roleName):
         """
         Parameters:
@@ -695,6 +740,7 @@ class Processor(airavata.base.api.BaseAPI.Processor, Iface, TProcessor):
         self._processMap["resetUserPassword"] = Processor.process_resetUserPassword
         self._processMap["findUsers"] = Processor.process_findUsers
         self._processMap["updateUserProfile"] = Processor.process_updateUserProfile
+        self._processMap["deleteUser"] = Processor.process_deleteUser
         self._processMap["addRoleToUser"] = Processor.process_addRoleToUser
         self._processMap["removeRoleFromUser"] = Processor.process_removeRoleFromUser
         self._processMap["getUsersWithRole"] = Processor.process_getUsersWithRole
@@ -989,6 +1035,31 @@ class Processor(airavata.base.api.BaseAPI.Processor, Iface, TProcessor):
         oprot.writeMessageEnd()
         oprot.trans.flush()
 
+    def process_deleteUser(self, seqid, iprot, oprot):
+        args = deleteUser_args()
+        args.read(iprot)
+        iprot.readMessageEnd()
+        result = deleteUser_result()
+        try:
+            result.success = self._handler.deleteUser(args.authzToken, args.username)
+            msg_type = TMessageType.REPLY
+        except (TTransport.TTransportException, KeyboardInterrupt, SystemExit):
+            raise
+        except airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException as Idse:
+            msg_type = TMessageType.REPLY
+            result.Idse = Idse
+        except airavata.api.error.ttypes.AuthorizationException as ae:
+            msg_type = TMessageType.REPLY
+            result.ae = ae
+        except Exception as ex:
+            msg_type = TMessageType.EXCEPTION
+            logging.exception(ex)
+            result = TApplicationException(TApplicationException.INTERNAL_ERROR, 'Internal error')
+        oprot.writeMessageBegin("deleteUser", msg_type, seqid)
+        result.write(oprot)
+        oprot.writeMessageEnd()
+        oprot.trans.flush()
+
     def process_addRoleToUser(self, seqid, iprot, oprot):
         args = addRoleToUser_args()
         args.read(iprot)
@@ -2970,6 +3041,168 @@ class updateUserProfile_result(object):
         return not (self == other)
 
 
+class deleteUser_args(object):
+    """
+    Attributes:
+     - authzToken
+     - username
+    """
+
+    thrift_spec = (
+        None,  # 0
+        (1, TType.STRUCT, 'authzToken', (airavata.model.security.ttypes.AuthzToken, airavata.model.security.ttypes.AuthzToken.thrift_spec), None, ),  # 1
+        (2, TType.STRING, 'username', 'UTF8', None, ),  # 2
+    )
+
+    def __init__(self, authzToken=None, username=None,):
+        self.authzToken = authzToken
+        self.username = username
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 1:
+                if ftype == TType.STRUCT:
+                    self.authzToken = airavata.model.security.ttypes.AuthzToken()
+                    self.authzToken.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            elif fid == 2:
+                if ftype == TType.STRING:
+                    self.username = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
+            return
+        oprot.writeStructBegin('deleteUser_args')
+        if self.authzToken is not None:
+            oprot.writeFieldBegin('authzToken', TType.STRUCT, 1)
+            self.authzToken.write(oprot)
+            oprot.writeFieldEnd()
+        if self.username is not None:
+            oprot.writeFieldBegin('username', TType.STRING, 2)
+            oprot.writeString(self.username.encode('utf-8') if sys.version_info[0] == 2 else self.username)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        if self.authzToken is None:
+            raise TProtocolException(message='Required field authzToken is unset!')
+        if self.username is None:
+            raise TProtocolException(message='Required field username is unset!')
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+
+
+class deleteUser_result(object):
+    """
+    Attributes:
+     - success
+     - Idse
+     - ae
+    """
+
+    thrift_spec = (
+        (0, TType.BOOL, 'success', None, None, ),  # 0
+        (1, TType.STRUCT, 'Idse', (airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException, airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException.thrift_spec), None, ),  # 1
+        (2, TType.STRUCT, 'ae', (airavata.api.error.ttypes.AuthorizationException, airavata.api.error.ttypes.AuthorizationException.thrift_spec), None, ),  # 2
+    )
+
+    def __init__(self, success=None, Idse=None, ae=None,):
+        self.success = success
+        self.Idse = Idse
+        self.ae = ae
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 0:
+                if ftype == TType.BOOL:
+                    self.success = iprot.readBool()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 1:
+                if ftype == TType.STRUCT:
+                    self.Idse = airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException()
+                    self.Idse.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            elif fid == 2:
+                if ftype == TType.STRUCT:
+                    self.ae = airavata.api.error.ttypes.AuthorizationException()
+                    self.ae.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
+            return
+        oprot.writeStructBegin('deleteUser_result')
+        if self.success is not None:
+            oprot.writeFieldBegin('success', TType.BOOL, 0)
+            oprot.writeBool(self.success)
+            oprot.writeFieldEnd()
+        if self.Idse is not None:
+            oprot.writeFieldBegin('Idse', TType.STRUCT, 1)
+            self.Idse.write(oprot)
+            oprot.writeFieldEnd()
+        if self.ae is not None:
+            oprot.writeFieldBegin('ae', TType.STRUCT, 2)
+            self.ae.write(oprot)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+
+
 class addRoleToUser_args(object):
     """
     Attributes:


[airavata-django-portal] 03/06: AIRAVATA-3080 Allow admin to manually enable user

Posted by ma...@apache.org.
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 972ca02ac466e72a18369fb3d16ecdd1a8eb296f
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Sun Jun 16 18:49:51 2019 -0400

    AIRAVATA-3080 Allow admin to manually enable user
---
 .../src/components/users/EnableUserPanel.vue       | 31 ++++++++++++
 .../IdentityServiceUserManagementContainer.vue     | 20 ++++----
 .../UnverifiedEmailUserManagementContainer.vue     | 55 +++++++++++++++++++---
 .../src/components/users/UserDetailsContainer.vue  | 25 +++++++---
 .../django_airavata_api/js/service_config.js       |  7 +++
 django_airavata/apps/api/views.py                  |  8 ++++
 6 files changed, 124 insertions(+), 22 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/EnableUserPanel.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/EnableUserPanel.vue
new file mode 100644
index 0000000..a81ef82
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/EnableUserPanel.vue
@@ -0,0 +1,31 @@
+<template>
+  <b-card header="Enable User">
+    <p class="card-text">
+      Enable user {{ username }} to log in. By clicking <b>Enable</b> you are verifying that the user's email address
+      is {{ email }}
+    </p>
+    <b-button @click="enable">Enable</b-button>
+  </b-card>
+</template>
+
+<script>
+export default {
+  name: "enable-user-panel",
+  props: {
+    username: {
+      type: String,
+      required: true
+    },
+    email: {
+      type: String,
+      required: true
+    }
+  },
+  methods: {
+    enable() {
+      this.$emit("enable-user", this.username);
+    }
+  }
+};
+</script>
+
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
index dba6213..1a40b3f 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
@@ -33,18 +33,16 @@
             >
               <template
                 slot="creationTime"
-                slot-scope="data">
-                <human-date :date="data.value"/>
+                slot-scope="data"
+              >
+                <human-date :date="data.value" />
               </template>
               <template
                 slot="action"
                 slot-scope="data"
               >
-                <b-button
-                  v-if="data.item.airavataUserProfileExists"
-                  @click="toggleDetails(data)"
-                >
-                  Edit Groups
+                <b-button @click="toggleDetails(data)">
+                  Edit
                 </b-button>
               </template>
               <template
@@ -55,6 +53,7 @@
                   :iam-user-profile="data.item"
                   :editable-groups="editableGroups"
                   @groups-updated="groupsUpdated"
+                  @enable-user="enableUser"
                 />
               </template>
             </b-table>
@@ -87,7 +86,7 @@ export default {
   },
   components: {
     pager: components.Pager,
-    'human-date': components.HumanDate,
+    "human-date": components.HumanDate,
     UserDetailsContainer
   },
   created() {
@@ -195,6 +194,11 @@ export default {
       this.usersPaginator = null;
       this.search = null;
       this.reloadUserProfiles();
+    },
+    enableUser(username) {
+      services.IAMUserProfileService.enable({ lookup: username }).finally(() =>
+        this.reloadUserProfiles()
+      );
     }
   }
 };
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UnverifiedEmailUserManagementContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UnverifiedEmailUserManagementContainer.vue
index 5cb7edd..8c71046 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UnverifiedEmailUserManagementContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UnverifiedEmailUserManagementContainer.vue
@@ -11,8 +11,28 @@
             >
               <template
                 slot="creationTime"
-                slot-scope="data">
-                <human-date :date="data.value"/>
+                slot-scope="data"
+              >
+                <human-date :date="data.value" />
+              </template>
+              <template
+                slot="action"
+                slot-scope="data"
+              >
+                <b-button @click="toggleDetails(data)">
+                  Edit
+                </b-button>
+              </template>
+              <template
+                slot="row-details"
+                slot-scope="data"
+              >
+                <enable-user-panel
+                  v-if="!data.item.enabled && !data.item.emailVerified"
+                  :username="data.item.userId"
+                  :email="data.item.email"
+                  @enable-user="enableUser"
+                />
               </template>
             </b-table>
             <pager
@@ -29,16 +49,20 @@
 <script>
 import { components } from "django-airavata-common-ui";
 import { services } from "django-airavata-api";
+import EnableUserPanel from "./EnableUserPanel";
+
 export default {
   name: "unverified-email-user-management-container",
   data() {
     return {
       usersPaginator: null,
+      showingDetails: {}
     };
   },
   components: {
     pager: components.Pager,
-    'human-date': components.HumanDate,
+    "human-date": components.HumanDate,
+    EnableUserPanel
   },
   created() {
     services.UnverifiedEmailUserProfileService.list({ limit: 10 }).then(
@@ -72,13 +96,15 @@ export default {
           label: "Created",
           key: "creationTime"
         },
+        {
+          label: "Action",
+          key: "action"
+        }
       ];
     },
     items() {
-      return this.usersPaginator
-        ? this.usersPaginator.results
-        : [];
-    },
+      return this.usersPaginator ? this.usersPaginator.results : [];
+    }
   },
   methods: {
     next() {
@@ -87,6 +113,21 @@ export default {
     previous() {
       this.usersPaginator.previous();
     },
+    enableUser(username) {
+      services.IAMUserProfileService.enable({ lookup: username }).finally(() =>
+        this.loadUnverifiedEmailUsers()
+      );
+    },
+    loadUnverifiedEmailUsers() {
+      return services.UnverifiedEmailUserProfileService.list({
+        limit: 10
+      }).then(users => (this.usersPaginator = users));
+    },
+    toggleDetails(row) {
+      row.toggleDetails();
+      this.showingDetails[row.item.userId] = !this
+        .showingDetails[row.item.userId];
+    },
   }
 };
 </script>
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
index 18ce57f..8bd6bd8 100644
--- 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
@@ -1,14 +1,24 @@
 <template>
-  <user-group-membership-editor
-    v-model="localIAMUserProfile.groups"
-    :editable-groups="editableGroups"
-    :airavata-internal-user-id="iamUserProfile.airavataInternalUserId"
-    @input="groupsUpdated"
-  />
+  <div>
+    <user-group-membership-editor
+      v-if="iamUserProfile.airavataUserProfileExists"
+      v-model="localIAMUserProfile.groups"
+      :editable-groups="editableGroups"
+      :airavata-internal-user-id="iamUserProfile.airavataInternalUserId"
+      @input="groupsUpdated"
+    />
+    <enable-user-panel
+      v-if="!iamUserProfile.enabled && !iamUserProfile.emailVerified"
+      :username="iamUserProfile.userId"
+      :email="iamUserProfile.email"
+      @enable-user="$emit('enable-user', $event)"
+    />
+  </div>
 </template>
 <script>
 import { models } from "django-airavata-api";
 import UserGroupMembershipEditor from "./UserGroupMembershipEditor";
+import EnableUserPanel from "./EnableUserPanel";
 
 export default {
   name: "user-details-container",
@@ -23,7 +33,8 @@ export default {
     }
   },
   components: {
-    UserGroupMembershipEditor
+    UserGroupMembershipEditor,
+    EnableUserPanel
   },
   data() {
     return {
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
index ed76e06..473305e 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
@@ -244,6 +244,13 @@ export default {
     url: "/api/iam-user-profiles",
     viewSet: true,
     pagination: true,
+    methods: {
+      enable: {
+        url: "/api/iam-user-profiles/<lookup>/enable/",
+        requestType: "post",
+        modelClass: IAMUserProfile
+      }
+    },
     queryParams: ["limit", "offset", "search"],
     modelClass: IAMUserProfile
   },
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index f22a4ea..d3a1db3 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -1422,6 +1422,14 @@ class IAMUserViewSet(mixins.CreateModelMixin,
             group_manager_client.removeUsersFromGroup(
                 self.authz_token, [user_id], group_id)
 
+    @detail_route(methods=['post'])
+    def enable(self, request, user_id=None):
+        iam_admin_client.enable_user(user_id)
+        instance = self.get_instance(user_id)
+        serializer = self.serializer_class(instance=instance,
+                                           context={'request': request})
+        return Response(serializer.data)
+
     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']


[airavata-django-portal] 02/06: AIRAVATA-3048 Only allow Admins to use user mgmt APIs

Posted by ma...@apache.org.
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 36ee0fcb61366998d042e120038413486bc2b72b
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Sun Jun 16 09:11:10 2019 -0400

    AIRAVATA-3048 Only allow Admins to use user mgmt APIs
---
 django_airavata/apps/api/view_utils.py | 9 ++++++++-
 django_airavata/apps/api/views.py      | 5 ++++-
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/django_airavata/apps/api/view_utils.py b/django_airavata/apps/api/view_utils.py
index 8e932da..3ad0e34 100644
--- a/django_airavata/apps/api/view_utils.py
+++ b/django_airavata/apps/api/view_utils.py
@@ -5,7 +5,7 @@ from datetime import datetime
 import pytz
 from django.conf import settings
 from django.http import Http404
-from rest_framework import mixins, pagination
+from rest_framework import mixins, pagination, permissions
 from rest_framework.response import Response
 from rest_framework.reverse import reverse
 from rest_framework.utils.urls import remove_query_param, replace_query_param
@@ -194,3 +194,10 @@ def convert_utc_iso8601_to_date(iso8601_utc_string):
     logger.debug("convert_utc_iso8601_to_date({})={}".format(
         iso8601_utc_string, timestamp))
     return timestamp
+
+
+class IsInAdminsGroupPermission(permissions.BasePermission):
+    message = "User must be member of the Admins group."
+
+    def has_permission(self, request, view):
+        return request.is_gateway_admin
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index 135f112..f22a4ea 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -37,7 +37,8 @@ from django_airavata.apps.api.view_utils import (
     APIBackedViewSet,
     APIResultIterator,
     APIResultPagination,
-    GenericAPIBackedViewSet
+    GenericAPIBackedViewSet,
+    IsInAdminsGroupPermission
 )
 from django_airavata.apps.auth import iam_admin_client
 from django_airavata.apps.auth.models import EmailVerification
@@ -1392,6 +1393,7 @@ class IAMUserViewSet(mixins.CreateModelMixin,
                      GenericAPIBackedViewSet):
     serializer_class = serializers.IAMUserProfile
     pagination_class = APIResultPagination
+    permission_classes = (IsInAdminsGroupPermission,)
     lookup_field = 'user_id'
 
     def get_list(self):
@@ -1477,6 +1479,7 @@ class UnverifiedEmailUserViewSet(mixins.ListModelMixin,
                                  GenericAPIBackedViewSet):
     serializer_class = serializers.UnverifiedEmailUserProfile
     pagination_class = APIResultPagination
+    permission_classes = (IsInAdminsGroupPermission,)
     lookup_field = 'user_id'
 
     def get_list(self):