mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
iam: Add LDAP Account Form (#116)
Add LDAP account feature Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com> Co-authored-by: Rohit Yadav <rohit@apache.org>
This commit is contained in:
parent
f3858e5297
commit
812932abb3
@ -32,7 +32,7 @@
|
||||
v-if="action.api in $store.getters.apis &&
|
||||
action.showBadge &&
|
||||
((!dataView && (action.listView || action.groupAction && selectedRowKeys.length > 0)) || (dataView && action.dataView)) &&
|
||||
('show' in action ? action.show(resource, $store.getters.userInfo) : true)">
|
||||
('show' in action ? action.show(resource, $store.getters) : true)">
|
||||
<a-button
|
||||
:icon="action.icon"
|
||||
:type="action.icon === 'delete' ? 'danger' : (action.icon === 'plus' ? 'primary' : 'default')"
|
||||
@ -44,7 +44,7 @@
|
||||
v-if="action.api in $store.getters.apis &&
|
||||
!action.showBadge &&
|
||||
((!dataView && (action.listView || action.groupAction && selectedRowKeys.length > 0)) || (dataView && action.dataView)) &&
|
||||
('show' in action ? action.show(resource, $store.getters.userInfo) : true)"
|
||||
('show' in action ? action.show(resource, $store.getters) : true)"
|
||||
:icon="action.icon"
|
||||
:type="action.icon === 'delete' ? 'danger' : (action.icon === 'plus' ? 'primary' : 'default')"
|
||||
shape="circle"
|
||||
|
||||
@ -37,7 +37,7 @@ export default {
|
||||
props: {
|
||||
osId: {
|
||||
type: String,
|
||||
required: true
|
||||
default: ''
|
||||
},
|
||||
osName: {
|
||||
type: String,
|
||||
|
||||
@ -112,6 +112,17 @@ export default {
|
||||
listView: true,
|
||||
args: ['username', 'password', 'password', 'email', 'firstname', 'lastname', 'domainid', 'account', 'roleid', 'timezone', 'networkdomain']
|
||||
},
|
||||
{
|
||||
api: 'ldapCreateAccount',
|
||||
icon: 'user-add',
|
||||
label: 'label.add.ldap.account',
|
||||
listView: true,
|
||||
popup: true,
|
||||
show: (record, store) => {
|
||||
return store.isLdapEnabled
|
||||
},
|
||||
component: () => import('@/views/iam/AddLdapAccount.vue')
|
||||
},
|
||||
{
|
||||
api: 'updateAccount',
|
||||
icon: 'edit',
|
||||
|
||||
@ -91,7 +91,7 @@ export default {
|
||||
label: 'Update template permissions',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
show: (record, user) => { return (['Admin', 'DomainAdmin'].includes(user.roletype) && (record.domainid === user.domainid && record.account === user.account) || record.templatetype !== 'BUILTIN') },
|
||||
show: (record, store) => { return (['Admin', 'DomainAdmin'].includes(store.userInfo.roletype) && (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account) || record.templatetype !== 'BUILTIN') },
|
||||
component: () => import('@/views/image/UpdateTemplatePermissions')
|
||||
},
|
||||
{
|
||||
|
||||
@ -95,7 +95,7 @@ export default {
|
||||
label: 'Add Account to Project',
|
||||
dataView: true,
|
||||
args: ['projectid', 'account', 'email'],
|
||||
show: (record, user) => { return record.account === user.account || ['Admin', 'DomainAdmin'].includes(user.roletype) },
|
||||
show: (record, store) => { return record.account === store.userInfo.account || ['Admin', 'DomainAdmin'].includes(store.userInfo.roletype) },
|
||||
mapping: {
|
||||
projectid: {
|
||||
value: (record) => { return record.id }
|
||||
|
||||
@ -29,7 +29,8 @@ const getters = {
|
||||
userInfo: state => state.user.info,
|
||||
addRouters: state => state.permission.addRouters,
|
||||
multiTab: state => state.app.multiTab,
|
||||
asyncJobIds: state => state.user.asyncJobIds
|
||||
asyncJobIds: state => state.user.asyncJobIds,
|
||||
isLdapEnabled: state => state.user.isLdapEnabled
|
||||
}
|
||||
|
||||
export default getters
|
||||
|
||||
@ -31,7 +31,8 @@ const user = {
|
||||
apis: {},
|
||||
features: {},
|
||||
project: {},
|
||||
asyncJobIds: []
|
||||
asyncJobIds: [],
|
||||
isLdapEnabled: false
|
||||
},
|
||||
|
||||
mutations: {
|
||||
@ -62,6 +63,9 @@ const user = {
|
||||
Vue.ls.set(ASYNC_JOB_IDS, jobsJsonArray)
|
||||
state.asyncJobIds = jobsJsonArray
|
||||
},
|
||||
SET_LDAP: (state, isLdapEnabled) => {
|
||||
state.isLdapEnabled = isLdapEnabled
|
||||
},
|
||||
RESET_THEME: (state) => {
|
||||
Vue.ls.set(DEFAULT_THEME, 'light')
|
||||
}
|
||||
@ -126,6 +130,13 @@ const user = {
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
|
||||
api('listLdapConfigurations').then(response => {
|
||||
const ldapEnable = (response.ldapconfigurationresponse.count > 0)
|
||||
commit('SET_LDAP', ldapEnable)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
Logout ({ commit, state }) {
|
||||
|
||||
412
ui/src/views/iam/AddLdapAccount.vue
Normal file
412
ui/src/views/iam/AddLdapAccount.vue
Normal file
@ -0,0 +1,412 @@
|
||||
// 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="ldap-account-layout">
|
||||
<a-row :gutter="0">
|
||||
<a-col :md="24" :lg="16">
|
||||
<a-card :bordered="false">
|
||||
<a-input-search
|
||||
style="margin-bottom: 10px"
|
||||
placeholder="Search"
|
||||
v-model="searchQuery"
|
||||
@search="handleSearch" />
|
||||
<a-table
|
||||
size="small"
|
||||
:loading="listLoading"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:rowSelection="{
|
||||
columnWidth: 50,
|
||||
selectedRowKeys: selectedRowKeys,
|
||||
onChange: onSelectChange
|
||||
}"
|
||||
:rowKey="record => record.username"
|
||||
:rowClassName="getRowClassName"
|
||||
:pagination="false"
|
||||
style="overflow-y: auto"
|
||||
:scroll="{ y: '50vh' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :md="24" :lg="8">
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
:form="form"
|
||||
@submit="handleSubmit"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item :label="$t('domain')">
|
||||
<a-select
|
||||
showSearch
|
||||
v-decorator="['domainid', {
|
||||
rules: [{ required: true, message: 'Please select option' }]
|
||||
}]"
|
||||
:placeholder="apiParams.domainid.description"
|
||||
:loading="domainLoading">
|
||||
<a-select-option v-for="opt in listDomains" :key="opt.name">
|
||||
{{ opt.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('account')">
|
||||
<a-input
|
||||
v-decorator="['account']"
|
||||
:placeholder="apiParams.account.description"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('role')">
|
||||
<a-select
|
||||
showSearch
|
||||
v-decorator="['roleid', {
|
||||
rules: [{ required: true, message: 'Please select option' }]
|
||||
}]"
|
||||
:placeholder="apiParams.roleid.description"
|
||||
:loading="roleLoading">
|
||||
<a-select-option v-for="opt in listRoles" :key="opt.name">
|
||||
{{ opt.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('timezone')">
|
||||
<a-select
|
||||
showSearch
|
||||
v-decorator="['timezone']"
|
||||
:placeholder="apiParams.timezone.description"
|
||||
: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 :label="$t('networkdomain')">
|
||||
<a-input
|
||||
v-decorator="['networkdomain']"
|
||||
:placeholder="apiParams.networkdomain.description"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('group')">
|
||||
<a-input
|
||||
v-decorator="['group']"
|
||||
:placeholder="apiParams.group.description"
|
||||
/>
|
||||
</a-form-item>
|
||||
<div class="card-footer">
|
||||
<a-button @click="handleClose">{{ $t('Close') }}</a-button>
|
||||
<a-button :loading="loading" type="primary" @click="handleSubmit">{{ $t('add') }}</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import { timeZone } from '@/utils/timezone'
|
||||
|
||||
export default {
|
||||
name: 'AddLdapAccount',
|
||||
data () {
|
||||
return {
|
||||
columns: [],
|
||||
dataSource: [],
|
||||
oldDataSource: [],
|
||||
selectedRowKeys: [],
|
||||
listDomains: [],
|
||||
listRoles: [],
|
||||
timeZoneMap: [],
|
||||
listLoading: false,
|
||||
timeZoneLoading: false,
|
||||
domainLoading: false,
|
||||
roleLoading: false,
|
||||
loading: false,
|
||||
searchQuery: undefined
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
this.form = this.$form.createForm(this)
|
||||
this.apiLdapCreateAccountConfig = this.$store.getters.apis.ldapCreateAccount || {}
|
||||
this.apiImportLdapUsersConfig = this.$store.getters.apis.importLdapUsers || {}
|
||||
this.apiParams = {}
|
||||
this.apiLdapCreateAccountConfig.params.forEach(param => {
|
||||
this.apiParams[param.name] = param
|
||||
})
|
||||
this.apiImportLdapUsersConfig.params.forEach(param => {
|
||||
if (!this.apiParams || !this.apiParams[param.name]) {
|
||||
this.apiParams[param.name] = param
|
||||
}
|
||||
})
|
||||
},
|
||||
created () {
|
||||
this.selectedRowKeys = []
|
||||
this.dataSource = []
|
||||
this.listDomains = []
|
||||
this.listRoles = []
|
||||
this.columns = [
|
||||
{
|
||||
title: this.$t('name'),
|
||||
dataIndex: 'name',
|
||||
width: 120,
|
||||
scopedSlots: { customRender: 'name' }
|
||||
},
|
||||
{
|
||||
title: this.$t('username'),
|
||||
dataIndex: 'username',
|
||||
width: 120,
|
||||
scopedSlots: { customRender: 'username' }
|
||||
},
|
||||
{
|
||||
title: this.$t('email'),
|
||||
dataIndex: 'email',
|
||||
scopedSlots: { customRender: 'email' }
|
||||
}
|
||||
]
|
||||
},
|
||||
mounted () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
async fetchData () {
|
||||
this.listLoading = true
|
||||
this.timeZoneLoading = true
|
||||
this.domainLoading = true
|
||||
this.roleLoading = true
|
||||
const [
|
||||
listTimeZone,
|
||||
listLdapUsers,
|
||||
listDomains,
|
||||
listRoles
|
||||
] = await Promise.all([
|
||||
this.fetchTimeZone(),
|
||||
this.fetchListLdapUsers(),
|
||||
this.fetchListDomains(),
|
||||
this.fetchListRoles()
|
||||
]).catch(error => {
|
||||
this.$notification.error({
|
||||
message: 'Request Failed',
|
||||
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
|
||||
})
|
||||
}).finally(() => {
|
||||
this.listLoading = false
|
||||
this.timeZoneLoading = false
|
||||
this.domainLoading = false
|
||||
this.roleLoading = false
|
||||
})
|
||||
this.timeZoneMap = listTimeZone && listTimeZone.length > 0 ? listTimeZone : []
|
||||
this.listDomains = listDomains && listDomains.length > 0 ? listDomains : []
|
||||
this.listRoles = listRoles && listRoles.length > 0 ? listRoles : []
|
||||
this.dataSource = listLdapUsers
|
||||
this.oldDataSource = listLdapUsers
|
||||
},
|
||||
fetchTimeZone (value) {
|
||||
return new Promise((resolve, reject) => {
|
||||
timeZone(value).then(json => {
|
||||
resolve(json)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
fetchListLdapUsers () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const params = {}
|
||||
params.listtype = 'new'
|
||||
api('listLdapUsers', params).then(json => {
|
||||
const listLdapUsers = json.ldapuserresponse.LdapUser
|
||||
if (listLdapUsers) {
|
||||
const ldapUserLength = listLdapUsers.length
|
||||
for (let i = 0; i < ldapUserLength; i++) {
|
||||
listLdapUsers[i].name = [listLdapUsers[i].firstname, listLdapUsers[i].lastname].join(' ')
|
||||
}
|
||||
}
|
||||
resolve(listLdapUsers)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
fetchListDomains () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const params = {}
|
||||
api('listDomains', params).then(json => {
|
||||
const listDomains = json.listdomainsresponse.domain
|
||||
resolve(listDomains)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
fetchListRoles () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const params = {}
|
||||
api('listRoles', params).then(json => {
|
||||
const listRoles = json.listrolesresponse.role
|
||||
resolve(listRoles)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (err || this.selectedRowKeys.length === 0) {
|
||||
return
|
||||
}
|
||||
let apiName = 'ldapCreateAccount'
|
||||
const domain = this.listDomains.filter(item => item.name === values.domainid)
|
||||
const role = this.listRoles.filter(item => item.name === values.roleid)
|
||||
const promises = []
|
||||
const params = {}
|
||||
params.domainid = domain[0].id
|
||||
params.roleid = role[0].id
|
||||
params.timezone = values.timezone
|
||||
params.networkdomain = values.networkdomain
|
||||
params.group = values.group
|
||||
if (params.group && params.group.trim().length > 0) {
|
||||
apiName = 'importLdapUsers'
|
||||
}
|
||||
this.selectedRowKeys.forEach(username => {
|
||||
params.username = username
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
api(apiName, params).then(json => {
|
||||
resolve(json)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
}))
|
||||
})
|
||||
this.loading = true
|
||||
Promise.all(promises).then(response => {
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
if (apiName === 'ldapCreateAccount' && values.samlEnable) {
|
||||
const users = response.createaccountresponse.account.user
|
||||
const entity = values.samlEntity
|
||||
if (users && entity) {
|
||||
this.authorizeUsersForSamlSSO(users, entity)
|
||||
}
|
||||
} else if (apiName === 'importLdapUsers' && response.ldapuserresponse && values.samlEnable) {
|
||||
this.$notification.error({
|
||||
message: 'Request Failed',
|
||||
description: 'Unable to find users IDs to enable SAML Single Sign On, kindly enable it manually.'
|
||||
})
|
||||
} else {
|
||||
if (apiName === 'ldapCreateAccount') {
|
||||
this.$notification.success({
|
||||
message: this.$t('label.add.ldap.account'),
|
||||
description: response[i].createaccountresponse.account.name
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.$emit('refresh-data')
|
||||
this.handleClose()
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: 'Request Failed',
|
||||
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
|
||||
})
|
||||
this.$emit('refresh-data')
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
handleSearch () {
|
||||
this.dataSource = this.oldDataSource
|
||||
if (!this.searchQuery || this.searchQuery.length === 0) {
|
||||
return
|
||||
}
|
||||
this.dataSource = this.dataSource.filter(item => this.filterLdapUser(item))
|
||||
},
|
||||
filterLdapUser (item) {
|
||||
switch (true) {
|
||||
case (item.name && item.name.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1):
|
||||
return item
|
||||
case (item.username && item.username.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1):
|
||||
return item
|
||||
case (item.email && item.email.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1):
|
||||
return item
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
handleClose () {
|
||||
this.$emit('close-action')
|
||||
},
|
||||
authorizeUsersForSamlSSO (users, entity) {
|
||||
const promises = []
|
||||
for (var i = 0; i < users.length; i++) {
|
||||
const params = {}
|
||||
params.enable = true
|
||||
params.userid = users[i].id
|
||||
params.entityid = entity
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
api('authorizeSamlSso', params).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
}))
|
||||
}
|
||||
Promise.all(promises).catch(error => {
|
||||
this.$notification.error({
|
||||
message: 'Request Failed',
|
||||
description: error.response.headers['x-description']
|
||||
})
|
||||
})
|
||||
},
|
||||
onSelectChange (selectedRowKeys) {
|
||||
this.selectedRowKeys = selectedRowKeys
|
||||
},
|
||||
getRowClassName (record, index) {
|
||||
if (index % 2 === 0) {
|
||||
return 'light-row'
|
||||
}
|
||||
return 'dark-row'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ldap-account-layout {
|
||||
width: 85vw;
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
width: 900px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
text-align: right;
|
||||
|
||||
button + button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .light-row {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/deep/ .dark-row {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user