@webitel/ui-sdk 24.12.95 → 24.12.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/ui-sdk.css +1 -1
- package/dist/ui-sdk.js +2120 -2154
- package/dist/ui-sdk.umd.cjs +16 -16
- package/package.json +14 -8
- package/src/api/defaults/getDefaultInstance/getDefaultInstance.js +5 -2
- package/src/api/defaults/index.js +1 -1
- package/src/components/wt-dual-panel/wt-dual-panel.vue +1 -2
- package/src/components/wt-icon-action/iconMappings.js +1 -1
- package/src/composables/useAccessControl/v2/createUserAccessControl.ts +66 -0
- package/src/composables/useAccessControl/v2/types/CreateUserAccessControl.d.ts +21 -0
- package/src/enums/CrudAction/CrudAction.js +6 -0
- package/src/enums/CrudAction/CrudAction.ts +8 -0
- package/src/enums/WtObject/WtObject.js +51 -1
- package/src/enums/WtObject/WtObject.ts +51 -1
- package/src/enums/index.js +6 -19
- package/src/enums/index.ts +33 -0
- package/src/locale/en/en.js +1 -1
- package/src/locale/ru/ru.js +1 -1
- package/src/locale/ua/ua.js +1 -1
- package/src/modules/Userinfo/api/userinfo.js +21 -34
- package/src/modules/Userinfo/store/UserinfoStoreModule.js +1 -1
- package/src/modules/Userinfo/v2/api/UserinfoAPI.ts +62 -0
- package/src/modules/Userinfo/v2/enums/GlobalActions/GlobalActions.ts +36 -0
- package/src/modules/Userinfo/v2/enums/ScopeClass/ScopeClass.ts +47 -0
- package/src/modules/Userinfo/v2/enums/index.ts +7 -0
- package/src/modules/Userinfo/v2/index.ts +0 -0
- package/src/modules/Userinfo/v2/mappings/mappings.ts +161 -0
- package/src/modules/Userinfo/v2/scripts/utils.ts +123 -0
- package/src/modules/Userinfo/v2/stores/__tests__/accessStore.spec.ts +136 -0
- package/src/modules/Userinfo/v2/stores/accessStore.ts +131 -0
- package/src/modules/Userinfo/v2/stores/userinfoStore.ts +56 -0
- package/src/modules/Userinfo/v2/types/UserAccess.d.ts +118 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import invert from 'lodash/fp/invert';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AdminSections,
|
|
5
|
+
AuditorSections,
|
|
6
|
+
CrmSections,
|
|
7
|
+
CrudAction,
|
|
8
|
+
SupervisorSections,
|
|
9
|
+
WtApplication,
|
|
10
|
+
WtObject,
|
|
11
|
+
} from '../../../../enums';
|
|
12
|
+
import { CrudGlobalAction, ScopeClass } from '../enums';
|
|
13
|
+
|
|
14
|
+
export const mapGlobalActionToCrudAction = {
|
|
15
|
+
[CrudGlobalAction.Add]: CrudAction.Create,
|
|
16
|
+
[CrudGlobalAction.Read]: CrudAction.Read,
|
|
17
|
+
[CrudGlobalAction.Write]: CrudAction.Update,
|
|
18
|
+
[CrudGlobalAction.Delete]: CrudAction.Delete,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const mapCrudActionToGlobalAction = invert(mapGlobalActionToCrudAction);
|
|
22
|
+
|
|
23
|
+
/* one-to-many */
|
|
24
|
+
export const mapScopeClassToWtObjects: Record<ScopeClass, WtObject[]> = {
|
|
25
|
+
[ScopeClass.Users]: [WtObject.User],
|
|
26
|
+
[ScopeClass.Devices]: [WtObject.Device],
|
|
27
|
+
[ScopeClass.Schema]: [WtObject.Flow],
|
|
28
|
+
[ScopeClass.AcrRouting]: [WtObject.Dialplan],
|
|
29
|
+
[ScopeClass.Gateways]: [WtObject.Gateway],
|
|
30
|
+
[ScopeClass.AcrChatPlan]: [WtObject.Chatplan],
|
|
31
|
+
[ScopeClass.Chats]: [WtObject.ChatGateway],
|
|
32
|
+
[ScopeClass.Dictionaries]: [
|
|
33
|
+
WtObject.Region,
|
|
34
|
+
WtObject.Bucket,
|
|
35
|
+
WtObject.Communication,
|
|
36
|
+
WtObject.PauseCause,
|
|
37
|
+
],
|
|
38
|
+
[ScopeClass.List]: [WtObject.Blacklist],
|
|
39
|
+
[ScopeClass.Skills]: [WtObject.Skill],
|
|
40
|
+
[ScopeClass.Calendars]: [WtObject.Calendar],
|
|
41
|
+
[ScopeClass.MediaFile]: [WtObject.Media],
|
|
42
|
+
[ScopeClass.Agent]: [WtObject.Agent],
|
|
43
|
+
[ScopeClass.Queue]: [WtObject.Queue, WtObject.Member],
|
|
44
|
+
[ScopeClass.ResourceGroup]: [WtObject.ResourceGroup],
|
|
45
|
+
[ScopeClass.Resource]: [WtObject.Resource],
|
|
46
|
+
[ScopeClass.Team]: [WtObject.Team],
|
|
47
|
+
[ScopeClass.StorageProfile]: [WtObject.Storage],
|
|
48
|
+
[ScopeClass.CognitiveProfile]: [WtObject.CognitiveProfile],
|
|
49
|
+
[ScopeClass.EmailProfile]: [WtObject.EmailProfile],
|
|
50
|
+
[ScopeClass.SingleSignOn]: [WtObject.SingleSignOn],
|
|
51
|
+
[ScopeClass.ImportTemplate]: [WtObject.ImportCsv],
|
|
52
|
+
[ScopeClass.Trigger]: [WtObject.Trigger],
|
|
53
|
+
[ScopeClass.Role]: [WtObject.Role],
|
|
54
|
+
[ScopeClass.Contacts]: [WtObject.Contact],
|
|
55
|
+
[ScopeClass.Logger]: [WtObject.Logger], // Change log in Admin
|
|
56
|
+
[ScopeClass.Calls]: [WtObject.Call], // Call history
|
|
57
|
+
[ScopeClass.RecordFile]: [WtObject.RecordFile], // Call history
|
|
58
|
+
[ScopeClass.ContactGroups]: [WtObject.ContactGroup], //CRM
|
|
59
|
+
[ScopeClass.ChatBots]: [WtObject.ChatBot], // routing cht_gateway
|
|
60
|
+
[ScopeClass.Cases]: [WtObject.Cases], // CRM
|
|
61
|
+
[ScopeClass.CaseComments]: [WtObject.CaseComment],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const mapScopeClassAccessTokenToCrudAction = {
|
|
65
|
+
r: CrudAction.Read,
|
|
66
|
+
w: CrudAction.Update,
|
|
67
|
+
d: CrudAction.Delete,
|
|
68
|
+
x: CrudAction.Create,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const mapCrudActionToScopeClassAccessToken = invert(
|
|
72
|
+
mapScopeClassAccessTokenToCrudAction,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
type UiSection =
|
|
76
|
+
| AdminSections
|
|
77
|
+
| AuditorSections
|
|
78
|
+
| CrmSections
|
|
79
|
+
| SupervisorSections;
|
|
80
|
+
export const mapWtObjectToUiSection: Record<
|
|
81
|
+
WtApplication,
|
|
82
|
+
Partial<Record<WtObject, UiSection>>
|
|
83
|
+
> = {
|
|
84
|
+
// if we don`t need empty objects we can use Partial<Record<WtApplication, Partial<Record<WtObject, UiSection>>>>
|
|
85
|
+
[WtApplication.Agent]: {},
|
|
86
|
+
[WtApplication.History]: {},
|
|
87
|
+
[WtApplication.Analytics]: {},
|
|
88
|
+
|
|
89
|
+
// Admin sections
|
|
90
|
+
[WtApplication.Admin]: {
|
|
91
|
+
[WtObject.User]: AdminSections.Users,
|
|
92
|
+
[WtObject.Agent]: AdminSections.Agents,
|
|
93
|
+
[WtObject.License]: AdminSections.License,
|
|
94
|
+
[WtObject.Device]: AdminSections.Devices,
|
|
95
|
+
[WtObject.Flow]: AdminSections.Flow,
|
|
96
|
+
[WtObject.Dialplan]: AdminSections.Dialplan,
|
|
97
|
+
[WtObject.Gateway]: AdminSections.Gateways,
|
|
98
|
+
[WtObject.Chatplan]: AdminSections.Chatplan,
|
|
99
|
+
[WtObject.ChatGateway]: AdminSections.ChatGateways,
|
|
100
|
+
[WtObject.Skill]: AdminSections.Skills,
|
|
101
|
+
[WtObject.Bucket]: AdminSections.Buckets,
|
|
102
|
+
[WtObject.Blacklist]: AdminSections.Blacklist,
|
|
103
|
+
[WtObject.Region]: AdminSections.Regions,
|
|
104
|
+
[WtObject.Calendar]: AdminSections.Calendars,
|
|
105
|
+
[WtObject.Communication]: AdminSections.Communications,
|
|
106
|
+
[WtObject.PauseCause]: AdminSections.PauseCause,
|
|
107
|
+
[WtObject.Media]: AdminSections.Media,
|
|
108
|
+
[WtObject.Team]: AdminSections.Teams,
|
|
109
|
+
[WtObject.Resource]: AdminSections.Resources,
|
|
110
|
+
[WtObject.ResourceGroup]: AdminSections.ResourceGroups,
|
|
111
|
+
[WtObject.Queue]: AdminSections.Queues,
|
|
112
|
+
[WtObject.Storage]: AdminSections.Storage,
|
|
113
|
+
[WtObject.CognitiveProfile]: AdminSections.CognitiveProfiles,
|
|
114
|
+
[WtObject.EmailProfile]: AdminSections.EmailProfiles,
|
|
115
|
+
[WtObject.ImportCsv]: AdminSections.ImportCsv,
|
|
116
|
+
[WtObject.Trigger]: AdminSections.Triggers,
|
|
117
|
+
[WtObject.Role]: AdminSections.Roles,
|
|
118
|
+
[WtObject.Object]: AdminSections.Objects,
|
|
119
|
+
[WtObject.ChangeLog]: AdminSections.Changelogs,
|
|
120
|
+
[WtObject.Configuration]: AdminSections.Configuration,
|
|
121
|
+
[WtObject.GlobalVariable]: AdminSections.GlobalVariables,
|
|
122
|
+
[WtObject.ShiftTemplate]: AdminSections.ShiftTemplates,
|
|
123
|
+
[WtObject.PauseTemplate]: AdminSections.PauseTemplates,
|
|
124
|
+
[WtObject.WorkingCondition]: AdminSections.WorkingConditions,
|
|
125
|
+
[WtObject.Member]: AdminSections.Members,
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// Auditor sections
|
|
129
|
+
[WtApplication.Audit]: {
|
|
130
|
+
[WtObject.Scorecard]: AuditorSections.Scorecards,
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// Crm sections
|
|
134
|
+
[WtApplication.Crm]: {
|
|
135
|
+
[WtObject.Contact]: CrmSections.Contacts,
|
|
136
|
+
[WtObject.Cases]: CrmSections.Cases,
|
|
137
|
+
[WtObject.Slas]: CrmSections.Slas,
|
|
138
|
+
[WtObject.ServiceCatalog]: CrmSections.ServiceCatalogs,
|
|
139
|
+
[WtObject.CaseSource]: CrmSections.CaseSources,
|
|
140
|
+
[WtObject.CloseReasonGroup]: CrmSections.CloseReasonGroups,
|
|
141
|
+
[WtObject.Priorities]: CrmSections.Priorities,
|
|
142
|
+
[WtObject.Status]: CrmSections.Statuses,
|
|
143
|
+
[WtObject.Source]: CrmSections.Sources,
|
|
144
|
+
[WtObject.ContactGroup]: CrmSections.ContactGroups,
|
|
145
|
+
[WtObject.CustomLookup]: CrmSections.CustomLookups,
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// Supervisor sections
|
|
149
|
+
[WtApplication.Supervisor]: {
|
|
150
|
+
[WtObject.Queue]: SupervisorSections.Queues,
|
|
151
|
+
[WtObject.Agent]: SupervisorSections.Agents,
|
|
152
|
+
[WtObject.Communication]: SupervisorSections.ActiveCalls,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const mapUiSectionToWtObject = invert(mapWtObjectToUiSection);
|
|
157
|
+
|
|
158
|
+
export const AdminSectionsValues = invert(AdminSections);
|
|
159
|
+
export const AuditorSectionsValues = invert(AuditorSections);
|
|
160
|
+
export const CrmSectionsValues = invert(CrmSections);
|
|
161
|
+
export const SupervisorSectionsValues = invert(SupervisorSections);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { CrudAction, WtApplication, WtObject } from '../../../../enums';
|
|
2
|
+
import { _wtUiLog as wtlog } from '../../../../scripts/logger';
|
|
3
|
+
import {
|
|
4
|
+
AdminSectionsValues,
|
|
5
|
+
AuditorSectionsValues,
|
|
6
|
+
CrmSectionsValues,
|
|
7
|
+
mapGlobalActionToCrudAction,
|
|
8
|
+
mapScopeClassAccessTokenToCrudAction,
|
|
9
|
+
mapScopeClassToWtObjects,
|
|
10
|
+
mapUiSectionToWtObject,
|
|
11
|
+
mapWtObjectToUiSection,
|
|
12
|
+
SupervisorSectionsValues,
|
|
13
|
+
} from '../mappings/mappings';
|
|
14
|
+
import type {
|
|
15
|
+
AppVisibilityMap,
|
|
16
|
+
GlobalAccessApiResponseItem,
|
|
17
|
+
GlobalAction,
|
|
18
|
+
GlobalActionAccessMap,
|
|
19
|
+
ScopeAccessApiResponseItem,
|
|
20
|
+
ScopeAccessMap,
|
|
21
|
+
SectionVisibilityMap,
|
|
22
|
+
UiSection,
|
|
23
|
+
VisibilityAccess,
|
|
24
|
+
} from '../types/UserAccess.d.ts';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @internal
|
|
28
|
+
* @description
|
|
29
|
+
* backend -> frontend
|
|
30
|
+
* */
|
|
31
|
+
const castGlobalActionToCrudAction = (
|
|
32
|
+
globalAction: GlobalAction,
|
|
33
|
+
): CrudAction | null => {
|
|
34
|
+
return mapGlobalActionToCrudAction[globalAction] || null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const makeGlobalAccessMap = (
|
|
38
|
+
rawGlobalAccess: GlobalAccessApiResponseItem[],
|
|
39
|
+
): GlobalActionAccessMap => {
|
|
40
|
+
return rawGlobalAccess.reduce((map, { id }) => {
|
|
41
|
+
const key = castGlobalActionToCrudAction(id) || id;
|
|
42
|
+
return map.set(key, true);
|
|
43
|
+
}, new Map());
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const makeScopeAccessMap = (
|
|
47
|
+
rawScope: ScopeAccessApiResponseItem[],
|
|
48
|
+
): ScopeAccessMap => {
|
|
49
|
+
const map = new Map();
|
|
50
|
+
|
|
51
|
+
rawScope.forEach(({ class: scopeClass, access: scopeAccess }) => {
|
|
52
|
+
const access = scopeAccess.split('').reduce((accessMap, token) => {
|
|
53
|
+
accessMap.set(mapScopeClassAccessTokenToCrudAction[token], true);
|
|
54
|
+
return accessMap;
|
|
55
|
+
}, new Map());
|
|
56
|
+
|
|
57
|
+
const scopeClassObjects = mapScopeClassToWtObjects[scopeClass];
|
|
58
|
+
|
|
59
|
+
if (!scopeClassObjects) {
|
|
60
|
+
wtlog.error({ module: 'modules/userinfo' })(
|
|
61
|
+
'Unknown scope class to convert to WtObject:',
|
|
62
|
+
scopeClass,
|
|
63
|
+
);
|
|
64
|
+
map.set(scopeClass, access);
|
|
65
|
+
} else {
|
|
66
|
+
scopeClassObjects.forEach((object) => {
|
|
67
|
+
map.set(object, access);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return map;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const makeAppVisibilityMap = (
|
|
76
|
+
rawVisibility: VisibilityAccess,
|
|
77
|
+
): AppVisibilityMap => {
|
|
78
|
+
const map = new Map();
|
|
79
|
+
Object.entries(rawVisibility).forEach(([app, visibility]) => {
|
|
80
|
+
map.set(app, visibility._enabled);
|
|
81
|
+
});
|
|
82
|
+
return map;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const makeSectionVisibilityMap = (
|
|
86
|
+
rawVisibility: VisibilityAccess,
|
|
87
|
+
): SectionVisibilityMap => {
|
|
88
|
+
const map = new Map();
|
|
89
|
+
|
|
90
|
+
Object.values(rawVisibility).forEach((appSectionsVisibility) => {
|
|
91
|
+
Object.entries(appSectionsVisibility).forEach(([section, visibility]) => {
|
|
92
|
+
if (section.startsWith('_')) return map; // skip private fields
|
|
93
|
+
map.set(section, visibility._enabled);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return map;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const castUiSectionToWtObject = (section: UiSection): WtObject => {
|
|
101
|
+
return mapUiSectionToWtObject[section];
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const castWtObjectToUiSection = (object: WtObject): UiSection => {
|
|
105
|
+
return mapWtObjectToUiSection[object];
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const getWtAppByUiSection = (section: UiSection): WtApplication => {
|
|
109
|
+
/* use inverted maps because UiSection is the enum value, not key */
|
|
110
|
+
if (AdminSectionsValues[section]) {
|
|
111
|
+
return WtApplication.Admin;
|
|
112
|
+
}
|
|
113
|
+
if (AuditorSectionsValues[section]) {
|
|
114
|
+
return WtApplication.Audit;
|
|
115
|
+
}
|
|
116
|
+
if (CrmSectionsValues[section]) {
|
|
117
|
+
return WtApplication.Crm;
|
|
118
|
+
}
|
|
119
|
+
if (SupervisorSectionsValues[section]) {
|
|
120
|
+
return WtApplication.Supervisor;
|
|
121
|
+
}
|
|
122
|
+
wtlog.error({ module: 'modules/userinfo' })('Unknown section:', section);
|
|
123
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {createPinia, setActivePinia, type StoreDefinition} from 'pinia';
|
|
2
|
+
import { beforeEach, describe, expect,it } from 'vitest';
|
|
3
|
+
import {createApp, h} from "vue";
|
|
4
|
+
import {createRouter, createWebHistory, type Router} from "vue-router";
|
|
5
|
+
|
|
6
|
+
import {AdminSections, WtApplication, WtObject} from "../../../../../enums";
|
|
7
|
+
import {CrudGlobalAction} from "../../enums";
|
|
8
|
+
import { createUserAccessStore } from '../accessStore';
|
|
9
|
+
|
|
10
|
+
describe('AccessStore', () => {
|
|
11
|
+
let router: Router;
|
|
12
|
+
let useAccessStore: StoreDefinition;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
router = createRouter({
|
|
16
|
+
history: createWebHistory(),
|
|
17
|
+
routes: [
|
|
18
|
+
{
|
|
19
|
+
path: '/',
|
|
20
|
+
name: 'home',
|
|
21
|
+
component: () => h('div', 'home'),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
path: '/users',
|
|
25
|
+
name: 'users',
|
|
26
|
+
component: () => h('div', 'users'),
|
|
27
|
+
meta: {
|
|
28
|
+
WtObject: WtObject.User,
|
|
29
|
+
UiSection: AdminSections.Users,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const pinia = createPinia();
|
|
36
|
+
const app = createApp({});
|
|
37
|
+
app.use(router);
|
|
38
|
+
app.use(pinia)
|
|
39
|
+
setActivePinia(pinia);
|
|
40
|
+
useAccessStore = createUserAccessStore();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('restricts route if no access', async () => {
|
|
44
|
+
const { initialize, routeAccessGuard } = useAccessStore();
|
|
45
|
+
router.beforeEach(routeAccessGuard);
|
|
46
|
+
|
|
47
|
+
initialize({
|
|
48
|
+
permissions: [],
|
|
49
|
+
scope: [],
|
|
50
|
+
access: {},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await router.push({ name: 'users' });
|
|
54
|
+
|
|
55
|
+
/* because guard should not allow to navigate
|
|
56
|
+
there since we pass empty permissions */
|
|
57
|
+
expect(router.currentRoute.value.name).not.toBe('users');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('allows route access if has global permission', async () => {
|
|
61
|
+
const { initialize, routeAccessGuard } = useAccessStore();
|
|
62
|
+
router.beforeEach(routeAccessGuard);
|
|
63
|
+
|
|
64
|
+
initialize({
|
|
65
|
+
permissions: [{ id: CrudGlobalAction.Read }],
|
|
66
|
+
scope: [],
|
|
67
|
+
access: {},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await router.push({ name: 'users' });
|
|
71
|
+
expect(router.currentRoute.value.name).toBe('users');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('allows route access if has scope permission, app visibility and section visibility', async () => {
|
|
75
|
+
const { initialize, routeAccessGuard } = useAccessStore();
|
|
76
|
+
router.beforeEach(routeAccessGuard);
|
|
77
|
+
|
|
78
|
+
initialize({
|
|
79
|
+
permissions: [],
|
|
80
|
+
scope: [{ class: 'users', access: 'r' }],
|
|
81
|
+
access: {
|
|
82
|
+
[WtApplication.Admin]: {
|
|
83
|
+
_enabled: true,
|
|
84
|
+
[AdminSections.Users]: {
|
|
85
|
+
_enabled: true,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await router.push({ name: 'users' });
|
|
92
|
+
expect(router.currentRoute.value.name).toBe('users');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('restricts route access if has scope permission, app visibility, but no section visibility', async () => {
|
|
96
|
+
const { initialize, routeAccessGuard } = useAccessStore();
|
|
97
|
+
router.beforeEach(routeAccessGuard);
|
|
98
|
+
|
|
99
|
+
initialize({
|
|
100
|
+
permissions: [],
|
|
101
|
+
scope: [{ class: 'users', access: 'r' }],
|
|
102
|
+
access: {
|
|
103
|
+
[WtApplication.Admin]: {
|
|
104
|
+
_enabled: true,
|
|
105
|
+
[AdminSections.Users]: {
|
|
106
|
+
_enabled: false,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await router.push({ name: 'users' });
|
|
113
|
+
expect(router.currentRoute.value.name).not.toBe('users');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('restricts route access if has scope permission, section visibility but no app visibility', async () => {
|
|
117
|
+
const { initialize, routeAccessGuard } = useAccessStore();
|
|
118
|
+
router.beforeEach(routeAccessGuard);
|
|
119
|
+
|
|
120
|
+
initialize({
|
|
121
|
+
permissions: [],
|
|
122
|
+
scope: [{ class: 'users', access: 'r' }],
|
|
123
|
+
access: {
|
|
124
|
+
[WtApplication.Admin]: {
|
|
125
|
+
_enabled: false,
|
|
126
|
+
[AdminSections.Users]: {
|
|
127
|
+
_enabled: true,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
await router.push({ name: 'users' });
|
|
134
|
+
expect(router.currentRoute.value.name).not.toBe('users');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { defineStore } from 'pinia';
|
|
2
|
+
import { NavigationGuard } from 'vue-router';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CrudAction,
|
|
6
|
+
type WtApplication,
|
|
7
|
+
type WtObject,
|
|
8
|
+
} from '../../../../enums';
|
|
9
|
+
import type { SpecialGlobalAction } from '../enums';
|
|
10
|
+
import {
|
|
11
|
+
getWtAppByUiSection,
|
|
12
|
+
makeAppVisibilityMap,
|
|
13
|
+
makeGlobalAccessMap,
|
|
14
|
+
makeScopeAccessMap,
|
|
15
|
+
makeSectionVisibilityMap,
|
|
16
|
+
} from '../scripts/utils';
|
|
17
|
+
import type {
|
|
18
|
+
AppVisibilityMap,
|
|
19
|
+
CreateUserAccessStoreConfig,
|
|
20
|
+
CreateUserAccessStoreRawAccess,
|
|
21
|
+
GlobalActionAccessMap,
|
|
22
|
+
ScopeAccessMap,
|
|
23
|
+
SectionVisibilityMap,
|
|
24
|
+
UiSection,
|
|
25
|
+
UserAccessStore,
|
|
26
|
+
} from '../types/UserAccess.d.ts';
|
|
27
|
+
|
|
28
|
+
export const createUserAccessStore = ({
|
|
29
|
+
namespace = 'userinfo',
|
|
30
|
+
}: CreateUserAccessStoreConfig = {}) => {
|
|
31
|
+
return defineStore(`${namespace}/access`, (): UserAccessStore => {
|
|
32
|
+
let globalAccess: GlobalActionAccessMap = new Map();
|
|
33
|
+
|
|
34
|
+
let scopeAccess: ScopeAccessMap = new Map();
|
|
35
|
+
|
|
36
|
+
let appVisibilityAccess: AppVisibilityMap = new Map();
|
|
37
|
+
|
|
38
|
+
let sectionVisibilityAccess: SectionVisibilityMap = new Map();
|
|
39
|
+
|
|
40
|
+
const hasAccess = (
|
|
41
|
+
action: CrudAction | SpecialGlobalAction,
|
|
42
|
+
object?: WtObject,
|
|
43
|
+
) => {
|
|
44
|
+
const allowGlobalAccess = globalAccess.get(action);
|
|
45
|
+
if (allowGlobalAccess) return true;
|
|
46
|
+
|
|
47
|
+
const allowScopeAccess =
|
|
48
|
+
object && scopeAccess.get(object)?.get(action as CrudAction);
|
|
49
|
+
if (allowScopeAccess) return true;
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const hasReadAccess = (object?: WtObject) => {
|
|
55
|
+
return hasAccess(CrudAction.Read, object);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const hasCreateAccess = (object?: WtObject) => {
|
|
59
|
+
return hasAccess(CrudAction.Create, object);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const hasUpdateAccess = (object?: WtObject) => {
|
|
63
|
+
return hasAccess(CrudAction.Update, object);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const hasDeleteAccess = (object?: WtObject) => {
|
|
67
|
+
return hasAccess(CrudAction.Delete, object);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const hasApplicationVisibility = (app: WtApplication) => {
|
|
71
|
+
return hasReadAccess() || appVisibilityAccess.get(app);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const hasSectionVisibility = (section: UiSection, object: WtObject) => {
|
|
75
|
+
const appOfSection = getWtAppByUiSection(section);
|
|
76
|
+
const objectOfSection = object; /*castUiSectionToWtObject(section)*/
|
|
77
|
+
const hasSectionVisibilityAccess = (section: UiSection) => {
|
|
78
|
+
return sectionVisibilityAccess.get(section);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const allowGlobalAccess = hasReadAccess();
|
|
82
|
+
if (allowGlobalAccess) return true;
|
|
83
|
+
|
|
84
|
+
const allowAppVisibility = hasApplicationVisibility(appOfSection);
|
|
85
|
+
const allowObjectAccess = hasReadAccess(objectOfSection);
|
|
86
|
+
const allowSectionVisibility = hasSectionVisibilityAccess(section);
|
|
87
|
+
|
|
88
|
+
return allowAppVisibility && allowObjectAccess && allowSectionVisibility;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const routeAccessGuard: NavigationGuard = (to) => {
|
|
92
|
+
/* find last because "matched" has top=>bottom routes order */
|
|
93
|
+
const uiSection = to.matched
|
|
94
|
+
.toReversed()
|
|
95
|
+
.find(({ meta }) => meta.UiSection)?.meta?.UiSection as UiSection;
|
|
96
|
+
/* find last because "matched" has top=>bottom routes order */
|
|
97
|
+
const wtObject = to.matched
|
|
98
|
+
.toReversed()
|
|
99
|
+
.find(({ meta }) => meta.UiSection)?.meta?.WtObject as WtObject;
|
|
100
|
+
|
|
101
|
+
if (uiSection && !hasSectionVisibility(uiSection, wtObject)) {
|
|
102
|
+
// return false;
|
|
103
|
+
return { path: '/access-denied' };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return true;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const initialize = ({
|
|
110
|
+
permissions: rawGlobalAccess,
|
|
111
|
+
scope: rawScopeAccess,
|
|
112
|
+
access: rawVisibilityAccess,
|
|
113
|
+
}: CreateUserAccessStoreRawAccess) => {
|
|
114
|
+
globalAccess = makeGlobalAccessMap(rawGlobalAccess);
|
|
115
|
+
scopeAccess = makeScopeAccessMap(rawScopeAccess);
|
|
116
|
+
appVisibilityAccess = makeAppVisibilityMap(rawVisibilityAccess);
|
|
117
|
+
sectionVisibilityAccess = makeSectionVisibilityMap(rawVisibilityAccess);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
initialize,
|
|
122
|
+
|
|
123
|
+
hasReadAccess,
|
|
124
|
+
hasCreateAccess,
|
|
125
|
+
hasUpdateAccess,
|
|
126
|
+
hasDeleteAccess,
|
|
127
|
+
|
|
128
|
+
routeAccessGuard,
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { defineStore } from 'pinia';
|
|
2
|
+
import { ref } from 'vue';
|
|
3
|
+
|
|
4
|
+
import { getSession, getUiVisibilityAccess } from '../api/UserinfoAPI';
|
|
5
|
+
import { createUserAccessStore } from './accessStore';
|
|
6
|
+
|
|
7
|
+
export const createUserinfoStore = () => {
|
|
8
|
+
const namespace = 'userinfo';
|
|
9
|
+
const useAccessStore = createUserAccessStore({
|
|
10
|
+
namespace,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const store = defineStore(namespace, () => {
|
|
14
|
+
const accessStore = useAccessStore();
|
|
15
|
+
const {
|
|
16
|
+
hasReadAccess,
|
|
17
|
+
hasCreateAccess,
|
|
18
|
+
hasUpdateAccess,
|
|
19
|
+
hasDeleteAccess,
|
|
20
|
+
initialize: initializeAccessStore,
|
|
21
|
+
routeAccessGuard,
|
|
22
|
+
} = accessStore;
|
|
23
|
+
|
|
24
|
+
const userId = ref();
|
|
25
|
+
|
|
26
|
+
const initialize = async () => {
|
|
27
|
+
const { scope, permissions, ...userinfo } = await getSession();
|
|
28
|
+
const access = await getUiVisibilityAccess();
|
|
29
|
+
|
|
30
|
+
userId.value = userinfo.userId;
|
|
31
|
+
|
|
32
|
+
initializeAccessStore({
|
|
33
|
+
scope,
|
|
34
|
+
permissions,
|
|
35
|
+
access,
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
userId,
|
|
41
|
+
initialize,
|
|
42
|
+
|
|
43
|
+
hasReadAccess,
|
|
44
|
+
hasCreateAccess,
|
|
45
|
+
hasUpdateAccess,
|
|
46
|
+
hasDeleteAccess,
|
|
47
|
+
|
|
48
|
+
routeAccessGuard,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// @ts-ignore
|
|
53
|
+
window._userinfoStore = store;
|
|
54
|
+
|
|
55
|
+
return store;
|
|
56
|
+
};
|