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
         },