diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/locales/en-US.json | 26 | ||||
-rw-r--r-- | src/store/modules/AccessControl/LdapStore.js | 117 | ||||
-rw-r--r-- | src/views/AccessControl/Ldap/Ldap.vue | 87 | ||||
-rw-r--r-- | src/views/AccessControl/Ldap/ModalAddRoleGroup.vue | 164 | ||||
-rw-r--r-- | src/views/AccessControl/Ldap/TableRoleGroups.vue | 240 |
5 files changed, 585 insertions, 49 deletions
diff --git a/src/locales/en-US.json b/src/locales/en-US.json index d62ff326..9a34b65b 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -9,6 +9,7 @@ "delete": "Delete", "disable": "Disable", "download": "Download", + "edit": "Edit", "enable": "Enable", "filter": "Filter", "replace": "Replace", @@ -92,7 +93,9 @@ }, "pageLdap": { "pageDescription": "Configure LDAP settings and manage role groups", + "roleGroups": "Role groups", "settings": "Settings", + "addRoleGroup": "Add role group", "ariaLabel": { "ldapSettings": "LDAP settings" }, @@ -103,7 +106,6 @@ "caCertificateValidUntil": "CA Certificate valid until", "groupIdAttribute": "Group ID attribute", "ldapAuthentication": "LDAP authentication", - "ldapAuthenticationHelper": "Must be enabled to modify role groups", "ldapCertificateValidUntil": "LDAP Certificate valid until", "manageSslCertificates": "Manage SSL certificates", "secureLdapHelper": "A CA certificate and an LDAP certificate are required to enable secure LDAP", @@ -113,11 +115,31 @@ "serviceType": "Service type", "userIdAttribute": "User ID attribute" }, + "modal": { + "addNewRoleGroup": "Add new role group", + "deleteRoleGroupBatchConfirmMessage": "Are you sure you want to delete %{count} role group? This action cannot be undone. | Are you sure you want to delete %{count} role groups? This action cannot be undone.", + "deleteRoleGroupConfirmMessage": "Are you sure you want to delete '%{groupName}'? This action cannot be undone.", + "deleteRoleGroup": "Delete role group", + "editRoleGroup": "Edit role group", + "groupName": "Group name", + "groupPrivilege": "Group privilege" + }, + "tableRoleGroups": { + "alertContent": "LDAP authentication must be enabled to modify role groups.", + "groupName": "Group name", + "groupPrivilege": "Group privilege" + }, "toast": { + "errorAddRoleGroup": "Error adding role group.", + "errorDeleteRoleGroup": "Error deleting role group. | Error deleting role groups.", "errorSaveActiveDirectorySettings": "Error saving Active Directory settings.", "errorSaveLdapSettings": "Error saving Open LDAP settings.", + "errorSaveRoleGroup": "Error saving role group.", + "successAddRoleGroup": "Successfully added role group '%{groupName}'.", + "successDeleteRoleGroup": "Successfully deleted role group. | Successfully deleted role groups.", "successSaveActiveDirectorySettings": "Successfully saved Active Directory settings.", - "successSaveLdapSettings": "Successfully saved Open LDAP settings." + "successSaveLdapSettings": "Successfully saved Open LDAP settings.", + "successSaveRoleGroup": "Successfully saved role group '%{groupName}'." } }, "pageLocalUserManagement": { diff --git a/src/store/modules/AccessControl/LdapStore.js b/src/store/modules/AccessControl/LdapStore.js index 54fbbccd..7b5d853d 100644 --- a/src/store/modules/AccessControl/LdapStore.js +++ b/src/store/modules/AccessControl/LdapStore.js @@ -1,5 +1,6 @@ import api from '@/store/api'; import i18n from '@/i18n'; +import { find } from 'lodash'; const LdapStore = { namespaced: true, @@ -11,7 +12,8 @@ const LdapStore = { bindDn: null, baseDn: null, userAttribute: null, - groupsAttribute: null + groupsAttribute: null, + roleGroups: [] }, activeDirectory: { serviceEnabled: null, @@ -19,13 +21,23 @@ const LdapStore = { bindDn: null, baseDn: null, userAttribute: null, - groupsAttribute: null + groupsAttribute: null, + roleGroups: [] } }, getters: { isServiceEnabled: state => state.isServiceEnabled, ldap: state => state.ldap, - activeDirectory: state => state.activeDirectory + activeDirectory: state => state.activeDirectory, + isActiveDirectoryEnabled: state => { + return state.activeDirectory.serviceEnabled; + }, + enabledRoleGroups: (state, getters) => { + const serviceType = getters.isActiveDirectoryEnabled + ? 'activeDirectory' + : 'ldap'; + return state[serviceType].roleGroups; + } }, mutations: { setServiceEnabled: (state, serviceEnabled) => @@ -36,7 +48,8 @@ const LdapStore = { ServiceEnabled, ServiceAddresses, Authentication = {}, - LDAPService: { SearchSettings = {} } = {} + LDAPService: { SearchSettings = {} } = {}, + RemoteRoleMapping = [] } ) => { state.ldap.serviceAddress = ServiceAddresses[0]; @@ -45,6 +58,7 @@ const LdapStore = { state.ldap.bindDn = Authentication.Username; state.ldap.userAttribute = SearchSettings.UsernameAttribute; state.ldap.groupsAttribute = SearchSettings.GroupsAttribute; + state.ldap.roleGroups = RemoteRoleMapping; }, setActiveDirectoryProperties: ( state, @@ -52,7 +66,8 @@ const LdapStore = { ServiceEnabled, ServiceAddresses, Authentication = {}, - LDAPService: { SearchSettings = {} } = {} + LDAPService: { SearchSettings = {} } = {}, + RemoteRoleMapping = [] } ) => { state.activeDirectory.serviceEnabled = ServiceEnabled; @@ -61,6 +76,7 @@ const LdapStore = { state.activeDirectory.baseDn = SearchSettings.BaseDistinguishedNames[0]; state.activeDirectory.userAttribute = SearchSettings.UsernameAttribute; state.activeDirectory.groupsAttribute = SearchSettings.GroupsAttribute; + state.activeDirectory.roleGroups = RemoteRoleMapping; } }, actions: { @@ -149,6 +165,97 @@ const LdapStore = { } else { return await dispatch('saveLdapSettings', data); } + }, + async addNewRoleGroup( + { dispatch, getters }, + { groupName, groupPrivilege } + ) { + const data = {}; + const enabledRoleGroups = getters['enabledRoleGroups']; + const isActiveDirectoryEnabled = getters['isActiveDirectoryEnabled']; + const RemoteRoleMapping = [ + ...enabledRoleGroups, + { + LocalRole: groupPrivilege, + RemoteGroup: groupName + } + ]; + if (isActiveDirectoryEnabled) { + data.ActiveDirectory = { RemoteRoleMapping }; + } else { + data.LDAP = { RemoteRoleMapping }; + } + return await api + .patch('/redfish/v1/AccountService', data) + .then(() => dispatch('getAccountSettings')) + .then(() => + i18n.t('pageLdap.toast.successAddRoleGroup', { + groupName + }) + ) + .catch(error => { + console.log(error); + throw new Error(i18n.t('pageLdap.toast.errorAddRoleGroup')); + }); + }, + async saveRoleGroup({ dispatch, getters }, { groupName, groupPrivilege }) { + const data = {}; + const enabledRoleGroups = getters['enabledRoleGroups']; + const isActiveDirectoryEnabled = getters['isActiveDirectoryEnabled']; + const RemoteRoleMapping = enabledRoleGroups.map(group => { + if (group.RemoteGroup === groupName) { + return { + RemoteGroup: groupName, + LocalRole: groupPrivilege + }; + } else { + return {}; + } + }); + if (isActiveDirectoryEnabled) { + data.ActiveDirectory = { RemoteRoleMapping }; + } else { + data.LDAP = { RemoteRoleMapping }; + } + return await api + .patch('/redfish/v1/AccountService', data) + .then(() => dispatch('getAccountSettings')) + .then(() => + i18n.t('pageLdap.toast.successSaveRoleGroup', { groupName }) + ) + .catch(error => { + console.log(error); + throw new Error(i18n.t('pageLdap.toast.errorSaveRoleGroup')); + }); + }, + async deleteRoleGroup({ dispatch, getters }, { roleGroups = [] }) { + const data = {}; + const enabledRoleGroups = getters['enabledRoleGroups']; + const isActiveDirectoryEnabled = getters['isActiveDirectoryEnabled']; + const RemoteRoleMapping = enabledRoleGroups.map(group => { + if (find(roleGroups, { groupName: group.RemoteGroup })) { + return null; + } else { + return {}; + } + }); + if (isActiveDirectoryEnabled) { + data.ActiveDirectory = { RemoteRoleMapping }; + } else { + data.LDAP = { RemoteRoleMapping }; + } + return await api + .patch('/redfish/v1/AccountService', data) + .then(() => dispatch('getAccountSettings')) + .then(() => + i18n.tc('pageLdap.toast.successDeleteRoleGroup', roleGroups.length) + ) + .catch(error => { + console.log(error); + throw new Error( + i18n.tc('pageLdap.toast.errorDeleteRoleGroup', roleGroups.length) + ); + }); } } }; diff --git a/src/views/AccessControl/Ldap/Ldap.vue b/src/views/AccessControl/Ldap/Ldap.vue index c2d0e347..3ae4784f 100644 --- a/src/views/AccessControl/Ldap/Ldap.vue +++ b/src/views/AccessControl/Ldap/Ldap.vue @@ -8,14 +8,11 @@ <b-form-group class="mb-3" :label="$t('pageLdap.form.ldapAuthentication')" + label-for="enable-ldap-auth" > - <b-form-text id="enable-ldap-auth-help-block"> - {{ $t('pageLdap.form.ldapAuthenticationHelper') }} - </b-form-text> <b-form-checkbox id="enable-ldap-auth" v-model="form.ldapAuthenticationEnabled" - aria-describedby="enable-ldap-auth-help-block" @change="onChangeldapAuthenticationEnabled" > {{ $t('global.action.enable') }} @@ -193,7 +190,7 @@ </b-row> </b-form-group> </div> - <b-row class="mt-4"> + <b-row class="mt-4 mb-5"> <b-col> <b-btn variant="primary" @@ -206,6 +203,11 @@ </b-row> </b-form> </page-section> + + <!-- Role groups --> + <page-section :section-title="$t('pageLdap.roleGroups')"> + <table-role-groups /> + </page-section> </b-container> </template> @@ -220,17 +222,26 @@ import PageTitle from '@/components/Global/PageTitle'; import PageSection from '@/components/Global/PageSection'; import InfoTooltip from '@/components/Global/InfoTooltip'; import InputPasswordToggle from '@/components/Global/InputPasswordToggle'; +import TableRoleGroups from './TableRoleGroups'; export default { name: 'Ldap', - components: { InfoTooltip, InputPasswordToggle, PageTitle, PageSection }, + components: { + InfoTooltip, + InputPasswordToggle, + PageTitle, + PageSection, + TableRoleGroups + }, mixins: [BVToastMixin, VuelidateMixin], data() { return { form: { - ldapAuthenticationEnabled: false, + ldapAuthenticationEnabled: this.$store.getters['ldap/isServiceEnabled'], secureLdapEnabled: false, - activeDirectoryEnabled: false, + activeDirectoryEnabled: this.$store.getters[ + 'ldap/isActiveDirectoryEnabled' + ], serverUri: '', bindDn: '', bindPassword: '', @@ -241,7 +252,12 @@ export default { }; }, computed: { - ...mapGetters('ldap', ['isServiceEnabled', 'ldap', 'activeDirectory']), + ...mapGetters('ldap', [ + 'isServiceEnabled', + 'isActiveDirectoryEnabled', + 'ldap', + 'activeDirectory' + ]), sslCertificates() { return this.$store.getters['sslCertificates/allCertificates']; }, @@ -267,22 +283,9 @@ export default { isServiceEnabled: function(value) { this.form.ldapAuthenticationEnabled = value; }, - ldap: { - handler: function(value) { - if (value.serviceEnabled || !this.form.activeDirectoryEnabled) { - this.setFormValues(value); - } - }, - deep: true - }, - activeDirectory: { - handler: function(value) { - if (value.serviceEnabled) { - this.form.activeDirectoryEnabled = true; - this.setFormValues(value); - } - }, - deep: true + isActiveDirectoryEnabled: function(value) { + this.form.activeDirectoryEnabled = value; + this.setFormValues(); } }, validations: { @@ -321,20 +324,22 @@ export default { created() { this.$store.dispatch('ldap/getAccountSettings'); this.$store.dispatch('sslCertificates/getCertificates'); - if (this.form.activeDirectoryEnabled) { - this.setFormValues(this.activeDirectory); - } else { - this.setFormValues(this.ldap); - } + this.setFormValues(); }, methods: { - setFormValues({ - serviceAddress = '', - bindDn = '', - baseDn = '', - userAttribute = '', - groupsAttribute = '' - }) { + setFormValues(serviceType) { + if (!serviceType) { + serviceType = this.isActiveDirectoryEnabled + ? this.activeDirectory + : this.ldap; + } + const { + serviceAddress = '', + bindDn = '', + baseDn = '', + userAttribute = '', + groupsAttribute = '' + } = serviceType; const secureLdap = serviceAddress && serviceAddress.includes('ldaps://') ? true : false; const serverUri = serviceAddress @@ -377,6 +382,8 @@ export default { const serviceType = isActiveDirectoryEnabled ? this.activeDirectory : this.ldap; + // Set form values according to user selected + // service type this.setFormValues(serviceType); }, onChangeldapAuthenticationEnabled(isServiceEnabled) { @@ -387,11 +394,7 @@ export default { // when the service is enabled. This is to prevent // an error if a user clears any properties then // disables the service. - if (this.form.activeDirectoryEnabled) { - this.setFormValues(this.activeDirectory); - } else { - this.setFormValues(this.ldap); - } + this.setFormValues(); } } } diff --git a/src/views/AccessControl/Ldap/ModalAddRoleGroup.vue b/src/views/AccessControl/Ldap/ModalAddRoleGroup.vue new file mode 100644 index 00000000..e2da1eb1 --- /dev/null +++ b/src/views/AccessControl/Ldap/ModalAddRoleGroup.vue @@ -0,0 +1,164 @@ +<template> + <b-modal id="modal-role-group" ref="modal" @ok="onOk" @hidden="resetForm"> + <template v-slot:modal-title> + <template v-if="roleGroup"> + {{ $t('pageLdap.modal.editRoleGroup') }} + </template> + <template v-else> + {{ $t('pageLdap.modal.addNewRoleGroup') }} + </template> + </template> + <b-container> + <b-row> + <b-col sm="8"> + <b-form id="role-group"> + <!-- Edit role group --> + <template v-if="roleGroup !== null"> + <dl class="mb-4"> + <dt>{{ $t('pageLdap.modal.groupName') }}</dt> + <dd>{{ form.groupName }}</dd> + </dl> + </template> + + <!-- Add new role group --> + <template v-else> + <b-form-group + :label="$t('pageLdap.modal.groupName')" + label-for="role-group-name" + > + <b-form-input + id="role-group-name" + v-model="form.groupName" + :state="getValidationState($v.form.groupName)" + @input="$v.form.groupName.$touch()" + /> + <b-form-invalid-feedback role="alert"> + {{ $t('global.form.fieldRequired') }} + </b-form-invalid-feedback> + </b-form-group> + </template> + + <b-form-group + :label="$t('pageLdap.modal.groupPrivilege')" + label-for="privilege" + > + <b-form-select + id="privilege" + v-model="form.groupPrivilege" + :options="accountRoles" + :state="getValidationState($v.form.groupPrivilege)" + @input="$v.form.groupPrivilege.$touch()" + > + <template v-if="!roleGroup" v-slot:first> + <b-form-select-option :value="null" disabled> + {{ $t('global.form.selectAnOption') }} + </b-form-select-option> + </template> + </b-form-select> + <b-form-invalid-feedback role="alert"> + {{ $t('global.form.fieldRequired') }} + </b-form-invalid-feedback> + </b-form-group> + </b-form> + </b-col> + </b-row> + </b-container> + <template v-slot:modal-footer="{ ok, cancel }"> + <b-button variant="secondary" @click="cancel()"> + {{ $t('global.action.cancel') }} + </b-button> + <b-button form="role-group" type="submit" variant="primary" @click="ok()"> + <template v-if="roleGroup"> + {{ $t('global.action.save') }} + </template> + <template v-else> + {{ $t('global.action.add') }} + </template> + </b-button> + </template> + </b-modal> +</template> + +<script> +import { required, requiredIf } from 'vuelidate/lib/validators'; +import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js'; + +export default { + mixins: [VuelidateMixin], + props: { + roleGroup: { + type: Object, + default: null, + validator: prop => { + if (prop === null) return true; + return ( + prop.hasOwnProperty('groupName') && + prop.hasOwnProperty('groupPrivilege') + ); + } + } + }, + data() { + return { + form: { + groupName: null, + groupPrivilege: null + } + }; + }, + computed: { + accountRoles() { + return this.$store.getters['localUsers/accountRoles']; + } + }, + watch: { + roleGroup: function(value) { + if (value === null) return; + this.form.groupName = value.groupName; + this.form.groupPrivilege = value.groupPrivilege; + } + }, + validations() { + return { + form: { + groupName: { + required: requiredIf(function() { + return !this.roleGroup; + }) + }, + groupPrivilege: { + required + } + } + }; + }, + methods: { + handleSubmit() { + this.$v.$touch(); + if (this.$v.$invalid) return; + this.$emit('ok', { + addNew: !this.roleGroup, + groupName: this.form.groupName, + groupPrivilege: this.form.groupPrivilege + }); + this.closeModal(); + }, + closeModal() { + this.$nextTick(() => { + this.$refs.modal.hide(); + }); + }, + resetForm() { + this.form.groupName = null; + this.form.groupPrivilege = null; + this.$v.$reset(); + this.$emit('hidden'); + }, + onOk(bvModalEvt) { + // prevent modal close + bvModalEvt.preventDefault(); + this.handleSubmit(); + } + } +}; +</script> diff --git a/src/views/AccessControl/Ldap/TableRoleGroups.vue b/src/views/AccessControl/Ldap/TableRoleGroups.vue new file mode 100644 index 00000000..a851a033 --- /dev/null +++ b/src/views/AccessControl/Ldap/TableRoleGroups.vue @@ -0,0 +1,240 @@ +<template> + <div> + <b-row> + <b-col md="9"> + <alert :show="isServiceEnabled === false" variant="info"> + {{ $t('pageLdap.tableRoleGroups.alertContent') }} + </alert> + </b-col> + </b-row> + <b-row> + <b-col class="text-right" md="9"> + <b-btn + variant="primary" + :disabled="!isServiceEnabled" + @click="initRoleGroupModal(null)" + > + <icon-add /> + {{ $t('pageLdap.addRoleGroup') }} + </b-btn> + </b-col> + </b-row> + <b-row> + <b-col md="9"> + <table-toolbar + ref="toolbar" + :selected-items-count="selectedRows.length" + :actions="batchActions" + @clearSelected="clearSelectedRows($refs.table)" + @batchAction="onBatchAction" + /> + <b-table + ref="table" + selectable + no-select-on-click + no-sort-reset + sort-icon-left + :items="tableItems" + :fields="fields" + @row-selected="onRowSelected($event, tableItems.length)" + > + <!-- Checkbox column --> + <template v-slot:head(checkbox)> + <b-form-checkbox + v-model="tableHeaderCheckboxModel" + :indeterminate="tableHeaderCheckboxIndeterminate" + :disabled="!isServiceEnabled" + @change="onChangeHeaderCheckbox($refs.table)" + /> + </template> + <template v-slot:cell(checkbox)="row"> + <b-form-checkbox + v-model="row.rowSelected" + :disabled="!isServiceEnabled" + @change="toggleSelectRow($refs.table, row.index)" + /> + </template> + + <!-- table actions column --> + <template v-slot:cell(actions)="{ item }"> + <table-row-action + v-for="(action, index) in item.actions" + :key="index" + :value="action.value" + :enabled="action.enabled" + :title="action.title" + @click:tableAction="onTableRowAction($event, item)" + > + <template v-slot:icon> + <icon-edit v-if="action.value === 'edit'" /> + <icon-trashcan v-if="action.value === 'delete'" /> + </template> + </table-row-action> + </template> + </b-table> + </b-col> + </b-row> + <modal-add-role-group + :role-group="activeRoleGroup" + @ok="saveRoleGroup" + @hidden="activeRoleGroup = null" + /> + </div> +</template> + +<script> +import IconEdit from '@carbon/icons-vue/es/edit/20'; +import IconTrashcan from '@carbon/icons-vue/es/trash-can/20'; +import IconAdd from '@carbon/icons-vue/es/add--alt/20'; +import { mapGetters } from 'vuex'; + +import Alert from '@/components/Global/Alert'; +import TableToolbar from '@/components/Global/TableToolbar'; +import TableRowAction from '@/components/Global/TableRowAction'; +import BVTableSelectableMixin from '@/components/Mixins/BVTableSelectableMixin'; +import BVToastMixin from '@/components/Mixins/BVToastMixin'; +import ModalAddRoleGroup from './ModalAddRoleGroup'; + +export default { + components: { + Alert, + IconAdd, + IconEdit, + IconTrashcan, + ModalAddRoleGroup, + TableRowAction, + TableToolbar + }, + mixins: [BVTableSelectableMixin, BVToastMixin], + data() { + return { + activeRoleGroup: null, + fields: [ + { + key: 'checkbox', + sortable: false + }, + { + key: 'groupName', + sortable: true, + label: this.$t('pageLdap.tableRoleGroups.groupName') + }, + { + key: 'groupPrivilege', + sortable: true, + label: this.$t('pageLdap.tableRoleGroups.groupPrivilege') + }, + { + key: 'actions', + sortable: false, + label: '', + tdClass: 'text-right' + } + ], + batchActions: [ + { + value: 'delete', + label: this.$t('global.action.delete') + } + ] + }; + }, + computed: { + ...mapGetters('ldap', ['isServiceEnabled', 'enabledRoleGroups']), + tableItems() { + return this.enabledRoleGroups.map(({ LocalRole, RemoteGroup }) => { + return { + groupName: RemoteGroup, + groupPrivilege: LocalRole, + actions: [ + { + value: 'edit', + title: this.$t('global.action.edit'), + enabled: this.isServiceEnabled + }, + { + value: 'delete', + title: this.$t('global.action.delete'), + enabled: this.isServiceEnabled + } + ] + }; + }); + } + }, + created() { + this.$store.dispatch('localUsers/getAccountRoles'); + }, + methods: { + onBatchAction() { + this.$bvModal + .msgBoxConfirm( + this.$tc( + 'pageLdap.modal.deleteRoleGroupBatchConfirmMessage', + this.selectedRows.length + ), + { + title: this.$t('pageLdap.modal.deleteRoleGroup'), + okTitle: this.$t('global.action.delete') + } + ) + .then(deleteConfirmed => { + if (deleteConfirmed) { + this.$store + .dispatch('ldap/deleteRoleGroup', { + roleGroups: this.selectedRows + }) + .then(success => this.successToast(success)) + .catch(({ message }) => this.errorToast(message)); + } + }); + }, + onTableRowAction(action, row) { + switch (action) { + case 'edit': + this.initRoleGroupModal(row); + break; + case 'delete': + this.$bvModal + .msgBoxConfirm( + this.$t('pageLdap.modal.deleteRoleGroupConfirmMessage', { + groupName: row.groupName + }), + { + title: this.$t('pageLdap.modal.deleteRoleGroup'), + okTitle: this.$t('global.action.delete') + } + ) + .then(deleteConfirmed => { + if (deleteConfirmed) { + this.$store + .dispatch('ldap/deleteRoleGroup', { roleGroups: [row] }) + .then(success => this.successToast(success)) + .catch(({ message }) => this.errorToast(message)); + } + }); + break; + } + }, + initRoleGroupModal(roleGroup) { + this.activeRoleGroup = roleGroup; + this.$bvModal.show('modal-role-group'); + }, + saveRoleGroup({ addNew, groupName, groupPrivilege }) { + this.activeRoleGroup = null; + const data = { groupName, groupPrivilege }; + if (addNew) { + this.$store + .dispatch('ldap/addNewRoleGroup', data) + .then(success => this.successToast(success)) + .catch(({ message }) => this.errorToast(message)); + } else { + this.$store + .dispatch('ldap/saveRoleGroup', data) + .then(success => this.successToast(success)) + .catch(({ message }) => this.errorToast(message)); + } + } + } +}; +</script> |