You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2020/06/26 18:27:27 UTC

[cloudstack-primate] branch master updated: iam: account form with saml option (#170)

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

rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack-primate.git


The following commit(s) were added to refs/heads/master by this push:
     new be0db5e  iam: account form with saml option (#170)
be0db5e is described below

commit be0db5e8eda8d5e9be17680c0105dcfdaf5998b2
Author: Pearl Dsilva <pe...@gmail.com>
AuthorDate: Fri Jun 26 23:57:20 2020 +0530

    iam: account form with saml option (#170)
    
    - New accounts form with option to enable SAML when applicable
    - LDAP import form with saml support
    - SAML authorisation action form for users
    
    Co-authored-by: Pearl Dsilva <pe...@shapeblue.com>
    Co-authored-by: Abhishek Kumar <ab...@gmail.com>
    Co-authored-by: Rohit Yadav <ro...@shapeblue.com>
    Co-authored-by: davidjumani <dj...@gmail.com>
---
 src/config/section/account.js          |   3 +-
 src/config/section/user.js             |   8 +
 src/locales/en.json                    |   2 +
 src/views/iam/AddAccount.vue           | 419 +++++++++++++++++++++++++++++++++
 src/views/iam/AddLdapAccount.vue       |  62 ++++-
 src/views/iam/ConfigureSamlSsoAuth.vue | 138 +++++++++++
 6 files changed, 619 insertions(+), 13 deletions(-)

diff --git a/src/config/section/account.js b/src/config/section/account.js
index f465523..6c973d9 100644
--- a/src/config/section/account.js
+++ b/src/config/section/account.js
@@ -54,7 +54,8 @@ export default {
       icon: 'plus',
       label: 'label.add.account',
       listView: true,
-      args: ['username', 'password', 'confirmpassword', 'email', 'firstname', 'lastname', 'domainid', 'account', 'roleid', 'timezone', 'networkdomain']
+      popup: true,
+      component: () => import('@/views/iam/AddAccount.vue')
     },
     {
       api: 'ldapCreateAccount',
diff --git a/src/config/section/user.js b/src/config/section/user.js
index 689b229..176e36f 100644
--- a/src/config/section/user.js
+++ b/src/config/section/user.js
@@ -71,6 +71,14 @@ export default {
       show: (record) => { return record.state === 'enabled' }
     },
     {
+      api: 'authorizeSamlSso',
+      icon: 'form',
+      label: 'Configure SAML SSO Authorization',
+      dataView: true,
+      popup: true,
+      component: () => import('@/views/iam/ConfigureSamlSsoAuth.vue')
+    },
+    {
       api: 'deleteUser',
       icon: 'delete',
       label: 'label.action.delete.user',
diff --git a/src/locales/en.json b/src/locales/en.json
index 7008ce0..9626d59 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -1659,6 +1659,8 @@
 "label.rule.number": "Rule Number",
 "label.rules": "Rules",
 "label.running": "Running VMs",
+"label.saml.disable": "SAML Disable",
+"label.saml.enable": "SAML Enable",
 "label.s3.access.key": "Access Key",
 "label.s3.bucket": "Bucket",
 "label.s3.connection.timeout": "Connection Timeout",
diff --git a/src/views/iam/AddAccount.vue b/src/views/iam/AddAccount.vue
new file mode 100644
index 0000000..2c9130d
--- /dev/null
+++ b/src/views/iam/AddAccount.vue
@@ -0,0 +1,419 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <div class="form-layout">
+    <a-spin :spinning="loading">
+      <a-form :form="form" :loading="loading" @submit="handleSubmit" layout="vertical">
+        <a-form-item>
+          <span slot="label">
+            {{ $t('label.role') }}
+            <a-tooltip :title="apiParams.roleid.description">
+              <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+            </a-tooltip>
+          </span>
+          <a-select
+            v-decorator="['roleid', {
+              initialValue: selectedRole,
+              rules: [{ required: true, message: $t('message.error.select') }] }]"
+            :loading="roleLoading"
+            :placeholder="apiParams.roleid.description">
+            <a-select-option v-for="role in roles" :key="role.id">
+              {{ role.name + ' (' + role.type + ')' }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item>
+          <span slot="label">
+            {{ $t('label.username') }}
+            <a-tooltip :title="apiParams.username.description">
+              <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+            </a-tooltip>
+          </span>
+          <a-input
+            v-decorator="['username', {
+              rules: [{ required: true, message: $t('message.error.required.input') }]
+            }]"
+            :placeholder="apiParams.username.description" />
+        </a-form-item>
+        <a-row :gutter="12">
+          <a-col :md="24" :lg="12">
+            <a-form-item>
+              <span slot="label">
+                {{ $t('label.password') }}
+                <a-tooltip :title="apiParams.password.description">
+                  <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+                </a-tooltip>
+              </span>
+              <a-input-password
+                v-decorator="['password', {
+                  rules: [{ required: true, message: $t('message.error.required.input') }]
+                }]"
+                :placeholder="apiParams.password.description"/>
+            </a-form-item>
+          </a-col>
+          <a-col :md="24" :lg="12">
+            <a-form-item>
+              <span slot="label">
+                {{ $t('label.confirmpassword') }}
+                <a-tooltip :title="apiParams.password.description">
+                  <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+                </a-tooltip>
+              </span>
+              <a-input-password
+                v-decorator="['confirmpassword', {
+                  rules: [
+                    { required: true, message: $t('message.error.required.input') },
+                    { validator: validateConfirmPassword }
+                  ]
+                }]"
+                :placeholder="apiParams.password.description"/>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-form-item>
+          <span slot="label">
+            {{ $t('label.email') }}
+            <a-tooltip :title="apiParams.email.description">
+              <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+            </a-tooltip>
+          </span>
+          <a-input
+            v-decorator="['email', {
+              rules: [{ required: true, message: $t('message.error.required.input') }]
+            }]"
+            :placeholder="apiParams.email.description" />
+        </a-form-item>
+        <a-row :gutter="12">
+          <a-col :md="24" :lg="12">
+            <a-form-item>
+              <span slot="label">
+                {{ $t('label.firstname') }}
+                <a-tooltip :title="apiParams.firstname.description">
+                  <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+                </a-tooltip>
+              </span>
+              <a-input
+                v-decorator="['firstname', {
+                  rules: [{ required: true, message: $t('message.error.required.input') }]
+                }]"
+                :placeholder="apiParams.firstname.description" />
+            </a-form-item>
+          </a-col>
+          <a-col :md="24" :lg="12">
+            <a-form-item>
+              <span slot="label">
+                {{ $t('label.lastname') }}
+                <a-tooltip :title="apiParams.lastname.description">
+                  <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+                </a-tooltip>
+              </span>
+              <a-input
+                v-decorator="['lastname', {
+                  rules: [{ required: true, message: $t('message.error.required.input') }]
+                }]"
+                :placeholder="apiParams.lastname.description" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-form-item v-if="this.isAdminOrDomainAdmin()">
+          <span slot="label">
+            {{ $t('label.domain') }}
+            <a-tooltip :title="apiParams.domainid.description">
+              <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+            </a-tooltip>
+          </span>
+          <a-select
+            :loading="domainLoading"
+            v-decorator="['domainid', {
+              initialValue: selectedDomain,
+              rules: [{ required: true, message: $t('message.error.select') }] }]"
+            :placeholder="apiParams.domainid.description">
+            <a-select-option v-for="domain in domainsList" :key="domain.id">
+              {{ domain.name }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item>
+          <span slot="label">
+            {{ $t('label.account') }}
+            <a-tooltip :title="apiParams.account.description">
+              <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+            </a-tooltip>
+          </span>
+          <a-input v-decorator="['account']" :placeholder="apiParams.account.description" />
+        </a-form-item>
+        <a-form-item>
+          <span slot="label">
+            {{ $t('label.timezone') }}
+            <a-tooltip :title="apiParams.timezone.description">
+              <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+            </a-tooltip>
+          </span>
+          <a-select
+            showSearch
+            v-decorator="['timezone']"
+            :loading="timeZoneLoading">
+            <a-select-option v-for="opt in timeZoneMap" :key="opt.id">
+              {{ opt.name || opt.description }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item>
+          <span slot="label">
+            {{ $t('label.networkdomain') }}
+            <a-tooltip :title="apiParams.networkdomain.description">
+              <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+            </a-tooltip>
+          </span>
+          <a-input
+            v-decorator="['networkdomain']"
+            :placeholder="apiParams.networkdomain.description" />
+        </a-form-item>
+        <div v-if="'authorizeSamlSso' in $store.getters.apis">
+          <a-form-item :label="$t('label.samlenable')">
+            <a-switch v-decorator="['samlenable']" @change="checked => { this.samlEnable = checked }" />
+          </a-form-item>
+          <a-form-item v-if="samlEnable">
+            <span slot="label">
+              {{ $t('label.samlentity') }}
+              <a-tooltip :title="apiParams.entityid.description">
+                <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+              </a-tooltip>
+            </span>
+            <a-select
+              v-decorator="['samlentity', {
+                initialValue: selectedIdp,
+              }]"
+              :loading="idpLoading">
+              <a-select-option v-for="(idp, idx) in idps" :key="idx">
+                {{ idp.orgName }}
+              </a-select-option>
+            </a-select>
+          </a-form-item>
+        </div>
+        <div :span="24" class="action-button">
+          <a-button @click="closeAction">{{ this.$t('Cancel') }}</a-button>
+          <a-button :loading="loading" type="primary" @click="handleSubmit">{{ this.$t('OK') }}</a-button>
+        </div>
+      </a-form>
+    </a-spin>
+  </div>
+</template>
+<script>
+import { api } from '@/api'
+import { timeZone } from '@/utils/timezone'
+import debounce from 'lodash/debounce'
+
+export default {
+  name: 'AddAccountForm',
+  data () {
+    this.fetchTimeZone = debounce(this.fetchTimeZone, 800)
+    return {
+      loading: false,
+      domainLoading: false,
+      domainsList: [],
+      selectedDomain: '',
+      roleLoading: false,
+      roles: [],
+      selectedRole: '',
+      timeZoneLoading: false,
+      timeZoneMap: [],
+      samlEnable: false,
+      idpLoading: false,
+      idps: [],
+      selectedIdp: ''
+    }
+  },
+  beforeCreate () {
+    this.form = this.$form.createForm(this)
+    this.apiConfig = this.$store.getters.apis.createAccount || {}
+    this.apiParams = {}
+    this.apiConfig.params.forEach(param => {
+      this.apiParams[param.name] = param
+    })
+    this.apiConfig = this.$store.getters.apis.authorizeSamlSso || {}
+    this.apiConfig.params.forEach(param => {
+      this.apiParams[param.name] = param
+    })
+  },
+  mounted () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      this.fetchDomains()
+      this.fetchRoles()
+      this.fetchTimeZone()
+      if ('listIdps' in this.$store.getters.apis) {
+        this.fetchIdps()
+      }
+    },
+    isAdminOrDomainAdmin () {
+      return ['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype)
+    },
+    isValidValueForKey (obj, key) {
+      return key in obj && obj[key] != null
+    },
+    validateConfirmPassword (rule, value, callback) {
+      if (!value || value.length === 0) {
+        callback()
+      } else if (rule.field === 'confirmpassword') {
+        const form = this.form
+        const messageConfirm = this.$t('error.password.not.match')
+        const passwordVal = form.getFieldValue('password')
+        if (passwordVal && passwordVal !== value) {
+          callback(messageConfirm)
+        } else {
+          callback()
+        }
+      } else {
+        callback()
+      }
+    },
+    fetchDomains () {
+      this.domainLoading = true
+      api('listDomains', {
+        listAll: true,
+        details: 'min'
+      }).then(response => {
+        this.domainsList = response.listdomainsresponse.domain || []
+        this.selectedDomain = this.domainsList[0].id || ''
+      }).catch(error => {
+        this.$notification.error({
+          message: `Error ${error.response.status}`,
+          description: error.response.data.errorresponse.errortext
+        })
+      }).finally(() => {
+        this.domainLoading = false
+      })
+    },
+    fetchRoles () {
+      this.roleLoading = true
+      api('listRoles').then(response => {
+        this.roles = response.listrolesresponse.role || []
+        this.selectedRole = this.roles[0].id
+      }).finally(() => {
+        this.roleLoading = false
+      })
+    },
+    fetchTimeZone (value) {
+      this.timeZoneMap = []
+      this.timeZoneLoading = true
+
+      timeZone(value).then(json => {
+        this.timeZoneMap = json
+        this.timeZoneLoading = false
+      })
+    },
+    fetchIdps () {
+      this.idpLoading = true
+      api('listIdps').then(response => {
+        this.idps = response.listidpsresponse.idp || []
+        this.selectedIdp = this.idps[0].id || ''
+      }).finally(() => {
+        this.idpLoading = false
+      })
+    },
+    handleSubmit (e) {
+      e.preventDefault()
+      this.form.validateFields((err, values) => {
+        if (err) {
+          return
+        }
+        this.loading = true
+        const params = {
+          roleid: values.roleid,
+          username: values.username,
+          password: values.password,
+          email: values.email,
+          firstname: values.firstname,
+          lastname: values.lastname,
+          domainid: values.domainid
+        }
+        if (this.isValidValueForKey(values, 'account') && values.account.length > 0) {
+          params.account = values.account
+        }
+        if (this.isValidValueForKey(values, 'timezone') && values.timezone.length > 0) {
+          params.timezone = values.timezone
+        }
+        if (this.isValidValueForKey(values, 'networkdomain') && values.networkdomain.length > 0) {
+          params.networkdomain = values.networkdomain
+        }
+
+        api('createAccount', params).then(response => {
+          this.$emit('refresh-data')
+          this.$notification.success({
+            message: 'Create Account',
+            description: 'Successfully created account ' + params.username
+          })
+          const users = response.createaccountresponse.account.user
+          if (values.samlenable && users) {
+            for (var i = 0; i < users.length; i++) {
+              api('authorizeSamlSso', {
+                enable: values.samlenable,
+                entityid: values.samlentity,
+                userid: users[i].id
+              }).then(response => {
+                this.$notification.success({
+                  message: this.$t('samlenable'),
+                  description: 'Successfully enabled SAML Authorization'
+                })
+              }).catch(error => {
+                this.$notification.error({
+                  message: 'Request Failed',
+                  description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
+                  duration: 0
+                })
+              }).finally(() => {
+                this.loading = false
+                this.closeAction()
+              })
+            }
+          }
+        }).catch(error => {
+          this.$notification.error({
+            message: 'Request Failed',
+            description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
+            duration: 0
+          })
+        }).finally(() => {
+          this.loading = false
+          this.closeAction()
+        })
+      })
+    },
+    closeAction () {
+      this.$emit('close-action')
+    }
+  }
+}
+</script>
+<style scoped lang="less">
+  .form-layout {
+    width: 80vw;
+    @media (min-width: 600px) {
+      width: 450px;
+    }
+  }
+  .action-button {
+    text-align: right;
+    button {
+      margin-right: 5px;
+    }
+  }
+</style>
diff --git a/src/views/iam/AddLdapAccount.vue b/src/views/iam/AddLdapAccount.vue
index ce91f0c..e41c11a 100644
--- a/src/views/iam/AddLdapAccount.vue
+++ b/src/views/iam/AddLdapAccount.vue
@@ -112,6 +112,25 @@
                 :placeholder="apiParams.group.description"
               />
             </a-form-item>
+            <div v-if="'authorizeSamlSso' in $store.getters.apis">
+              <a-form-item :label="$t('label.samlenable')">
+                <a-switch v-decorator="['samlEnable']" @change="checked => { this.samlEnable = checked }" />
+              </a-form-item>
+              <a-form-item v-if="samlEnable" :label="$t('label.samlentity')">
+                <a-select
+                  v-decorator="['samlEntity', {
+                    initialValue: selectedIdp,
+                    rules: [{ required: samlEnable, message: `${this.$t('message.error.select')}` }]
+                  }]"
+                  placeholder="Choose SAML identity provider"
+                  :loading="loading">
+                  <a-select-option v-for="(idp, idx) in listIdps" :key="idx">
+                    {{ idp.orgName }}
+                  </a-select-option>
+                </a-select>
+              </a-form-item>
+            </div>
+
             <div class="card-footer">
               <a-button @click="handleClose">{{ $t('label.close') }}</a-button>
               <a-button :loading="loading" type="primary" @click="handleSubmit">{{ $t('label.add') }}</a-button>
@@ -139,6 +158,8 @@ export default {
       listDomains: [],
       listRoles: [],
       timeZoneMap: [],
+      listIdps: [],
+      selectedIdp: '',
       filters: [],
       selectedFilter: '',
       listLoading: false,
@@ -146,7 +167,8 @@ export default {
       domainLoading: false,
       roleLoading: false,
       loading: false,
-      searchQuery: undefined
+      searchQuery: undefined,
+      samlEnable: false
     }
   },
   beforeCreate () {
@@ -168,6 +190,7 @@ export default {
     this.dataSource = []
     this.listDomains = []
     this.listRoles = []
+    this.listIdps = []
     this.columns = [
       {
         title: this.$t('label.name'),
@@ -224,11 +247,13 @@ export default {
       const [
         listTimeZone,
         listDomains,
-        listRoles
+        listRoles,
+        listIdps
       ] = await Promise.all([
         this.fetchTimeZone(),
         this.fetchListDomains(),
-        this.fetchListRoles()
+        this.fetchListRoles(),
+        ('listIdps' in this.$store.getters.apis) ? this.fetchIdps() : []
       ]).catch(error => {
         this.$notifyError(error)
       }).finally(() => {
@@ -239,6 +264,7 @@ export default {
       this.timeZoneMap = listTimeZone && listTimeZone.length > 0 ? listTimeZone : []
       this.listDomains = listDomains && listDomains.length > 0 ? listDomains : []
       this.listRoles = listRoles && listRoles.length > 0 ? listRoles : []
+      this.listIdps = listIdps && listIdps.length > 0 ? listIdps : []
     },
     fetchTimeZone (value) {
       return new Promise((resolve, reject) => {
@@ -299,6 +325,19 @@ export default {
         })
       })
     },
+    fetchIdps () {
+      return new Promise((resolve, reject) => {
+        api('listIdps').then(json => {
+          const listIdps = json.listidpsresponse.idp || []
+          if (listIdps.length !== 0) {
+            this.selectedIdp = listIdps[0].id
+          }
+          resolve(listIdps)
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
     handleSubmit (e) {
       e.preventDefault()
       this.form.validateFields((err, values) => {
@@ -338,13 +377,13 @@ export default {
           })
         }
         this.loading = true
-        Promise.all(promises).then(response => {
-          for (let i = 0; i < response.length; i++) {
+        Promise.all(promises).then(responses => {
+          for (const response of responses) {
             if (apiName === 'ldapCreateAccount' && values.samlEnable) {
               const users = response.createaccountresponse.account.user
-              const entity = values.samlEntity
-              if (users && entity) {
-                this.authorizeUsersForSamlSSO(users, entity)
+              const entityId = values.samlEntity
+              if (users && entityId) {
+                this.authorizeUsersForSamlSSO(users, entityId)
               }
             } else if (apiName === 'importLdapUsers' && response.ldapuserresponse && values.samlEnable) {
               this.$notification.error({
@@ -355,12 +394,11 @@ export default {
               if (apiName === 'ldapCreateAccount') {
                 this.$notification.success({
                   message: this.$t('label.add.ldap.account'),
-                  description: response[i].createaccountresponse.account.name
+                  description: response.createaccountresponse.account.name
                 })
               }
             }
           }
-
           this.$emit('refresh-data')
           this.handleClose()
         }).catch(error => {
@@ -393,13 +431,13 @@ export default {
     handleClose () {
       this.$emit('close-action')
     },
-    authorizeUsersForSamlSSO (users, entity) {
+    authorizeUsersForSamlSSO (users, entityId) {
       const promises = []
       for (var i = 0; i < users.length; i++) {
         const params = {}
         params.enable = true
         params.userid = users[i].id
-        params.entityid = entity
+        params.entityid = entityId
         promises.push(new Promise((resolve, reject) => {
           api('authorizeSamlSso', params).catch(error => {
             reject(error)
diff --git a/src/views/iam/ConfigureSamlSsoAuth.vue b/src/views/iam/ConfigureSamlSsoAuth.vue
new file mode 100644
index 0000000..d8e213a
--- /dev/null
+++ b/src/views/iam/ConfigureSamlSsoAuth.vue
@@ -0,0 +1,138 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <div class="form-layout">
+    <a-form :form="form" @submit="handleSubmit" layout="vertical" :loading="loading">
+      <a-form-item :label="$t('label.samlenable')">
+        <a-switch
+          v-decorator="['samlEnable', {
+            initialValue: isSamlEnabled
+          }]"
+          :checked="isSamlEnabled"
+          @change="val => { isSamlEnabled = val }"
+        />
+      </a-form-item>
+      <a-form-item :label="$t('label.samlentity')">
+        <a-select
+          v-decorator="['samlEntity', {
+            initialValue: selectedIdp,
+          }]">
+          <a-select-option v-for="(idp, idx) in idps" :key="idx">
+            {{ idp.orgName }}
+          </a-select-option>
+        </a-select>
+      </a-form-item>
+      <div class="card-footer">
+        <a-button @click="handleClose">{{ $t('Close') }}</a-button>
+        <a-button :loading="loading" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
+      </div>
+    </a-form>
+  </div>
+</template>
+<script>
+import { api } from '@/api'
+export default {
+  name: 'ConfigureSamlSsoAuth',
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      selectedIdp: '',
+      idps: [],
+      isSamlEnabled: false,
+      loading: false
+    }
+  },
+  beforeCreate () {
+    this.form = this.$form.createForm(this)
+  },
+  mounted () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      this.IsUserSamlAuthorized()
+      this.loading = true
+      api('listIdps').then(response => {
+        this.idps = response.listidpsresponse.idp || []
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    IsUserSamlAuthorized () {
+      api('listSamlAuthorization', {
+        userid: this.resource.id
+      }).then(response => {
+        this.isSamlEnabled = response.listsamlauthorizationsresponse.samlauthorization[0].status || false
+        this.selectedIdp = response.listsamlauthorizationsresponse.samlauthorization[0].idpid || ''
+      })
+    },
+    handleClose () {
+      this.$emit('close-action')
+    },
+    handleSubmit (e) {
+      e.preventDefault()
+      this.form.validateFields((err, values) => {
+        if (err) {
+          return
+        }
+        api('authorizeSamlSso', {
+          enable: values.samlEnable,
+          userid: this.resource.id,
+          entityid: values.samlEntity
+        }).then(response => {
+          this.$notification.success({
+            message: values.samlEnable ? this.$t('label.saml.enable') : this.$t('label.saml.disable'),
+            description: values.samlEnable ? `Successfully enabled SAML Authorization for ${this.resource.username}`
+              : `Successfully disabled SAML Authorization for ${this.resource.username}`
+          })
+        }).catch(error => {
+          this.$notification.error({
+            message: 'Request Failed',
+            description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
+            duration: 0
+          })
+        }).finally(() => {
+          this.loading = false
+          this.handleClose()
+        })
+      })
+    }
+  }
+}
+</script>
+<style scoped lang="less">
+.form-layout {
+  width: 75vw;
+
+  @media (min-width: 700px) {
+    width: 40vw;
+  }
+}
+.card-footer {
+  text-align: right;
+
+  button + button {
+    margin-left: 8px;
+  }
+}
+</style>