From e7aee799c4270cf8c388c8cddf714e5aabcb4adc Mon Sep 17 00:00:00 2001 From: Debora Crescenzo <dcrescenzo@sipwise.com> Date: Fri, 2 Aug 2024 16:28:18 +0100 Subject: [PATCH] MT#60623 Hide features not covered by the license Following the changes about the licenses, some API will now return a "403 - License not available" error when the needed license is not available to the user. In the frontend we handle the licenses with two levels of protection: 1) We hide menus of missing licenses from the sidebar 2) We add a guard in boot/routes.js that would redirect to the homepage any attempts to access the mentioned menus, in case they are mistakenly shown. Change-Id: I9e88473ee90935db9b2a234ff03aef1b3a44a97b (cherry picked from commit 2d1ec7c499df4dd5f46fe7c74ee8da5aa90535fc) (cherry picked from commit 1c1f7b5ee30c21551b6ba5fee55fd7968a8d3301) --- src/boot/routes.js | 9 ++++++- src/components/CscMainMenuTop.vue | 36 +++++++++++++++------------ src/constants.js | 9 +++++++ src/pages/CscPageConversations.vue | 40 +++++++++++++++++------------- src/pages/CscPageDashboard.vue | 6 ++--- src/router/routes.js | 11 +++++--- src/store/user.js | 3 +++ 7 files changed, 73 insertions(+), 41 deletions(-) diff --git a/src/boot/routes.js b/src/boot/routes.js index 944afd5d..24ac9e5f 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -34,7 +34,14 @@ export default ({ app, router, store }) => { break default: if (to.meta?.profileAttribute) { - store.getters['user/hasSubscriberProfileAttribute'](to.meta.profileAttribute) ? next() : next('/') + const hasSubscriberProfileAttribute = store.getters['user/hasSubscriberProfileAttribute'](to.meta.profileAttribute) + if (to.meta.license && hasSubscriberProfileAttribute) { + // Guard to assure that users cannot click on menu if + // it is mistakenly visible when the license is inactive + store.getters['user/isLicenseActive'](to.meta.profileAttribute) ? next() : next('/') + } + + hasSubscriberProfileAttribute ? next() : next('/') } else if (to.meta?.profileAttributes) { store.getters['user/hasSubscriberProfileAttributes'](to.meta.profileAttributes) ? next() : next('/') } else { diff --git a/src/components/CscMainMenuTop.vue b/src/components/CscMainMenuTop.vue index 06e4d4f0..0c30f85a 100644 --- a/src/components/CscMainMenuTop.vue +++ b/src/components/CscMainMenuTop.vue @@ -5,11 +5,9 @@ </template> <script> -import { - mapGetters -} from 'vuex' +import { mapGetters } from 'vuex' import CscMainMenu from 'components/CscMainMenu' -import { PROFILE_ATTRIBUTE_MAP, PROFILE_ATTRIBUTES_MAP } from 'src/constants' +import { LICENSES, PROFILE_ATTRIBUTE_MAP, PROFILE_ATTRIBUTES_MAP } from 'src/constants' export default { name: 'CscMainMenuTop', @@ -47,12 +45,13 @@ export default { }, computed: { ...mapGetters('user', [ - 'isPbxEnabled', + 'getCustomerId', 'hasFaxCapability', 'hasSubscriberProfileAttribute', 'hasSubscriberProfileAttributes', - 'getCustomerId', - 'isOldCSCProxyingAllowed' + 'isLicenseActive', + 'isOldCSCProxyingAllowed', + 'isPbxEnabled' ]), items () { return [ @@ -67,7 +66,7 @@ export default { icon: 'call', label: this.callStateTitle, sublabel: this.callStateSubtitle, - visible: this.hasSubscriberProfileAttribute(PROFILE_ATTRIBUTE_MAP.cscCalls) + visible: this.hasSubscriberProfileAttribute(PROFILE_ATTRIBUTE_MAP.cscCalls) && this.isLicenseActive(LICENSES.csc_calls) }, { to: '/user/conversations', @@ -80,7 +79,7 @@ export default { to: '/user/subscriber-phonebook', icon: 'fas fa-user', label: this.$t('Subscriber Phonebook'), - visible: true + visible: this.isLicenseActive(LICENSES.phonebook) }, { icon: 'settings_phone', @@ -139,7 +138,7 @@ export default { to: '/user/recordings', icon: 'play_circle', label: this.$t('Recordings'), - visible: this.hasSubscriberProfileAttribute(PROFILE_ATTRIBUTE_MAP.recordings) + visible: this.hasSubscriberProfileAttribute(PROFILE_ATTRIBUTE_MAP.recordings) && this.isLicenseActive(LICENSES.call_recording) } ] }, @@ -147,12 +146,15 @@ export default { to: '/user/fax-settings', icon: 'fas fa-fax', label: this.$t('Fax Settings'), - visible: this.hasFaxCapability && this.hasSubscriberProfileAttribute(PROFILE_ATTRIBUTE_MAP.faxServer) + visible: this.hasFaxCapability && + this.hasSubscriberProfileAttribute(PROFILE_ATTRIBUTE_MAP.faxServer) && + this.isLicenseActive(LICENSES.fax) + }, { icon: 'fas fa-chart-line', label: this.$t('PBX Statistics'), - visible: this.isPbxAdmin, + visible: this.isPbxAdmin && this.isLicenseActive(LICENSES.pbx), opened: this.isPbxConfiguration, children: [ { @@ -166,7 +168,7 @@ export default { { icon: 'miscellaneous_services', label: this.$t('PBX Configuration'), - visible: this.isPbxAdmin, + visible: this.isPbxAdmin && this.isLicenseActive(LICENSES.pbx), opened: this.isPbxConfiguration, children: [ { @@ -185,7 +187,7 @@ export default { to: '/user/pbx-configuration/devices', icon: 'fas fa-fax', label: this.$t('Devices'), - visible: this.hasSubscriberProfileAttribute(PROFILE_ATTRIBUTE_MAP.deviceProvisioning) + visible: this.hasSubscriberProfileAttribute(PROFILE_ATTRIBUTE_MAP.deviceProvisioning) && this.isLicenseActive(LICENSES.device_provisioning) }, { to: '/user/pbx-configuration/call-queues', @@ -215,7 +217,7 @@ export default { to: '/user/pbx-configuration/customer-phonebook', icon: 'person', label: this.$t('Customer Phonebook'), - visible: true + visible: this.isLicenseActive(LICENSES.phonebook) }, { to: '/user/pbx-configuration/customer-preferences', @@ -228,7 +230,9 @@ export default { { icon: 'settings', label: this.$t('Extension Settings'), - visible: this.isPbxEnabled && this.hasSubscriberProfileAttributes(PROFILE_ATTRIBUTES_MAP.pbxSettings), + visible: this.isPbxEnabled && + this.hasSubscriberProfileAttributes(PROFILE_ATTRIBUTES_MAP.pbxSettings) && + this.isLicenseActive(LICENSES.pbx), children: [ { to: '/user/extension-settings/call-queues', diff --git a/src/constants.js b/src/constants.js index abfff053..bc62bbd7 100644 --- a/src/constants.js +++ b/src/constants.js @@ -32,3 +32,12 @@ export const PROFILE_ATTRIBUTES_MAP = { pbxSettings: ['auto_attendant', 'cloud_pbx_callqueue', 'max_queue_length', 'queue_wrap_up_time', 'manager_secretary'], pbxSettingsCallQueue: ['cloud_pbx_callqueue', 'max_queue_length', 'queue_wrap_up_time'] } + +export const LICENSES = { + csc_calls: 'csc_calls', + device_provisioning: 'device_provisioning', + fax: 'fax', + pbx: 'pbx', + phonebook: 'phonebook', + call_recording: 'call_recording' +} diff --git a/src/pages/CscPageConversations.vue b/src/pages/CscPageConversations.vue index 38a5c258..0a7a858d 100644 --- a/src/pages/CscPageConversations.vue +++ b/src/pages/CscPageConversations.vue @@ -123,6 +123,7 @@ import CscConversationsFilter from 'components/pages/Conversations/CscConversati import CscConversationsCallsFilter from 'components/pages/Conversations/CscConversationsCallsFilter' import CscRemoveDialog from 'components/CscRemoveDialog' import { mapWaitingActions } from 'vue-wait' +import { LICENSES } from 'src/constants' export default { name: 'CscPageConversations', components: { @@ -146,6 +147,20 @@ export default { } }, computed: { + ...mapGetters('user', [ + 'isLicenseActive' + ]), + ...mapState('conversations', [ + 'reachedLastPage' + ]), + ...mapGetters('conversations', [ + 'items', + 'isNumberIncomingBlocked', + 'isNumberOutgoingBlocked' + ]), + ...mapGetters('call', [ + 'isCallEnabled' + ]), tabs () { return [ { @@ -163,24 +178,15 @@ export default { value: 'voicemail', icon: 'voicemail' }, - { - label: this.$t('Faxes'), - value: 'fax', - icon: 'description' - } - ] + this.isLicenseActive(LICENSES.fax) + ? { + label: this.$t('Faxes'), + value: 'fax', + icon: 'description' + } + : null + ].filter((label) => label !== null) }, - ...mapState('conversations', [ - 'reachedLastPage' - ]), - ...mapGetters('conversations', [ - 'items', - 'isNumberIncomingBlocked', - 'isNumberOutgoingBlocked' - ]), - ...mapGetters('call', [ - 'isCallEnabled' - ]), pageStyle () { return { paddingTop: this.topMargin + 'px' diff --git a/src/pages/CscPageDashboard.vue b/src/pages/CscPageDashboard.vue index ce956839..e9b85e18 100644 --- a/src/pages/CscPageDashboard.vue +++ b/src/pages/CscPageDashboard.vue @@ -4,7 +4,7 @@ class="row justify-center" > <csc-card-dashboard - v-if="showConvList" + v-if="showConversationsCard" :title="$t('Voicebox Messages')" :count="voicemailsCount" :count-title="$t('Messages')" @@ -18,7 +18,7 @@ @action="downloadVoicemail" /> <csc-card-dashboard - v-if="showConvList" + v-if="showConversationsCard" :title="$t('Call List')" :count="callsCount" :count-title="$t('Calls')" @@ -100,7 +100,7 @@ export default { ...mapGetters('user', [ 'hasSubscriberProfileAttribute' ]), - showConvList () { + showConversationsCard () { return this.hasSubscriberProfileAttribute(PROFILE_ATTRIBUTE_MAP.conversations) }, showRegDevices () { diff --git a/src/router/routes.js b/src/router/routes.js index 6249f307..1e0d4ac4 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -1,4 +1,4 @@ -import { PROFILE_ATTRIBUTE_MAP, PROFILE_ATTRIBUTES_MAP } from 'src/constants' +import { LICENSES, PROFILE_ATTRIBUTE_MAP, PROFILE_ATTRIBUTES_MAP } from 'src/constants' import CscLayoutLogin from 'src/layouts/CscLayoutLogin' import CscLayoutMain from 'src/layouts/CscLayoutMain' @@ -99,7 +99,8 @@ const routes = [ meta: { get title () { return i18n.global.tc('Subscriber Phonebook') - } + }, + license: LICENSES.phonebook } }, { @@ -217,7 +218,8 @@ const routes = [ get subtitle () { return i18n.global.tc('CDR') } - } + }, + license: LICENSES.pbx }, { path: 'pbx-configuration/groups', @@ -431,7 +433,8 @@ const routes = [ get subtitle () { return i18n.global.tc('Set your fax settings') }, - profileAttribute: PROFILE_ATTRIBUTE_MAP.faxServer + profileAttribute: PROFILE_ATTRIBUTE_MAP.faxServer, + license: LICENSES.phonebook } }, { diff --git a/src/store/user.js b/src/store/user.js index fcde883d..ba331a02 100644 --- a/src/store/user.js +++ b/src/store/user.js @@ -196,6 +196,9 @@ export default { isOldCSCProxyingAllowed (state, getters) { return getters.isAdmin && state.platformInfo?.csc_v2_mode === 'mixed' && !!getters.getCustomerId }, + isLicenseActive: (state) => (license) => { + return state?.platformInfo.licenses.includes(license) + }, isPbxPilot (state) { return !!state.subscriber?.is_pbx_pilot },