diff --git a/src/containers/DefaultContainer.vue b/src/containers/DefaultContainer.vue index b53ccc59c..e99642376 100644 --- a/src/containers/DefaultContainer.vue +++ b/src/containers/DefaultContainer.vue @@ -37,7 +37,10 @@ import DefaultHeader from './DefaultHeader'; import DefaultFooter from './DefaultFooter'; import EventBus from '../shared/eventbus'; import ProfileEditModal from '../views/components/ProfileEditModal'; -import * as permissions from '../shared/permissions'; +import PERMISSIONS, { + hasComplexPermission, + hasPermission, +} from '../shared/permissions'; export default { name: 'DefaultContainer', @@ -64,7 +67,6 @@ export default { name: this.$t('message.dashboard'), url: '/dashboard', icon: 'icon-speedometer', - permission: permissions.VIEW_PORTFOLIO, }, { title: true, @@ -74,37 +76,35 @@ export default { element: '', attributes: {}, }, - permission: permissions.VIEW_PORTFOLIO, }, { name: this.$t('message.projects'), url: '/projects', icon: 'fa fa-sitemap', - permission: permissions.VIEW_PORTFOLIO, + permission: PERMISSIONS.PROJECT_READ, }, { name: this.$t('message.components'), url: '/components', icon: 'fa fa-cubes', - permission: permissions.VIEW_PORTFOLIO, + permission: PERMISSIONS.PROJECT_READ, }, { name: this.$t('message.vulnerabilities'), url: '/vulnerabilities', icon: 'fa fa-shield', - permission: permissions.VIEW_PORTFOLIO, + permission: PERMISSIONS.VULNERABILITY_MANAGEMENT, }, { name: this.$t('message.licenses'), url: '/licenses', icon: 'fa fa-balance-scale', - permission: permissions.VIEW_PORTFOLIO, }, { - name: 'Tags', + name: this.$t('message.tags'), url: '/tags', icon: 'fa fa-tag', - permission: permissions.VIEW_PORTFOLIO, + permission: PERMISSIONS.TAG_MANAGEMENT, }, { title: true, @@ -115,21 +115,22 @@ export default { attributes: {}, }, permissions: [ - permissions.VIEW_VULNERABILITY, - permissions.VIEW_POLICY_VIOLATION, + PERMISSIONS.PROJECT_READ, + PERMISSIONS.FINDING_READ, + PERMISSIONS.POLICY_VIOLATION_READ, ], }, { name: this.$t('message.vulnerability_audit'), url: '/vulnerabilityAudit', icon: 'fa fa-tasks', - permission: permissions.VIEW_VULNERABILITY, + permissions: [PERMISSIONS.PROJECT_READ, PERMISSIONS.FINDING_READ], }, { name: this.$t('message.policy_violation_audit'), url: '/policyViolationAudit', icon: 'fa fa-fire', - permission: permissions.VIEW_POLICY_VIOLATION, + permission: PERMISSIONS.POLICY_VIOLATION_READ, }, { title: true, @@ -139,37 +140,19 @@ export default { element: '', attributes: {}, }, - permission: [ - permissions.SYSTEM_CONFIGURATION, - permissions.SYSTEM_CONFIGURATION_CREATE, - permissions.SYSTEM_CONFIGURATION_READ, - permissions.SYSTEM_CONFIGURATION_UPDATE, - permissions.SYSTEM_CONFIGURATION_DELETE, - ], + permission: PERMISSIONS.SYSTEM_CONFIGURATION, }, { name: this.$t('message.policy_management'), url: '/policy', icon: 'fa fa-list-alt', - permission: [ - permissions.POLICY_MANAGEMENT, - permissions.POLICY_MANAGEMENT_CREATE, - permissions.POLICY_MANAGEMENT_READ, - permissions.POLICY_MANAGEMENT_UPDATE, - permissions.POLICY_MANAGEMENT_DELETE, - ], + permission: PERMISSIONS.POLICY_MANAGEMENT, }, { name: this.$t('message.administration'), url: '/admin', icon: 'fa fa-cogs', - permission: [ - permissions.SYSTEM_CONFIGURATION, - permissions.SYSTEM_CONFIGURATION_CREATE, - permissions.SYSTEM_CONFIGURATION_READ, - permissions.SYSTEM_CONFIGURATION_UPDATE, - permissions.SYSTEM_CONFIGURATION_DELETE, - ], + permission: PERMISSIONS.SYSTEM_CONFIGURATION, }, ], }; @@ -229,9 +212,8 @@ export default { }, mounted() { this.isSidebarMinimized = - localStorage && localStorage.getItem('isSidebarMinimized') !== null - ? localStorage.getItem('isSidebarMinimized') === 'true' - : false; + localStorage?.getItem('isSidebarMinimized') === 'true'; + const sidebar = document.body; if (sidebar) { if (this.isSidebarMinimized) { @@ -259,21 +241,21 @@ export default { } }, permissibleNav() { - let decodedToken = permissions.decodeToken(permissions.getToken()); - let array = []; - for (const item of this.nav) { + return this.nav.filter((item) => { + if (item.permission && !hasPermission(item.permission)) { + return false; + } + if (item.permissions && !hasPermission(item.permissions)) { + return false; + } if ( - (item.permission !== null && - permissions.hasPermission(item.permission, decodedToken)) || - (Object.prototype.hasOwnProperty.call(item, 'permissions') && - item.permissions.some((permission) => - permissions.hasPermission(permission, decodedToken), - )) + item.complexPermission && + !hasComplexPermission(item.complexPermission) ) { - array.push(item); + return false; } - } - return array; + return true; + }); }, }, created() { diff --git a/src/directives/VuePermission.js b/src/directives/VuePermission.js index fcc24165a..18540d68a 100644 --- a/src/directives/VuePermission.js +++ b/src/directives/VuePermission.js @@ -2,34 +2,44 @@ * Permissions Vue Directive */ import Vue from 'vue'; -import { hasPermission, decodeToken, getToken } from '../shared/permissions'; +import { hasPermission, hasComplexPermission } from '../shared/permissions'; -Vue.directive('permission', function (el, binding) { - let decodedToken = decodeToken(getToken()); - if (Array.isArray(binding.value)) { +Vue.directive('permission', { + inserted(el, binding) { + const { arg, value, modifiers } = binding; + const modifierKeys = Object.keys(modifiers); let permitted = false; - if (binding.arg === 'and') { - // This is the AND case. If a user has ALL of the specified permissions, permitted will be true - permitted = true; - binding.value.forEach(function (b) { - if (!hasPermission(b, decodedToken)) { - permitted = false; - } - }); - } else if (binding.arg === 'or') { - // This is the OR case. If a user has one or more of the specified permissions, permitted will be true - binding.value.forEach(function (b) { - if (hasPermission(b, decodedToken)) { - permitted = true; - } - }); - } - if (!permitted) { - el.style.display = 'none'; + + if (arg === 'complex') { + permitted = hasComplexPermission(value); + } else { + permitted = hasPermission(value, arg); } - } else { - if (!hasPermission(binding.value, decodedToken)) { + + if (permitted) return; // User has permission, do nothing + + if (modifierKeys.length === 0) { el.style.display = 'none'; + return; } - } + + modifierKeys.forEach((modifier) => { + switch (modifier.toLowerCase()) { + case 'readonly': + el.setAttribute('readonly', true); + break; + case 'disabled': + el.setAttribute('disabled', true); + break; + case 'hide': + el.style.display = 'none'; + break; + case 'visibility': + el.style.visibility = 'hidden'; + break; + default: + throw new Error(`Unknown modifier v-permission:${modifier}`); + } + }); + }, }); diff --git a/src/mixins/permissionsMixin.js b/src/mixins/permissionsMixin.js index 09df56a35..6a0135d3c 100644 --- a/src/mixins/permissionsMixin.js +++ b/src/mixins/permissionsMixin.js @@ -1,90 +1,14 @@ -/* eslint-disable prettier/prettier */ -import * as permissions from '../shared/permissions'; +import PERMISSIONS, { + hasPermission, + hasComplexPermission, +} from '../shared/permissions'; export default { data() { return { - PERMISSIONS: { - BOM_UPLOAD: permissions.BOM_UPLOAD, - VIEW_PORTFOLIO: permissions.VIEW_PORTFOLIO, - PORTFOLIO_MANAGEMENT: permissions.PORTFOLIO_MANAGEMENT, - PORTFOLIO_MANAGEMENT_CREATE: permissions.PORTFOLIO_MANAGEMENT_CREATE, - PORTFOLIO_MANAGEMENT_READ: permissions.PORTFOLIO_MANAGEMENT_READ, - PORTFOLIO_MANAGEMENT_UPDATE: permissions.PORTFOLIO_MANAGEMENT_UPDATE, - PORTFOLIO_MANAGEMENT_DELETE: permissions.PORTFOLIO_MANAGEMENT_DELETE, - VIEW_VULNERABILITY: permissions.VIEW_VULNERABILITY, - VULNERABILITY_ANALYSIS: permissions.VULNERABILITY_ANALYSIS, - VULNERABILITY_ANALYSIS_CREATE: - permissions.VULNERABILITY_ANALYSIS_CREATE, - VULNERABILITY_ANALYSIS_READ: permissions.VULNERABILITY_ANALYSIS_READ, - VULNERABILITY_ANALYSIS_UPDATE: - permissions.VULNERABILITY_ANALYSIS_UPDATE, - VIEW_POLICY_VIOLATION: permissions.VIEW_POLICY_VIOLATION, - VULNERABILITY_MANAGEMENT: permissions.VULNERABILITY_MANAGEMENT, - VULNERABILITY_MANAGEMENT_CREATE: - permissions.VULNERABILITY_MANAGEMENT_CREATE, - VULNERABILITY_MANAGEMENT_READ: - permissions.VULNERABILITY_MANAGEMENT_READ, - VULNERABILITY_MANAGEMENT_UPDATE: - permissions.VULNERABILITY_MANAGEMENT_UPDATE, - VULNERABILITY_MANAGEMENT_DELETE: - permissions.VULNERABILITY_MANAGEMENT_DELETE, - POLICY_VIOLATION_ANALYSIS: permissions.POLICY_VIOLATION_ANALYSIS, - ACCESS_MANAGEMENT: permissions.ACCESS_MANAGEMENT, - ACCESS_MANAGEMENT_CREATE: permissions.ACCESS_MANAGEMENT_CREATE, - ACCESS_MANAGEMENT_READ: permissions.ACCESS_MANAGEMENT_READ, - ACCESS_MANAGEMENT_UPDATE: permissions.ACCESS_MANAGEMENT_UPDATE, - ACCESS_MANAGEMENT_DELETE: permissions.ACCESS_MANAGEMENT_DELETE, - SYSTEM_CONFIGURATION: permissions.SYSTEM_CONFIGURATION, - SYSTEM_CONFIGURATION_CREATE: permissions.SYSTEM_CONFIGURATION_CREATE, - SYSTEM_CONFIGURATION_READ: permissions.SYSTEM_CONFIGURATION_READ, - SYSTEM_CONFIGURATION_UPDATE: permissions.SYSTEM_CONFIGURATION_UPDATE, - SYSTEM_CONFIGURATION_DELETE: permissions.SYSTEM_CONFIGURATION_DELETE, - PROJECT_CREATION_UPLOAD: permissions.PROJECT_CREATION_UPLOAD, - POLICY_MANAGEMENT: permissions.POLICY_MANAGEMENT, - POLICY_MANAGEMENT_CREATE: permissions.POLICY_MANAGEMENT_CREATE, - POLICY_MANAGEMENT_READ: permissions.POLICY_MANAGEMENT_READ, - POLICY_MANAGEMENT_UPDATE: permissions.POLICY_MANAGEMENT_UPDATE, - POLICY_MANAGEMENT_DELETE: permissions.POLICY_MANAGEMENT_DELETE, - TAG_MANAGEMENT: permissions.TAG_MANAGEMENT, - TAG_MANAGEMENT_DELETE: permissions.TAG_MANAGEMENT_DELETE, - }, + PERMISSIONS, + hasPermission, + hasComplexPermission, }; }, - computed: { - decodedToken() { - return permissions.decodeToken(permissions.getToken()); - }, - }, - methods: { - isPermitted(permission) { - // return permissions.hasPermission(permission, this.decodedToken); - if (typeof permission == 'string') { - return permissions.hasPermission(permission, this.decodedToken); - } else if (Array.isArray(permission)) { - for (let perm of permission) { - if (permissions.hasPermission(perm, this.decodedToken)) { - return true; - } - } - return false; - } else { - throw new Error('permission must be of type string or array'); - } - }, - isNotPermitted(permission) { - if (typeof permission == 'string') { - return !permissions.hasPermission(permission, this.decodedToken); - } else if (Array.isArray(permission)) { - for (let perm of permission) { - if (permissions.hasPermission(perm, this.decodedToken)) { - return false; - } - } - return true; - } else { - throw new Error('permission must be of type string or array'); - } - }, - }, }; diff --git a/src/router/index.js b/src/router/index.js index 20bf9e760..f07d691a7 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -2,8 +2,9 @@ import Vue from 'vue'; import Router from 'vue-router'; import i18n from '../i18n'; import EventBus from '../shared/eventbus'; -import { getToken, hasPermission } from '../shared/permissions'; +import PERMISSIONS, { getToken } from '../shared/permissions'; import { getContextPath } from '../shared/utils'; +import { canAccessDashboard, resolveAuthorization } from './routerGuard'; // Containers const DefaultContainer = () => import('@/containers/DefaultContainer'); @@ -131,7 +132,6 @@ function configRoutes() { i18n: 'message.dashboard', sectionPath: '/dashboard', sectionName: 'Dashboard', - permissions: ['VIEW_PORTFOLIO'], }, }, { @@ -143,7 +143,7 @@ function configRoutes() { i18n: 'message.projects', sectionPath: '/projects', sectionName: 'Projects', - permissions: ['VIEW_PORTFOLIO'], + permission: PERMISSIONS.PROJECT_READ, }, }, { @@ -164,7 +164,7 @@ function configRoutes() { i18n: 'message.projects', sectionPath: '/projects', sectionName: 'Projects', - permissions: ['VIEW_PORTFOLIO'], + permission: PERMISSIONS.PROJECT_READ, }, }, { @@ -179,7 +179,7 @@ function configRoutes() { i18n: 'message.projects', sectionPath: '/projects', sectionName: 'Projects', - permissions: ['VIEW_PORTFOLIO'], + permission: PERMISSIONS.PROJECT_READ, }, }, { @@ -194,7 +194,7 @@ function configRoutes() { i18n: 'message.projects', sectionPath: '/projects', sectionName: 'Projects', - permissions: ['VIEW_PORTFOLIO'], + permissions: [PERMISSIONS.PROJECT_READ, PERMISSIONS.FINDING_READ], }, }, { @@ -210,7 +210,7 @@ function configRoutes() { i18n: 'message.projects', sectionPath: '/projects', sectionName: 'Projects', - permissions: ['VIEW_PORTFOLIO'], + permission: PERMISSIONS.PROJECT_READ, }, }, { @@ -222,7 +222,7 @@ function configRoutes() { i18n: 'message.component_search', sectionPath: '/components', sectionName: 'Component Lookup', - permissions: ['VIEW_PORTFOLIO'], + permission: PERMISSIONS.PROJECT_READ, }, }, { @@ -238,7 +238,7 @@ function configRoutes() { i18n: 'message.projects', sectionPath: '/projects', sectionName: 'Projects', - permissions: ['VIEW_PORTFOLIO'], + permission: PERMISSIONS.PROJECT_READ, }, }, { @@ -250,7 +250,7 @@ function configRoutes() { i18n: 'message.projects', sectionPath: '/projects', sectionName: 'Projects', - permissions: ['VIEW_PORTFOLIO'], + permission: PERMISSIONS.PROJECT_READ, }, }, { @@ -262,7 +262,7 @@ function configRoutes() { i18n: 'message.vulnerabilities', sectionPath: '/vulnerabilities', sectionName: 'Vulnerabilities', - permissions: ['VIEW_PORTFOLIO'], + permission: PERMISSIONS.VULNERABILITY_MANAGEMENT, }, }, { @@ -281,7 +281,7 @@ function configRoutes() { i18n: 'message.vulnerabilities', sectionPath: '/vulnerabilities', sectionName: 'Vulnerabilities', - permissions: ['VIEW_PORTFOLIO'], + permission: PERMISSIONS.VULNERABILITY_MANAGEMENT, }, }, { @@ -293,7 +293,7 @@ function configRoutes() { i18n: 'message.tags', sectionPath: '/tags', sectionName: 'Tags', - permission: 'VIEW_PORTFOLIO', + permission: PERMISSIONS.TAG_MANAGEMENT, }, }, { @@ -305,7 +305,6 @@ function configRoutes() { i18n: 'message.licenses', sectionPath: '/licenses', sectionName: 'Licenses', - permissions: ['VIEW_PORTFOLIO'], }, }, { @@ -323,7 +322,6 @@ function configRoutes() { i18n: 'message.licenses', sectionPath: '/licenses', sectionName: 'Licenses', - permissions: ['VIEW_PORTFOLIO'], }, }, { @@ -340,13 +338,7 @@ function configRoutes() { i18n: 'message.policy_management', sectionPath: '/policy', sectionName: 'Policy Management', - permissions: [ - 'POLICY_MANAGEMENT', - 'POLICY_MANAGEMENT_CREATE', - 'POLICY_MANAGEMENT_READ', - 'POLICY_MANAGEMENT_UPDATE', - 'POLICY_MANAGEMENT_DELETE', - ], + permission: PERMISSIONS.POLICY_MANAGEMENT, }, }, { @@ -356,7 +348,10 @@ function configRoutes() { title: i18n.t('message.policy_violation_audit'), i18n: 'message.policy_violation_audit', sectionPath: '/audit', - permission: 'VIEW_POLICY_VIOLATION', + permissions: [ + PERMISSIONS.POLICY_VIOLATION_READ, + PERMISSIONS.POLICY_MANAGEMENT, + ], }, }, { @@ -368,13 +363,8 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], + propagatePermissionsToChildren: true, + permission: PERMISSIONS.SYSTEM_CONFIGURATION, }, children: [ { @@ -387,13 +377,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -404,13 +387,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -421,7 +397,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permission: 'SYSTEM_CONFIGURATION', }, }, { @@ -432,13 +407,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -449,13 +417,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -466,13 +427,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -483,13 +437,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -500,13 +447,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -517,13 +457,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -534,13 +467,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -551,13 +477,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -569,13 +488,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -586,13 +498,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -603,13 +508,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -620,13 +518,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -637,13 +528,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -655,13 +539,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -672,13 +549,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -689,13 +559,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -707,13 +570,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -724,13 +580,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -741,13 +590,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -758,13 +600,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -775,13 +610,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -792,13 +620,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -809,13 +630,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -826,13 +640,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -843,13 +650,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -860,13 +660,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -877,13 +670,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -894,13 +680,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -911,13 +690,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -929,13 +701,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -946,13 +711,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -964,13 +722,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -981,13 +732,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -998,13 +742,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'SYSTEM_CONFIGURATION', - 'SYSTEM_CONFIGURATION_CREATE', - 'SYSTEM_CONFIGURATION_READ', - 'SYSTEM_CONFIGURATION_UPDATE', - 'SYSTEM_CONFIGURATION_DELETE', - ], }, }, { @@ -1016,13 +753,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'ACCESS_MANAGEMENT', - 'ACCESS_MANAGEMENT_CREATE', - 'ACCESS_MANAGEMENT_READ', - 'ACCESS_MANAGEMENT_UPDATE', - 'ACCESS_MANAGEMENT_DELETE', - ], }, }, { @@ -1033,13 +763,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'ACCESS_MANAGEMENT', - 'ACCESS_MANAGEMENT_CREATE', - 'ACCESS_MANAGEMENT_READ', - 'ACCESS_MANAGEMENT_UPDATE', - 'ACCESS_MANAGEMENT_DELETE', - ], }, }, { @@ -1050,13 +773,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'ACCESS_MANAGEMENT', - 'ACCESS_MANAGEMENT_CREATE', - 'ACCESS_MANAGEMENT_READ', - 'ACCESS_MANAGEMENT_UPDATE', - 'ACCESS_MANAGEMENT_DELETE', - ], }, }, { @@ -1067,13 +783,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'ACCESS_MANAGEMENT', - 'ACCESS_MANAGEMENT_CREATE', - 'ACCESS_MANAGEMENT_READ', - 'ACCESS_MANAGEMENT_UPDATE', - 'ACCESS_MANAGEMENT_DELETE', - ], }, }, { @@ -1084,13 +793,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'ACCESS_MANAGEMENT', - 'ACCESS_MANAGEMENT_CREATE', - 'ACCESS_MANAGEMENT_READ', - 'ACCESS_MANAGEMENT_UPDATE', - 'ACCESS_MANAGEMENT_DELETE', - ], }, }, { @@ -1101,13 +803,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'ACCESS_MANAGEMENT', - 'ACCESS_MANAGEMENT_CREATE', - 'ACCESS_MANAGEMENT_READ', - 'ACCESS_MANAGEMENT_UPDATE', - 'ACCESS_MANAGEMENT_DELETE', - ], }, }, { @@ -1118,13 +813,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'ACCESS_MANAGEMENT', - 'ACCESS_MANAGEMENT_CREATE', - 'ACCESS_MANAGEMENT_READ', - 'ACCESS_MANAGEMENT_UPDATE', - 'ACCESS_MANAGEMENT_DELETE', - ], }, }, { @@ -1135,13 +823,6 @@ function configRoutes() { i18n: 'message.administration', sectionPath: '/admin', sectionName: 'Admin', - permissions: [ - 'ACCESS_MANAGEMENT', - 'ACCESS_MANAGEMENT_CREATE', - 'ACCESS_MANAGEMENT_READ', - 'ACCESS_MANAGEMENT_UPDATE', - 'ACCESS_MANAGEMENT_DELETE', - ], }, }, ], @@ -1159,7 +840,9 @@ function configRoutes() { i18n: 'message.vulnerability_audit', sectionPath: '/vulnerabilityAudit', sectionName: 'Vulnerability Audit', - permissions: ['VIEW_VULNERABILITY'], + complexPermission: { + or: [PERMISSIONS.PROJECT_READ, PERMISSIONS.FINDING_READ], + }, }, }, // The following route redirects URLs from legacy Dependency-Track UI to new URL format. @@ -1227,6 +910,7 @@ function configRoutes() { name: 'Login', component: Login, meta: { + isPublic: true, title: i18n.t('message.login'), }, }, @@ -1235,6 +919,7 @@ function configRoutes() { name: 'PasswordForceChange', component: PasswordForceChange, meta: { + isPublic: true, title: i18n.t('message.change_password'), }, }, @@ -1243,6 +928,7 @@ function configRoutes() { name: '404', component: Page404, meta: { + isPublic: true, title: i18n.t('404.heading'), }, }, @@ -1257,48 +943,55 @@ const router = new Router({ routes: configRoutes(), }); -router.beforeEach((to, from, next) => { +function redirectToDashboardOrLogin(next) { + const auth = canAccessDashboard(router); + if (auth) { + next({ name: 'Dashboard', replace: true }); + } else { + next({ name: 'Login', replace: true }); + } +} + +router.beforeEach(async (to, from, next) => { const redirectToLogin = () => { + if (to.name === 'Login') { + next(); // Already on login, allow navigation + return; + } next({ name: 'Login', query: { redirect: to.fullPath }, replace: true }); }; - if (to.meta.permissions) { - // non-public route, check permissions - const jwt = getToken(); - if (jwt) { - const isAllowed = to.meta.permissions.some((permission) => - hasPermission(permission), - ); - if (isAllowed) { - // let backend verify the token - router.app.axios - .get(`${router.app.$api.BASE_URL}/${router.app.$api.URL_USER_SELF}`, { - headers: { Authorization: `Bearer ${jwt}` }, - }) - .then((result) => { - Vue.prototype.$currentUser = result.data; - // allowed to proceed - next(); - }) - .catch(() => { - // token is stale - // notify app about this - EventBus.$emit('authenticated', null); - // redirect to login page - redirectToLogin(); - }); - } else { - Vue.prototype.$toastr.e(i18n.t('condition.forbidden')); - next({ name: 'Dashboard', replace: true }); - } - } else { - // no token at all, redirect to login page - redirectToLogin(); - } - } else { - // public route, allowed to proceed + if (to.meta?.isPublic) { next(); + return; + } + + // Check if the user is authenticated + const jwt = getToken(); + if (!jwt) { + redirectToLogin(); + return; + } + + // validate token and fetch user details + try { + const url = `${router.app.$api.BASE_URL}/${router.app.$api.URL_USER_SELF}`; + const body = { headers: { Authorization: `Bearer ${jwt}` } }; + const result = await router.app.axios.get(url, body); + Vue.prototype.$currentUser = result.data; + } catch (error) { + EventBus.$emit('authenticated', null); + redirectToLogin(); + return; + } + + // Check if the user has the required permissions + if (!resolveAuthorization(to.matched)) { + Vue.prototype.$toastr.e(i18n.t('condition.forbidden')); + redirectToDashboardOrLogin(next); + return; } + next(); }); export default router; diff --git a/src/router/routerGuard.js b/src/router/routerGuard.js new file mode 100644 index 000000000..1c0b1fa11 --- /dev/null +++ b/src/router/routerGuard.js @@ -0,0 +1,63 @@ +import { hasComplexPermission, hasPermission } from '../shared/permissions'; + +/** + * Determines if the user can access the Dashboard route. + * + * @param {import('vue-router').Router} router - The Vue Router instance. + * @returns {boolean} True if the user can access the Dashboard, false otherwise. + */ +export function canAccessDashboard(router) { + // Find the root route ("/") and the dashboard child + + const rootRoute = router.options.routes[0]; + const dashboardRoute = rootRoute.children.find((r) => r.name === 'Dashboard'); + if (!dashboardRoute) return false; + + // Simulate the matched array as it would be during navigation + const matched = [rootRoute, dashboardRoute]; + return resolveAuthorization(matched); +} + +/** + * Resolves whether the current route chain is authorized based on permissions and propagation rules. + * + * This function checks the matched route records from the deepest child up to the nearest ancestor + * that does not propagate permissions. It verifies that all required permissions and complex permissions + * are satisfied for each route in the propagation chain. + * + * @param {Array} matched - The array of matched route records, each containing a `meta` object. + * @returns {boolean} Returns `true` if all required permissions are satisfied along the propagation chain, otherwise `false`. + * + * @note + * Permissions can be propagated from ancestor routes to their children if the ancestor's `meta.propagatePermissionsToChildren` is `true`. + * The function walks up the matched route chain, collecting all such ancestors, and checks permissions for each. + */ +export function resolveAuthorization(matched) { + let idx = matched.length - 1; + // Collect the propagation chain (from stop ancestor down to current) + const chain = [matched[idx]]; + while ( + idx > 0 && + matched[idx - 1].meta && + matched[idx - 1].meta.propagatePermissionsToChildren + ) { + idx--; + chain.unshift(matched[idx]); + } + // debugger; + for (const r of chain) { + if ( + r.meta.complexPermission && + !hasComplexPermission(r.meta.complexPermission) + ) { + return false; + } + if (r.meta.permissions && !hasPermission(r.meta.permissions)) { + return false; + } + if (r.meta.permission && !hasPermission([r.meta.permission])) { + return false; + } + } + return true; +} diff --git a/src/shared/permissions.js b/src/shared/permissions.js index 632b54541..4225a8d50 100644 --- a/src/shared/permissions.js +++ b/src/shared/permissions.js @@ -1,78 +1,172 @@ /* eslint-disable prettier/prettier */ // API Permissions -export const BOM_UPLOAD = 'BOM_UPLOAD'; -export const VIEW_PORTFOLIO = 'VIEW_PORTFOLIO'; -export const PORTFOLIO_MANAGEMENT = 'PORTFOLIO_MANAGEMENT'; -export const PORTFOLIO_MANAGEMENT_CREATE = 'PORTFOLIO_MANAGEMENT_CREATE'; -export const PORTFOLIO_MANAGEMENT_READ = 'PORTFOLIO_MANAGEMENT_READ'; -export const PORTFOLIO_MANAGEMENT_UPDATE = 'PORTFOLIO_MANAGEMENT_UPDATE'; -export const PORTFOLIO_MANAGEMENT_DELETE = 'PORTFOLIO_MANAGEMENT_DELETE'; -export const VIEW_VULNERABILITY = 'VIEW_VULNERABILITY'; -export const VULNERABILITY_ANALYSIS = 'VULNERABILITY_ANALYSIS'; -export const VULNERABILITY_ANALYSIS_CREATE = 'VULNERABILITY_ANALYSIS_CREATE'; -export const VULNERABILITY_ANALYSIS_READ = 'VULNERABILITY_ANALYSIS_READ'; -export const VULNERABILITY_ANALYSIS_UPDATE = 'VULNERABILITY_ANALYSIS_UPDATE'; -export const VIEW_POLICY_VIOLATION = 'VIEW_POLICY_VIOLATION'; -export const VULNERABILITY_MANAGEMENT = 'VULNERABILITY_MANAGEMENT'; -export const VULNERABILITY_MANAGEMENT_CREATE = - 'VULNERABILITY_MANAGEMENT_CREATE'; -export const VULNERABILITY_MANAGEMENT_READ = 'VULNERABILITY_MANAGEMENT_READ'; -export const VULNERABILITY_MANAGEMENT_UPDATE = - 'VULNERABILITY_MANAGEMENT_UPDATE'; -export const VULNERABILITY_MANAGEMENT_DELETE = - 'VULNERABILITY_MANAGEMENT_DELETE'; -export const POLICY_VIOLATION_ANALYSIS = 'POLICY_VIOLATION_ANALYSIS'; +import EventBus from './eventbus'; + export const ACCESS_MANAGEMENT = 'ACCESS_MANAGEMENT'; -export const ACCESS_MANAGEMENT_CREATE = 'ACCESS_MANAGEMENT_CREATE'; -export const ACCESS_MANAGEMENT_READ = 'ACCESS_MANAGEMENT_READ'; -export const ACCESS_MANAGEMENT_UPDATE = 'ACCESS_MANAGEMENT_UPDATE'; -export const ACCESS_MANAGEMENT_DELETE = 'ACCESS_MANAGEMENT_DELETE'; -export const SYSTEM_CONFIGURATION = 'SYSTEM_CONFIGURATION'; -export const SYSTEM_CONFIGURATION_CREATE = 'SYSTEM_CONFIGURATION_CREATE'; -export const SYSTEM_CONFIGURATION_READ = 'SYSTEM_CONFIGURATION_READ'; -export const SYSTEM_CONFIGURATION_UPDATE = 'SYSTEM_CONFIGURATION_UPDATE'; -export const SYSTEM_CONFIGURATION_DELETE = 'SYSTEM_CONFIGURATION_DELETE'; -export const PROJECT_CREATION_UPLOAD = 'PROJECT_CREATION_UPLOAD'; +export const BADGES_READ = 'BADGES_READ'; +export const BOM_CREATE = 'BOM_CREATE'; +export const BOM_READ = 'BOM_READ'; +export const FINDING_CREATE = 'FINDING_CREATE'; +export const FINDING_READ = 'FINDING_READ'; +export const FINDING_UPDATE = 'FINDING_UPDATE'; +export const NOTIFICATION_RULE_MANAGEMENT = 'NOTIFICATION_RULE_MANAGEMENT'; export const POLICY_MANAGEMENT = 'POLICY_MANAGEMENT'; -export const POLICY_MANAGEMENT_CREATE = 'POLICY_MANAGEMENT_CREATE'; -export const POLICY_MANAGEMENT_READ = 'POLICY_MANAGEMENT_READ'; -export const POLICY_MANAGEMENT_UPDATE = 'POLICY_MANAGEMENT_UPDATE'; -export const POLICY_MANAGEMENT_DELETE = 'POLICY_MANAGEMENT_DELETE'; +export const POLICY_VIOLATION_CREATE = 'POLICY_VIOLATION_CREATE'; +export const POLICY_VIOLATION_READ = 'POLICY_VIOLATION_READ'; +export const POLICY_VIOLATION_UPDATE = 'POLICY_VIOLATION_UPDATE'; +export const PORTFOLIO_ACCESS_CONTROL_BYPASS = + 'PORTFOLIO_ACCESS_CONTROL_BYPASS'; +export const PORTFOLIO_MANAGEMENT = 'PORTFOLIO_MANAGEMENT'; +export const PROJECT_DELETE = 'PROJECT_DELETE'; +export const PROJECT_READ = 'PROJECT_READ'; +export const PROJECT_UPDATE = 'PROJECT_UPDATE'; +export const SYSTEM_CONFIGURATION = 'SYSTEM_CONFIGURATION'; export const TAG_MANAGEMENT = 'TAG_MANAGEMENT'; -export const TAG_MANAGEMENT_DELETE = 'TAG_MANAGEMENT_DELETE'; +export const VULNERABILITY_MANAGEMENT = 'VULNERABILITY_MANAGEMENT'; + +export default Object.freeze({ + ACCESS_MANAGEMENT, + BADGES_READ, + BOM_CREATE, + BOM_READ, + FINDING_CREATE, + FINDING_READ, + FINDING_UPDATE, + NOTIFICATION_RULE_MANAGEMENT, + POLICY_MANAGEMENT, + POLICY_VIOLATION_CREATE, + POLICY_VIOLATION_READ, + POLICY_VIOLATION_UPDATE, + PORTFOLIO_ACCESS_CONTROL_BYPASS, + PORTFOLIO_MANAGEMENT, + PROJECT_DELETE, + PROJECT_READ, + PROJECT_UPDATE, + SYSTEM_CONFIGURATION, + TAG_MANAGEMENT, + VULNERABILITY_MANAGEMENT, +}); + +let cachedToken = null; +const existingToken = getToken(); + +function parseToken(token) { + if (!token) return null; + const decoded = decodeToken(token); + return Object.freeze({ + ...decoded, + rawToken: token, + rawPermissions: decoded.permissions, + permissions: decoded.permissions?.split(',') || [], + }); +} + +// On module load, try to cache the token if present +if (existingToken) { + cachedToken = parseToken(existingToken); +} + +// Listen for authentication events to update the cached token (e.g. login/logout) +EventBus.$on('authenticated', (jwt) => { + cachedToken = parseToken(jwt); +}); /** * Determines if the current logged in user has a specific permission. - * If the decodedToken is not passed, the function will automatically - * retrieve and decode it. */ -export const hasPermission = function hasPermission(permission, decodedToken) { - const token = decodedToken || decodeToken(getToken()); - const permissions = token?.permissions?.split(',') || []; - if (typeof permission == 'string') { - return permissions.includes(permission); - } else if (Array.isArray(permission)) { - for (let perm of permission) { - if (permissions.includes(perm)) { - return true; - } +export function hasPermission(permission, operation = 'and') { + if (!cachedToken) return false; + + if (typeof permission === 'string') { + return cachedToken.permissions.includes(permission); + } + + if (Array.isArray(permission)) { + switch (operation) { + case 'and': + return permission.every((perm) => + cachedToken.permissions.includes(perm), + ); + case 'not': + return permission.every( + (perm) => !cachedToken.permissions.includes(perm), + ); + case 'or': + default: + return permission.some((perm) => + cachedToken.permissions.includes(perm), + ); + } + } + + return false; +} + +/** + * Recursively determines if a user has complex permissions. + * Complex permissions can be specified as: + * - A string: single permission (e.g. "perm_x") + * - An array: all permissions required (AND logic, e.g. ["perm_x", "perm_y"]) + * - An object: + * { and: [...] } // All required (AND) + * { or: [...] } // Any required (OR) + * { not: [...] } // None must be present (NOT) + * Nested structures are supported. + * + * Example: + * { + * and: [ + * "perm_x", + * { or: ["perm_y", "perm_z"] }, + * { not: ["perm_a"] } + * ] + * } + * Means: perm_x AND (perm_y OR perm_z) AND (NOT perm_a) + * + * @param {string|Array|Object} requiredPermissions - The required permission(s). + * @param {Array} userPermissions - The user's permissions as an array of strings. + * @returns {boolean} True if the user has the required permissions, false otherwise. + */ +export function hasComplexPermission(requiredPermissions) { + if (!requiredPermissions) return true; + + if (typeof requiredPermissions === 'string') + return cachedToken.permissions?.includes(requiredPermissions); + + if (Array.isArray(requiredPermissions)) { + return requiredPermissions.every((r) => hasComplexPermission(r)); + } + + if (typeof requiredPermissions === 'object') { + if (requiredPermissions.and) { + return requiredPermissions.and.every((r) => hasComplexPermission(r)); + } + + if (requiredPermissions.or) { + return requiredPermissions.or.some((r) => hasComplexPermission(r)); + } + + if (requiredPermissions.not) { + return requiredPermissions.not.every((r) => !hasComplexPermission(r)); } - return false; } -}; + throw new Error( + 'Unrecognized permission structure: ' + JSON.stringify(requiredPermissions), + ); +} /** * Returns the decoded token as a JSON object. */ -export const decodeToken = function decodeToken(token) { - let base64Url = token.split('.')[1]; - let base64 = base64Url.replace('-', '+').replace('_', '/'); +export function decodeToken(token) { + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace('-', '+').replace('_', '/'); return JSON.parse(window.atob(base64)); -}; +} /** * Retrieves the token from session storage. */ -export const getToken = function getToken() { +export function getToken() { return sessionStorage.getItem('token'); -}; +} diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index 36a0013f3..c8f10fb62 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -1,5 +1,5 @@