@lightdash/common 0.1936.2 → 0.1937.1
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/dist/cjs/authorization/index.d.ts +8 -4
- package/dist/cjs/authorization/index.d.ts.map +1 -1
- package/dist/cjs/authorization/index.js +26 -3
- package/dist/cjs/authorization/index.js.map +1 -1
- package/dist/cjs/authorization/index.mock.d.ts +2 -0
- package/dist/cjs/authorization/index.mock.d.ts.map +1 -1
- package/dist/cjs/authorization/index.mock.js +2 -0
- package/dist/cjs/authorization/index.mock.js.map +1 -1
- package/dist/cjs/authorization/organizationMemberAbility.mock.d.ts.map +1 -1
- package/dist/cjs/authorization/organizationMemberAbility.mock.js +1 -0
- package/dist/cjs/authorization/organizationMemberAbility.mock.js.map +1 -1
- package/dist/cjs/authorization/parseScopes.d.ts +3 -2
- package/dist/cjs/authorization/parseScopes.d.ts.map +1 -1
- package/dist/cjs/authorization/parseScopes.js +17 -8
- package/dist/cjs/authorization/parseScopes.js.map +1 -1
- package/dist/cjs/authorization/parseScopes.test.js +9 -14
- package/dist/cjs/authorization/parseScopes.test.js.map +1 -1
- package/dist/cjs/authorization/projectMemberAbility.mock.d.ts.map +1 -1
- package/dist/cjs/authorization/projectMemberAbility.mock.js +1 -0
- package/dist/cjs/authorization/projectMemberAbility.mock.js.map +1 -1
- package/dist/cjs/authorization/roleToScopeMapping.d.ts +19 -0
- package/dist/cjs/authorization/roleToScopeMapping.d.ts.map +1 -0
- package/dist/cjs/authorization/roleToScopeMapping.js +163 -0
- package/dist/cjs/authorization/roleToScopeMapping.js.map +1 -0
- package/dist/cjs/authorization/roleToScopeMapping.test.d.ts +2 -0
- package/dist/cjs/authorization/roleToScopeMapping.test.d.ts.map +1 -0
- package/dist/cjs/authorization/roleToScopeMapping.test.js +549 -0
- package/dist/cjs/authorization/roleToScopeMapping.test.js.map +1 -0
- package/dist/cjs/authorization/roleToScopeMapping.testUtils.d.ts +1393 -0
- package/dist/cjs/authorization/roleToScopeMapping.testUtils.d.ts.map +1 -0
- package/dist/cjs/authorization/roleToScopeMapping.testUtils.js +329 -0
- package/dist/cjs/authorization/roleToScopeMapping.testUtils.js.map +1 -0
- package/dist/cjs/authorization/scopeAbilityBuilder.d.ts +14 -8
- package/dist/cjs/authorization/scopeAbilityBuilder.d.ts.map +1 -1
- package/dist/cjs/authorization/scopeAbilityBuilder.js +7 -7
- package/dist/cjs/authorization/scopeAbilityBuilder.js.map +1 -1
- package/dist/cjs/authorization/scopeAbilityBuilder.test.js +258 -185
- package/dist/cjs/authorization/scopeAbilityBuilder.test.js.map +1 -1
- package/dist/cjs/authorization/scopes.d.ts.map +1 -1
- package/dist/cjs/authorization/scopes.js +132 -187
- package/dist/cjs/authorization/scopes.js.map +1 -1
- package/dist/cjs/ee/AiAgent/schemas/tools/index.d.ts +1 -0
- package/dist/cjs/ee/AiAgent/schemas/tools/index.d.ts.map +1 -1
- package/dist/cjs/ee/AiAgent/schemas/tools/index.js +1 -0
- package/dist/cjs/ee/AiAgent/schemas/tools/index.js.map +1 -1
- package/dist/cjs/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.d.ts +2845 -0
- package/dist/cjs/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.d.ts.map +1 -0
- package/dist/cjs/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.js +41 -0
- package/dist/cjs/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.js.map +1 -0
- package/dist/cjs/types/organizationMemberProfile.d.ts +1 -0
- package/dist/cjs/types/organizationMemberProfile.d.ts.map +1 -1
- package/dist/cjs/types/organizationMemberProfile.js.map +1 -1
- package/dist/cjs/types/projectMemberProfile.d.ts +1 -0
- package/dist/cjs/types/projectMemberProfile.d.ts.map +1 -1
- package/dist/cjs/types/scopes.d.ts +19 -9
- package/dist/cjs/types/scopes.d.ts.map +1 -1
- package/dist/cjs/types/search.d.ts +20 -0
- package/dist/cjs/types/search.d.ts.map +1 -1
- package/dist/cjs/types/search.js.map +1 -1
- package/dist/cjs/types/user.d.ts +1 -0
- package/dist/cjs/types/user.d.ts.map +1 -1
- package/dist/cjs/types/user.js.map +1 -1
- package/dist/esm/authorization/index.d.ts +8 -4
- package/dist/esm/authorization/index.d.ts.map +1 -1
- package/dist/esm/authorization/index.js +26 -3
- package/dist/esm/authorization/index.js.map +1 -1
- package/dist/esm/authorization/index.mock.d.ts +2 -0
- package/dist/esm/authorization/index.mock.d.ts.map +1 -1
- package/dist/esm/authorization/index.mock.js +2 -0
- package/dist/esm/authorization/index.mock.js.map +1 -1
- package/dist/esm/authorization/organizationMemberAbility.mock.d.ts.map +1 -1
- package/dist/esm/authorization/organizationMemberAbility.mock.js +1 -0
- package/dist/esm/authorization/organizationMemberAbility.mock.js.map +1 -1
- package/dist/esm/authorization/parseScopes.d.ts +3 -2
- package/dist/esm/authorization/parseScopes.d.ts.map +1 -1
- package/dist/esm/authorization/parseScopes.js +15 -7
- package/dist/esm/authorization/parseScopes.js.map +1 -1
- package/dist/esm/authorization/parseScopes.test.js +9 -14
- package/dist/esm/authorization/parseScopes.test.js.map +1 -1
- package/dist/esm/authorization/projectMemberAbility.mock.d.ts.map +1 -1
- package/dist/esm/authorization/projectMemberAbility.mock.js +1 -0
- package/dist/esm/authorization/projectMemberAbility.mock.js.map +1 -1
- package/dist/esm/authorization/roleToScopeMapping.d.ts +19 -0
- package/dist/esm/authorization/roleToScopeMapping.d.ts.map +1 -0
- package/dist/esm/authorization/roleToScopeMapping.js +157 -0
- package/dist/esm/authorization/roleToScopeMapping.js.map +1 -0
- package/dist/esm/authorization/roleToScopeMapping.test.d.ts +2 -0
- package/dist/esm/authorization/roleToScopeMapping.test.d.ts.map +1 -0
- package/dist/esm/authorization/roleToScopeMapping.test.js +547 -0
- package/dist/esm/authorization/roleToScopeMapping.test.js.map +1 -0
- package/dist/esm/authorization/roleToScopeMapping.testUtils.d.ts +1393 -0
- package/dist/esm/authorization/roleToScopeMapping.testUtils.d.ts.map +1 -0
- package/dist/esm/authorization/roleToScopeMapping.testUtils.js +319 -0
- package/dist/esm/authorization/roleToScopeMapping.testUtils.js.map +1 -0
- package/dist/esm/authorization/scopeAbilityBuilder.d.ts +14 -8
- package/dist/esm/authorization/scopeAbilityBuilder.d.ts.map +1 -1
- package/dist/esm/authorization/scopeAbilityBuilder.js +7 -7
- package/dist/esm/authorization/scopeAbilityBuilder.js.map +1 -1
- package/dist/esm/authorization/scopeAbilityBuilder.test.js +259 -186
- package/dist/esm/authorization/scopeAbilityBuilder.test.js.map +1 -1
- package/dist/esm/authorization/scopes.d.ts.map +1 -1
- package/dist/esm/authorization/scopes.js +132 -187
- package/dist/esm/authorization/scopes.js.map +1 -1
- package/dist/esm/ee/AiAgent/schemas/tools/index.d.ts +1 -0
- package/dist/esm/ee/AiAgent/schemas/tools/index.d.ts.map +1 -1
- package/dist/esm/ee/AiAgent/schemas/tools/index.js +1 -0
- package/dist/esm/ee/AiAgent/schemas/tools/index.js.map +1 -1
- package/dist/esm/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.d.ts +2845 -0
- package/dist/esm/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.d.ts.map +1 -0
- package/dist/esm/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.js +38 -0
- package/dist/esm/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.js.map +1 -0
- package/dist/esm/types/organizationMemberProfile.d.ts +1 -0
- package/dist/esm/types/organizationMemberProfile.d.ts.map +1 -1
- package/dist/esm/types/organizationMemberProfile.js.map +1 -1
- package/dist/esm/types/projectMemberProfile.d.ts +1 -0
- package/dist/esm/types/projectMemberProfile.d.ts.map +1 -1
- package/dist/esm/types/scopes.d.ts +19 -9
- package/dist/esm/types/scopes.d.ts.map +1 -1
- package/dist/esm/types/search.d.ts +20 -0
- package/dist/esm/types/search.d.ts.map +1 -1
- package/dist/esm/types/search.js.map +1 -1
- package/dist/esm/types/user.d.ts +1 -0
- package/dist/esm/types/user.d.ts.map +1 -1
- package/dist/esm/types/user.js.map +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/authorization/index.d.ts +8 -4
- package/dist/types/authorization/index.d.ts.map +1 -1
- package/dist/types/authorization/index.mock.d.ts +2 -0
- package/dist/types/authorization/index.mock.d.ts.map +1 -1
- package/dist/types/authorization/organizationMemberAbility.mock.d.ts.map +1 -1
- package/dist/types/authorization/parseScopes.d.ts +3 -2
- package/dist/types/authorization/parseScopes.d.ts.map +1 -1
- package/dist/types/authorization/projectMemberAbility.mock.d.ts.map +1 -1
- package/dist/types/authorization/roleToScopeMapping.d.ts +19 -0
- package/dist/types/authorization/roleToScopeMapping.d.ts.map +1 -0
- package/dist/types/authorization/roleToScopeMapping.test.d.ts +2 -0
- package/dist/types/authorization/roleToScopeMapping.test.d.ts.map +1 -0
- package/dist/types/authorization/roleToScopeMapping.testUtils.d.ts +1393 -0
- package/dist/types/authorization/roleToScopeMapping.testUtils.d.ts.map +1 -0
- package/dist/types/authorization/scopeAbilityBuilder.d.ts +14 -8
- package/dist/types/authorization/scopeAbilityBuilder.d.ts.map +1 -1
- package/dist/types/authorization/scopes.d.ts.map +1 -1
- package/dist/types/ee/AiAgent/schemas/tools/index.d.ts +1 -0
- package/dist/types/ee/AiAgent/schemas/tools/index.d.ts.map +1 -1
- package/dist/types/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.d.ts +2845 -0
- package/dist/types/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.d.ts.map +1 -0
- package/dist/types/types/organizationMemberProfile.d.ts +1 -0
- package/dist/types/types/organizationMemberProfile.d.ts.map +1 -1
- package/dist/types/types/projectMemberProfile.d.ts +1 -0
- package/dist/types/types/projectMemberProfile.d.ts.map +1 -1
- package/dist/types/types/scopes.d.ts +19 -9
- package/dist/types/types/scopes.d.ts.map +1 -1
- package/dist/types/types/search.d.ts +20 -0
- package/dist/types/types/search.d.ts.map +1 -1
- package/dist/types/types/user.d.ts +1 -0
- package/dist/types/types/user.d.ts.map +1 -1
- package/package.json +1 -1
@@ -1,21 +1,28 @@
|
|
1
|
-
import { subject } from '@casl/ability';
|
1
|
+
import { Ability, AbilityBuilder, subject } from '@casl/ability';
|
2
2
|
import { ProjectType } from '../types/projects';
|
3
3
|
import { SpaceMemberRole } from '../types/space';
|
4
4
|
import { buildAbilityFromScopes } from './scopeAbilityBuilder';
|
5
5
|
describe('scopeAbilityBuilder', () => {
|
6
6
|
describe('buildAbilityFromScopes', () => {
|
7
7
|
const baseContext = {
|
8
|
-
organizationUuid: 'org-123',
|
9
8
|
isEnterprise: false,
|
10
9
|
organizationRole: 'admin',
|
11
10
|
projectUuid: 'project-123',
|
11
|
+
userUuid: 'user1',
|
12
12
|
scopes: [],
|
13
13
|
};
|
14
|
+
const baseContextWithOrg = {
|
15
|
+
...baseContext,
|
16
|
+
projectUuid: undefined,
|
17
|
+
organizationUuid: 'org-123',
|
18
|
+
};
|
14
19
|
it('should build ability with organization view permissions', () => {
|
15
|
-
const
|
16
|
-
|
20
|
+
const builder = new AbilityBuilder(Ability);
|
21
|
+
buildAbilityFromScopes({
|
22
|
+
...baseContextWithOrg,
|
17
23
|
scopes: ['view:organization'],
|
18
|
-
});
|
24
|
+
}, builder);
|
25
|
+
const ability = builder.build();
|
19
26
|
expect(ability.can('view', subject('Organization', {
|
20
27
|
organizationUuid: 'org-123',
|
21
28
|
projectUuid: 'project-123',
|
@@ -26,10 +33,12 @@ describe('scopeAbilityBuilder', () => {
|
|
26
33
|
}))).toBe(false);
|
27
34
|
});
|
28
35
|
it('should build ability with dashboard view permissions', () => {
|
29
|
-
const
|
36
|
+
const builder = new AbilityBuilder(Ability);
|
37
|
+
buildAbilityFromScopes({
|
30
38
|
...baseContext,
|
31
39
|
scopes: ['view:dashboard'],
|
32
|
-
});
|
40
|
+
}, builder);
|
41
|
+
const ability = builder.build();
|
33
42
|
// Should be able to view public dashboards
|
34
43
|
expect(ability.can('view', subject('Dashboard', {
|
35
44
|
organizationUuid: 'org-123',
|
@@ -48,10 +57,12 @@ describe('scopeAbilityBuilder', () => {
|
|
48
57
|
...baseContext,
|
49
58
|
userUuid: 'user-456',
|
50
59
|
};
|
51
|
-
const
|
60
|
+
const builder = new AbilityBuilder(Ability);
|
61
|
+
buildAbilityFromScopes({
|
52
62
|
...contextWithUser,
|
53
63
|
scopes: ['view:dashboard'],
|
54
|
-
});
|
64
|
+
}, builder);
|
65
|
+
const ability = builder.build();
|
55
66
|
// Can view dashboards with user access
|
56
67
|
expect(ability.can('view', subject('Dashboard', {
|
57
68
|
organizationUuid: 'org-123',
|
@@ -64,20 +75,24 @@ describe('scopeAbilityBuilder', () => {
|
|
64
75
|
...baseContext,
|
65
76
|
projectUuid: 'project-789',
|
66
77
|
};
|
67
|
-
const
|
78
|
+
const builder = new AbilityBuilder(Ability);
|
79
|
+
buildAbilityFromScopes({
|
68
80
|
...projectContext,
|
69
81
|
scopes: ['view:project'],
|
70
|
-
});
|
82
|
+
}, builder);
|
83
|
+
const ability = builder.build();
|
71
84
|
expect(ability.can('view', subject('Project', {
|
72
85
|
organizationUuid: 'org-123',
|
73
86
|
projectUuid: 'project-789',
|
74
87
|
}))).toBe(true);
|
75
88
|
});
|
76
89
|
it('should build ability with project creation permissions and type restrictions', () => {
|
77
|
-
const
|
78
|
-
|
90
|
+
const builder = new AbilityBuilder(Ability);
|
91
|
+
buildAbilityFromScopes({
|
92
|
+
...baseContextWithOrg,
|
79
93
|
scopes: ['create:project'],
|
80
|
-
});
|
94
|
+
}, builder);
|
95
|
+
const ability = builder.build();
|
81
96
|
// Can create preview projects
|
82
97
|
expect(ability.can('create', subject('Project', {
|
83
98
|
organizationUuid: 'org-123',
|
@@ -91,13 +106,15 @@ describe('scopeAbilityBuilder', () => {
|
|
91
106
|
});
|
92
107
|
it('should build ability with editor permissions for dashboards', () => {
|
93
108
|
const editorContext = {
|
94
|
-
...
|
109
|
+
...baseContextWithOrg,
|
95
110
|
userUuid: 'user-456',
|
96
111
|
};
|
97
|
-
const
|
112
|
+
const builder = new AbilityBuilder(Ability);
|
113
|
+
buildAbilityFromScopes({
|
98
114
|
...editorContext,
|
99
115
|
scopes: ['manage:dashboard'],
|
100
|
-
});
|
116
|
+
}, builder);
|
117
|
+
const ability = builder.build();
|
101
118
|
// Can manage dashboards where user is editor
|
102
119
|
expect(ability.can('manage', subject('Dashboard', {
|
103
120
|
organizationUuid: 'org-123',
|
@@ -111,13 +128,15 @@ describe('scopeAbilityBuilder', () => {
|
|
111
128
|
});
|
112
129
|
it('should build ability with admin permissions for spaces', () => {
|
113
130
|
const adminContext = {
|
114
|
-
...
|
131
|
+
...baseContextWithOrg,
|
115
132
|
userUuid: 'user-456',
|
116
133
|
};
|
117
|
-
const
|
134
|
+
const builder = new AbilityBuilder(Ability);
|
135
|
+
buildAbilityFromScopes({
|
118
136
|
...adminContext,
|
119
137
|
scopes: ['manage:space'],
|
120
|
-
});
|
138
|
+
}, builder);
|
139
|
+
const ability = builder.build();
|
121
140
|
// Can manage spaces where user is admin
|
122
141
|
expect(ability.can('manage', subject('Space', {
|
123
142
|
organizationUuid: 'org-123',
|
@@ -134,10 +153,12 @@ describe('scopeAbilityBuilder', () => {
|
|
134
153
|
...baseContext,
|
135
154
|
userUuid: 'user-456',
|
136
155
|
};
|
137
|
-
const
|
156
|
+
const builder = new AbilityBuilder(Ability);
|
157
|
+
buildAbilityFromScopes({
|
138
158
|
...userContext,
|
139
159
|
scopes: ['view:job_status'],
|
140
|
-
});
|
160
|
+
}, builder);
|
161
|
+
const ability = builder.build();
|
141
162
|
// Can view job status created by the user
|
142
163
|
expect(ability.can('view', subject('JobStatus', {
|
143
164
|
createdByUserUuid: 'user-456',
|
@@ -153,10 +174,12 @@ describe('scopeAbilityBuilder', () => {
|
|
153
174
|
userUuid: 'user-456',
|
154
175
|
isEnterprise: true,
|
155
176
|
};
|
156
|
-
const
|
177
|
+
const builder = new AbilityBuilder(Ability);
|
178
|
+
buildAbilityFromScopes({
|
157
179
|
...userContext,
|
158
180
|
scopes: ['manage:ai_agent_thread'],
|
159
|
-
});
|
181
|
+
}, builder);
|
182
|
+
const ability = builder.build();
|
160
183
|
// Can manage user's own AI agent threads
|
161
184
|
expect(ability.can('manage', subject('AiAgentThread', {
|
162
185
|
organizationUuid: 'org-123',
|
@@ -170,10 +193,12 @@ describe('scopeAbilityBuilder', () => {
|
|
170
193
|
});
|
171
194
|
it('should build ability with basic permissions for scopes without custom logic', () => {
|
172
195
|
// These scopes don't have custom applyConditions
|
173
|
-
const
|
196
|
+
const builder = new AbilityBuilder(Ability);
|
197
|
+
buildAbilityFromScopes({
|
174
198
|
...baseContext,
|
175
199
|
scopes: ['view:analytics', 'manage:tags'],
|
176
|
-
});
|
200
|
+
}, builder);
|
201
|
+
const ability = builder.build();
|
177
202
|
expect(ability.can('view', subject('Analytics', {
|
178
203
|
organizationUuid: 'org-123',
|
179
204
|
projectUuid: 'project-123',
|
@@ -184,7 +209,9 @@ describe('scopeAbilityBuilder', () => {
|
|
184
209
|
}))).toBe(true);
|
185
210
|
});
|
186
211
|
it('should handle unknown scopes gracefully', () => {
|
187
|
-
const
|
212
|
+
const builder = new AbilityBuilder(Ability);
|
213
|
+
buildAbilityFromScopes(baseContext, builder);
|
214
|
+
const ability = builder.build();
|
188
215
|
// Unknown scope should not add any abilities
|
189
216
|
expect(ability.rules.length).toBe(0);
|
190
217
|
});
|
@@ -193,15 +220,16 @@ describe('scopeAbilityBuilder', () => {
|
|
193
220
|
...baseContext,
|
194
221
|
isEnterprise: false,
|
195
222
|
};
|
196
|
-
const
|
223
|
+
const builder = new AbilityBuilder(Ability);
|
224
|
+
buildAbilityFromScopes(nonEnterpriseContext, builder);
|
225
|
+
const ability = builder.build();
|
197
226
|
// Enterprise scope should not add abilities in non-enterprise context
|
198
227
|
expect(ability.rules.length).toBe(0);
|
199
228
|
});
|
200
229
|
it('should build a complete ability from multiple scopes', () => {
|
201
230
|
const context = {
|
202
|
-
organizationUuid: 'org-123',
|
203
231
|
userUuid: 'user-456',
|
204
|
-
|
232
|
+
organizationUuid: 'org-123',
|
205
233
|
isEnterprise: false,
|
206
234
|
organizationRole: 'admin',
|
207
235
|
scopes: [
|
@@ -210,7 +238,9 @@ describe('scopeAbilityBuilder', () => {
|
|
210
238
|
'view:project',
|
211
239
|
],
|
212
240
|
};
|
213
|
-
const
|
241
|
+
const builder = new AbilityBuilder(Ability);
|
242
|
+
buildAbilityFromScopes(context, builder);
|
243
|
+
const ability = builder.build();
|
214
244
|
// Check that all abilities were applied
|
215
245
|
expect(ability.can('view', subject('Dashboard', {
|
216
246
|
organizationUuid: 'org-123',
|
@@ -239,7 +269,9 @@ describe('scopeAbilityBuilder', () => {
|
|
239
269
|
userUuid: 'user-456',
|
240
270
|
scopes: ['manage:organization', 'manage:saved_chart'],
|
241
271
|
};
|
242
|
-
const
|
272
|
+
const builder = new AbilityBuilder(Ability);
|
273
|
+
buildAbilityFromScopes(contextWithOrgManage, builder);
|
274
|
+
const ability = builder.build();
|
243
275
|
// Should have organization-wide permissions for saved charts
|
244
276
|
expect(ability.can('manage', subject('SavedChart', {
|
245
277
|
organizationUuid: 'org-123',
|
@@ -258,7 +290,9 @@ describe('scopeAbilityBuilder', () => {
|
|
258
290
|
userUuid: 'user-456',
|
259
291
|
scopes: ['manage:saved_chart'],
|
260
292
|
};
|
261
|
-
const
|
293
|
+
const builder = new AbilityBuilder(Ability);
|
294
|
+
buildAbilityFromScopes(contextWithoutOrgManage, builder);
|
295
|
+
const ability = builder.build();
|
262
296
|
// Should require user access restrictions
|
263
297
|
expect(ability.can('manage', subject('SavedChart', {
|
264
298
|
organizationUuid: 'org-123',
|
@@ -283,7 +317,9 @@ describe('scopeAbilityBuilder', () => {
|
|
283
317
|
userUuid: 'user-456',
|
284
318
|
scopes: ['manage:project', 'manage:space'],
|
285
319
|
};
|
286
|
-
const
|
320
|
+
const builder = new AbilityBuilder(Ability);
|
321
|
+
buildAbilityFromScopes(contextWithProjectManage, builder);
|
322
|
+
const ability = builder.build();
|
287
323
|
// Should allow managing public spaces when user has project management
|
288
324
|
expect(ability.can('manage', subject('Space', {
|
289
325
|
organizationUuid: 'org-123',
|
@@ -314,13 +350,17 @@ describe('scopeAbilityBuilder', () => {
|
|
314
350
|
scopes: ['promote:dashboard'],
|
315
351
|
};
|
316
352
|
// Test dashboard promotion with organization management
|
317
|
-
const
|
353
|
+
const builder = new AbilityBuilder(Ability);
|
354
|
+
buildAbilityFromScopes(contextWithOrgManage, builder);
|
355
|
+
const abilityWithOrg = builder.build();
|
318
356
|
expect(abilityWithOrg.can('promote', subject('Dashboard', {
|
319
357
|
organizationUuid: 'org-123',
|
320
358
|
projectUuid: 'project-123',
|
321
359
|
}))).toBe(true);
|
322
360
|
// Test dashboard promotion without organization management
|
323
|
-
const
|
361
|
+
const builderWithoutOrg = new AbilityBuilder(Ability);
|
362
|
+
buildAbilityFromScopes(contextWithoutOrgManage, builderWithoutOrg);
|
363
|
+
const abilityWithoutOrg = builderWithoutOrg.build();
|
324
364
|
expect(abilityWithoutOrg.can('promote', subject('Dashboard', {
|
325
365
|
organizationUuid: 'org-123',
|
326
366
|
projectUuid: 'project-123',
|
@@ -341,18 +381,21 @@ describe('scopeAbilityBuilder', () => {
|
|
341
381
|
});
|
342
382
|
describe('edge cases and error handling', () => {
|
343
383
|
it('should handle empty scope array', () => {
|
344
|
-
const
|
384
|
+
const builder = new AbilityBuilder(Ability);
|
385
|
+
buildAbilityFromScopes(baseContext, builder);
|
386
|
+
const ability = builder.build();
|
345
387
|
expect(ability.rules.length).toBe(0);
|
346
388
|
});
|
347
389
|
it('should handle undefined userUuid in context', () => {
|
348
390
|
const contextWithoutUser = {
|
349
391
|
...baseContext,
|
350
|
-
userUuid: undefined,
|
351
392
|
};
|
352
|
-
const
|
393
|
+
const builder = new AbilityBuilder(Ability);
|
394
|
+
buildAbilityFromScopes({
|
353
395
|
...contextWithoutUser,
|
354
396
|
scopes: ['view:dashboard'],
|
355
|
-
});
|
397
|
+
}, builder);
|
398
|
+
const ability = builder.build();
|
356
399
|
// Should only allow viewing public dashboards
|
357
400
|
expect(ability.can('view', subject('Dashboard', {
|
358
401
|
organizationUuid: 'org-123',
|
@@ -365,62 +408,35 @@ describe('scopeAbilityBuilder', () => {
|
|
365
408
|
isPrivate: true,
|
366
409
|
}))).toBe(false);
|
367
410
|
});
|
368
|
-
it('should handle missing projectUuid in context', () => {
|
369
|
-
const contextWithoutProject = {
|
370
|
-
...baseContext,
|
371
|
-
projectUuid: '',
|
372
|
-
};
|
373
|
-
const ability = buildAbilityFromScopes({
|
374
|
-
...contextWithoutProject,
|
375
|
-
scopes: ['view:dashboard'],
|
376
|
-
});
|
377
|
-
// Should work with public dashboards without project restriction
|
378
|
-
expect(ability.can('view', subject('Dashboard', {
|
379
|
-
organizationUuid: 'org-123',
|
380
|
-
isPrivate: false,
|
381
|
-
}))).toBe(true);
|
382
|
-
});
|
383
411
|
it('should handle mixed valid and invalid scopes', () => {
|
384
|
-
|
385
|
-
|
386
|
-
buildAbilityFromScopes({
|
387
|
-
...baseContext,
|
388
|
-
scopes: [
|
389
|
-
'view:dashboard',
|
390
|
-
'view:project',
|
391
|
-
'invalid:scope',
|
392
|
-
],
|
393
|
-
});
|
394
|
-
}).toThrow('Invalid scope: invalid:Scope. Please check the scope name and try again.');
|
395
|
-
// Should work with only valid scopes
|
396
|
-
const ability = buildAbilityFromScopes({
|
412
|
+
const builder = new AbilityBuilder(Ability);
|
413
|
+
buildAbilityFromScopes({
|
397
414
|
...baseContext,
|
398
|
-
scopes: [
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
expect(ability.
|
407
|
-
|
408
|
-
|
409
|
-
}))).toBe(true);
|
410
|
-
// Should have rules from valid scopes
|
411
|
-
expect(ability.rules.length).toBeGreaterThan(0);
|
415
|
+
scopes: [
|
416
|
+
'view:dashboard',
|
417
|
+
'view:project',
|
418
|
+
'invalid:scope',
|
419
|
+
],
|
420
|
+
}, builder);
|
421
|
+
const ability = builder.build();
|
422
|
+
// We have 3 valid rules, 2 for dashboard and 1 for project, dropping the invalid scope
|
423
|
+
expect(ability.rules.length).toBe(3);
|
424
|
+
expect(ability.rules.filter((r) => r.subject === 'Dashboard')).toHaveLength(2);
|
425
|
+
expect(ability.rules.find((r) => r.subject === 'Project')).toBeDefined();
|
412
426
|
});
|
413
427
|
});
|
414
428
|
describe('cross-boundary access tests', () => {
|
415
429
|
it('should not allow access to resources from different organizations', () => {
|
416
|
-
const
|
430
|
+
const builder = new AbilityBuilder(Ability);
|
431
|
+
buildAbilityFromScopes({
|
417
432
|
...baseContext,
|
418
433
|
scopes: [
|
419
434
|
'view:dashboard',
|
420
435
|
'manage:saved_chart',
|
421
436
|
'view:space',
|
422
437
|
],
|
423
|
-
});
|
438
|
+
}, builder);
|
439
|
+
const ability = builder.build();
|
424
440
|
// Should not access dashboard from different org
|
425
441
|
expect(ability.can('view', subject('Dashboard', {
|
426
442
|
organizationUuid: 'different-org',
|
@@ -437,10 +453,12 @@ describe('scopeAbilityBuilder', () => {
|
|
437
453
|
}))).toBe(false);
|
438
454
|
});
|
439
455
|
it('should not allow access to resources from different projects', () => {
|
440
|
-
const
|
456
|
+
const builder = new AbilityBuilder(Ability);
|
457
|
+
buildAbilityFromScopes({
|
441
458
|
...baseContext,
|
442
459
|
scopes: ['view:saved_chart'],
|
443
|
-
});
|
460
|
+
}, builder);
|
461
|
+
const ability = builder.build();
|
444
462
|
// Should not access saved chart from different project
|
445
463
|
expect(ability.can('view', subject('SavedChart', {
|
446
464
|
organizationUuid: 'org-123',
|
@@ -452,13 +470,15 @@ describe('scopeAbilityBuilder', () => {
|
|
452
470
|
describe('private resource access with space roles', () => {
|
453
471
|
it('should handle viewer role access to private resources', () => {
|
454
472
|
const contextWithUser = {
|
455
|
-
...
|
473
|
+
...baseContextWithOrg,
|
456
474
|
userUuid: 'user-456',
|
457
475
|
};
|
458
|
-
const
|
476
|
+
const builder = new AbilityBuilder(Ability);
|
477
|
+
buildAbilityFromScopes({
|
459
478
|
...contextWithUser,
|
460
479
|
scopes: ['view:dashboard'],
|
461
|
-
});
|
480
|
+
}, builder);
|
481
|
+
const ability = builder.build();
|
462
482
|
// Can view private dashboard with viewer access
|
463
483
|
expect(ability.can('view', subject('Dashboard', {
|
464
484
|
organizationUuid: 'org-123',
|
@@ -490,13 +510,15 @@ describe('scopeAbilityBuilder', () => {
|
|
490
510
|
});
|
491
511
|
it('should handle editor role for managing resources', () => {
|
492
512
|
const contextWithUser = {
|
493
|
-
...
|
513
|
+
...baseContextWithOrg,
|
494
514
|
userUuid: 'user-456',
|
495
515
|
};
|
496
|
-
const
|
516
|
+
const builder = new AbilityBuilder(Ability);
|
517
|
+
buildAbilityFromScopes({
|
497
518
|
...contextWithUser,
|
498
519
|
scopes: ['manage:dashboard'],
|
499
|
-
});
|
520
|
+
}, builder);
|
521
|
+
const ability = builder.build();
|
500
522
|
// Can manage dashboard with editor role
|
501
523
|
expect(ability.can('manage', subject('Dashboard', {
|
502
524
|
organizationUuid: 'org-123',
|
@@ -538,13 +560,15 @@ describe('scopeAbilityBuilder', () => {
|
|
538
560
|
...baseContext,
|
539
561
|
userUuid: 'user-456',
|
540
562
|
};
|
541
|
-
const
|
563
|
+
const builder = new AbilityBuilder(Ability);
|
564
|
+
buildAbilityFromScopes({
|
542
565
|
...contextWithUser,
|
543
|
-
scopes: ['manage:space'],
|
544
|
-
});
|
566
|
+
scopes: ['manage:space@assigned'],
|
567
|
+
}, builder);
|
568
|
+
const ability = builder.build();
|
545
569
|
// Can manage space with admin role
|
546
570
|
expect(ability.can('manage', subject('Space', {
|
547
|
-
|
571
|
+
projectUuid: baseContext.projectUuid,
|
548
572
|
access: [
|
549
573
|
{
|
550
574
|
userUuid: 'user-456',
|
@@ -582,10 +606,12 @@ describe('scopeAbilityBuilder', () => {
|
|
582
606
|
...baseContext,
|
583
607
|
userUuid: 'user-456',
|
584
608
|
};
|
585
|
-
const
|
609
|
+
const builder = new AbilityBuilder(Ability);
|
610
|
+
buildAbilityFromScopes({
|
586
611
|
...contextWithUser,
|
587
612
|
scopes: ['view:job'],
|
588
|
-
});
|
613
|
+
}, builder);
|
614
|
+
const ability = builder.build();
|
589
615
|
// Can view own jobs
|
590
616
|
expect(ability.can('view', subject('Job', {
|
591
617
|
userUuid: 'user-456',
|
@@ -596,14 +622,12 @@ describe('scopeAbilityBuilder', () => {
|
|
596
622
|
}))).toBe(false);
|
597
623
|
});
|
598
624
|
it('should handle view:job_status permissions for organization context', () => {
|
599
|
-
const
|
625
|
+
const builder = new AbilityBuilder(Ability);
|
626
|
+
buildAbilityFromScopes({
|
600
627
|
...baseContext,
|
601
|
-
userUuid: undefined,
|
602
|
-
};
|
603
|
-
const ability = buildAbilityFromScopes({
|
604
|
-
...contextWithoutUser,
|
605
628
|
scopes: ['view:job_status'],
|
606
|
-
});
|
629
|
+
}, builder);
|
630
|
+
const ability = builder.build();
|
607
631
|
// Cannot view job status without user context when no manage:Organization scope
|
608
632
|
expect(ability.can('view', subject('JobStatus', {
|
609
633
|
organizationUuid: 'org-123',
|
@@ -614,14 +638,12 @@ describe('scopeAbilityBuilder', () => {
|
|
614
638
|
}))).toBe(false);
|
615
639
|
});
|
616
640
|
it('should handle view:job_status permissions with manage:Organization scope', () => {
|
617
|
-
const
|
618
|
-
|
619
|
-
|
620
|
-
};
|
621
|
-
const ability = buildAbilityFromScopes({
|
622
|
-
...contextWithoutUser,
|
641
|
+
const builder = new AbilityBuilder(Ability);
|
642
|
+
buildAbilityFromScopes({
|
643
|
+
...baseContextWithOrg,
|
623
644
|
scopes: ['view:job_status', 'manage:organization'],
|
624
|
-
});
|
645
|
+
}, builder);
|
646
|
+
const ability = builder.build();
|
625
647
|
// Can view all job status in organization when manage:Organization scope is present
|
626
648
|
expect(ability.can('view', subject('JobStatus', {
|
627
649
|
organizationUuid: 'org-123',
|
@@ -636,10 +658,12 @@ describe('scopeAbilityBuilder', () => {
|
|
636
658
|
...baseContext,
|
637
659
|
userUuid: 'user-456',
|
638
660
|
};
|
639
|
-
const
|
661
|
+
const builder = new AbilityBuilder(Ability);
|
662
|
+
buildAbilityFromScopes({
|
640
663
|
...contextWithUser,
|
641
664
|
scopes: ['view:job_status'],
|
642
|
-
});
|
665
|
+
}, builder);
|
666
|
+
const ability = builder.build();
|
643
667
|
// Can view own job status
|
644
668
|
expect(ability.can('view', subject('JobStatus', {
|
645
669
|
createdByUserUuid: 'user-456',
|
@@ -652,10 +676,12 @@ describe('scopeAbilityBuilder', () => {
|
|
652
676
|
});
|
653
677
|
describe('semantic viewer permissions', () => {
|
654
678
|
it('should handle view:semantic_viewer permissions', () => {
|
655
|
-
const
|
679
|
+
const builder = new AbilityBuilder(Ability);
|
680
|
+
buildAbilityFromScopes({
|
656
681
|
...baseContext,
|
657
682
|
scopes: ['view:semantic_viewer'],
|
658
|
-
});
|
683
|
+
}, builder);
|
684
|
+
const ability = builder.build();
|
659
685
|
expect(ability.can('view', subject('SemanticViewer', {
|
660
686
|
organizationUuid: 'org-123',
|
661
687
|
projectUuid: 'project-123',
|
@@ -663,14 +689,19 @@ describe('scopeAbilityBuilder', () => {
|
|
663
689
|
});
|
664
690
|
it('should handle manage:semantic_viewer with organization scope', () => {
|
665
691
|
const contextWithOrgManage = {
|
666
|
-
...
|
692
|
+
...baseContextWithOrg,
|
667
693
|
userUuid: 'user-456',
|
668
694
|
scopes: ['manage:organization'],
|
669
695
|
};
|
670
|
-
const
|
696
|
+
const builder = new AbilityBuilder(Ability);
|
697
|
+
buildAbilityFromScopes({
|
671
698
|
...contextWithOrgManage,
|
672
|
-
scopes: [
|
673
|
-
|
699
|
+
scopes: [
|
700
|
+
'manage:organization',
|
701
|
+
'manage:semantic_viewer',
|
702
|
+
],
|
703
|
+
}, builder);
|
704
|
+
const ability = builder.build();
|
674
705
|
// Can manage semantic viewer organization-wide
|
675
706
|
expect(ability.can('manage', subject('SemanticViewer', {
|
676
707
|
organizationUuid: 'org-123',
|
@@ -678,13 +709,15 @@ describe('scopeAbilityBuilder', () => {
|
|
678
709
|
});
|
679
710
|
it('should handle manage:semantic_viewer with editor role', () => {
|
680
711
|
const contextWithUser = {
|
681
|
-
...
|
712
|
+
...baseContextWithOrg,
|
682
713
|
userUuid: 'user-456',
|
683
714
|
};
|
684
|
-
const
|
715
|
+
const builder = new AbilityBuilder(Ability);
|
716
|
+
buildAbilityFromScopes({
|
685
717
|
...contextWithUser,
|
686
718
|
scopes: ['manage:semantic_viewer'],
|
687
|
-
});
|
719
|
+
}, builder);
|
720
|
+
const ability = builder.build();
|
688
721
|
// Can manage semantic viewer with editor role
|
689
722
|
expect(ability.can('manage', subject('SemanticViewer', {
|
690
723
|
organizationUuid: 'org-123',
|
@@ -709,10 +742,12 @@ describe('scopeAbilityBuilder', () => {
|
|
709
742
|
});
|
710
743
|
describe('create space permissions', () => {
|
711
744
|
it('should handle create:space permissions', () => {
|
712
|
-
const
|
745
|
+
const builder = new AbilityBuilder(Ability);
|
746
|
+
buildAbilityFromScopes({
|
713
747
|
...baseContext,
|
714
748
|
scopes: ['create:space'],
|
715
|
-
});
|
749
|
+
}, builder);
|
750
|
+
const ability = builder.build();
|
716
751
|
expect(ability.can('create', subject('Space', {
|
717
752
|
organizationUuid: 'org-123',
|
718
753
|
projectUuid: 'project-123',
|
@@ -721,20 +756,24 @@ describe('scopeAbilityBuilder', () => {
|
|
721
756
|
});
|
722
757
|
describe('export permissions', () => {
|
723
758
|
it('should handle export csv permissions', () => {
|
724
|
-
const
|
759
|
+
const builder = new AbilityBuilder(Ability);
|
760
|
+
buildAbilityFromScopes({
|
725
761
|
...baseContext,
|
726
762
|
scopes: ['manage:export_csv'],
|
727
|
-
});
|
763
|
+
}, builder);
|
764
|
+
const ability = builder.build();
|
728
765
|
expect(ability.can('manage', subject('ExportCsv', {
|
729
766
|
organizationUuid: 'org-123',
|
730
767
|
projectUuid: 'project-123',
|
731
768
|
}))).toBe(true);
|
732
769
|
});
|
733
770
|
it('should handle change csv results permissions', () => {
|
734
|
-
const
|
771
|
+
const builder = new AbilityBuilder(Ability);
|
772
|
+
buildAbilityFromScopes({
|
735
773
|
...baseContext,
|
736
774
|
scopes: ['manage:change_csv_results'],
|
737
|
-
});
|
775
|
+
}, builder);
|
776
|
+
const ability = builder.build();
|
738
777
|
expect(ability.can('manage', subject('ChangeCsvResults', {
|
739
778
|
organizationUuid: 'org-123',
|
740
779
|
projectUuid: 'project-123',
|
@@ -743,10 +782,12 @@ describe('scopeAbilityBuilder', () => {
|
|
743
782
|
});
|
744
783
|
describe('underlying data permissions', () => {
|
745
784
|
it('should handle view:underlying_data permissions', () => {
|
746
|
-
const
|
785
|
+
const builder = new AbilityBuilder(Ability);
|
786
|
+
buildAbilityFromScopes({
|
747
787
|
...baseContext,
|
748
788
|
scopes: ['view:underlying_data'],
|
749
|
-
});
|
789
|
+
}, builder);
|
790
|
+
const ability = builder.build();
|
750
791
|
expect(ability.can('view', subject('UnderlyingData', {
|
751
792
|
organizationUuid: 'org-123',
|
752
793
|
projectUuid: 'project-123',
|
@@ -755,20 +796,24 @@ describe('scopeAbilityBuilder', () => {
|
|
755
796
|
});
|
756
797
|
describe('sql runner and custom sql permissions', () => {
|
757
798
|
it('should handle manage:sql_runner permissions', () => {
|
758
|
-
const
|
799
|
+
const builder = new AbilityBuilder(Ability);
|
800
|
+
buildAbilityFromScopes({
|
759
801
|
...baseContext,
|
760
802
|
scopes: ['manage:sql_runner'],
|
761
|
-
});
|
803
|
+
}, builder);
|
804
|
+
const ability = builder.build();
|
762
805
|
expect(ability.can('manage', subject('SqlRunner', {
|
763
806
|
organizationUuid: 'org-123',
|
764
807
|
projectUuid: 'project-123',
|
765
808
|
}))).toBe(true);
|
766
809
|
});
|
767
810
|
it('should handle manage:custom_sql permissions', () => {
|
768
|
-
const
|
811
|
+
const builder = new AbilityBuilder(Ability);
|
812
|
+
buildAbilityFromScopes({
|
769
813
|
...baseContext,
|
770
814
|
scopes: ['manage:custom_sql'],
|
771
|
-
});
|
815
|
+
}, builder);
|
816
|
+
const ability = builder.build();
|
772
817
|
expect(ability.can('manage', subject('CustomSql', {
|
773
818
|
organizationUuid: 'org-123',
|
774
819
|
projectUuid: 'project-123',
|
@@ -776,33 +821,31 @@ describe('scopeAbilityBuilder', () => {
|
|
776
821
|
});
|
777
822
|
});
|
778
823
|
describe('project delete permissions', () => {
|
779
|
-
it('should handle delete:project
|
780
|
-
const
|
824
|
+
it('should handle delete:project@self permissions', () => {
|
825
|
+
const builder = new AbilityBuilder(Ability);
|
826
|
+
buildAbilityFromScopes({
|
781
827
|
...baseContext,
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
scopes: ['delete:project'],
|
787
|
-
});
|
828
|
+
userUuid: 'user-456',
|
829
|
+
scopes: ['delete:project@self'],
|
830
|
+
}, builder);
|
831
|
+
const ability = builder.build();
|
788
832
|
// Can delete specific project
|
789
833
|
expect(ability.can('delete', subject('Project', {
|
790
|
-
|
834
|
+
createdByUserUuid: 'user-456',
|
835
|
+
type: ProjectType.PREVIEW,
|
791
836
|
}))).toBe(true);
|
792
|
-
// Cannot delete different project
|
793
837
|
expect(ability.can('delete', subject('Project', {
|
794
|
-
|
838
|
+
createdByUserUuid: 'different-user',
|
839
|
+
type: ProjectType.PREVIEW,
|
795
840
|
}))).toBe(false);
|
796
841
|
});
|
797
842
|
it('should handle delete:project for preview projects', () => {
|
798
|
-
const
|
799
|
-
|
800
|
-
|
801
|
-
};
|
802
|
-
const ability = buildAbilityFromScopes({
|
803
|
-
...contextWithoutProject,
|
843
|
+
const builder = new AbilityBuilder(Ability);
|
844
|
+
buildAbilityFromScopes({
|
845
|
+
...baseContextWithOrg,
|
804
846
|
scopes: ['delete:project'],
|
805
|
-
});
|
847
|
+
}, builder);
|
848
|
+
const ability = builder.build();
|
806
849
|
// Can delete preview projects in organization
|
807
850
|
expect(ability.can('delete', subject('Project', {
|
808
851
|
organizationUuid: 'org-123',
|
@@ -817,20 +860,24 @@ describe('scopeAbilityBuilder', () => {
|
|
817
860
|
});
|
818
861
|
describe('pinned items permissions', () => {
|
819
862
|
it('should handle view:pinned_items permissions', () => {
|
820
|
-
const
|
863
|
+
const builder = new AbilityBuilder(Ability);
|
864
|
+
buildAbilityFromScopes({
|
821
865
|
...baseContext,
|
822
866
|
scopes: ['view:pinned_items'],
|
823
|
-
});
|
867
|
+
}, builder);
|
868
|
+
const ability = builder.build();
|
824
869
|
expect(ability.can('view', subject('PinnedItems', {
|
825
870
|
organizationUuid: 'org-123',
|
826
871
|
projectUuid: 'project-123',
|
827
872
|
}))).toBe(true);
|
828
873
|
});
|
829
874
|
it('should handle manage:pinned_items permissions', () => {
|
830
|
-
const
|
875
|
+
const builder = new AbilityBuilder(Ability);
|
876
|
+
buildAbilityFromScopes({
|
831
877
|
...baseContext,
|
832
878
|
scopes: ['manage:pinned_items'],
|
833
|
-
});
|
879
|
+
}, builder);
|
880
|
+
const ability = builder.build();
|
834
881
|
expect(ability.can('manage', subject('PinnedItems', {
|
835
882
|
organizationUuid: 'org-123',
|
836
883
|
projectUuid: 'project-123',
|
@@ -839,10 +886,12 @@ describe('scopeAbilityBuilder', () => {
|
|
839
886
|
});
|
840
887
|
describe('explore permissions', () => {
|
841
888
|
it('should handle manage:explore permissions', () => {
|
842
|
-
const
|
889
|
+
const builder = new AbilityBuilder(Ability);
|
890
|
+
buildAbilityFromScopes({
|
843
891
|
...baseContext,
|
844
892
|
scopes: ['manage:explore'],
|
845
|
-
});
|
893
|
+
}, builder);
|
894
|
+
const ability = builder.build();
|
846
895
|
expect(ability.can('manage', subject('Explore', {
|
847
896
|
organizationUuid: 'org-123',
|
848
897
|
projectUuid: 'project-123',
|
@@ -851,10 +900,12 @@ describe('scopeAbilityBuilder', () => {
|
|
851
900
|
});
|
852
901
|
describe('virtual view permissions', () => {
|
853
902
|
it('should handle create:virtual_view permissions', () => {
|
854
|
-
const
|
903
|
+
const builder = new AbilityBuilder(Ability);
|
904
|
+
buildAbilityFromScopes({
|
855
905
|
...baseContext,
|
856
906
|
scopes: ['create:virtual_view'],
|
857
|
-
});
|
907
|
+
}, builder);
|
908
|
+
const ability = builder.build();
|
858
909
|
expect(ability.can('create', subject('VirtualView', {
|
859
910
|
organizationUuid: 'org-123',
|
860
911
|
projectUuid: 'project-123',
|
@@ -866,10 +917,12 @@ describe('scopeAbilityBuilder', () => {
|
|
866
917
|
}))).toBe(false);
|
867
918
|
});
|
868
919
|
it('should handle delete:virtual_view permissions', () => {
|
869
|
-
const
|
920
|
+
const builder = new AbilityBuilder(Ability);
|
921
|
+
buildAbilityFromScopes({
|
870
922
|
...baseContext,
|
871
923
|
scopes: ['delete:virtual_view'],
|
872
|
-
});
|
924
|
+
}, builder);
|
925
|
+
const ability = builder.build();
|
873
926
|
expect(ability.can('delete', subject('VirtualView', {
|
874
927
|
organizationUuid: 'org-123',
|
875
928
|
projectUuid: 'project-123',
|
@@ -881,10 +934,12 @@ describe('scopeAbilityBuilder', () => {
|
|
881
934
|
}))).toBe(false);
|
882
935
|
});
|
883
936
|
it('should handle manage:virtual_view permissions for both create and delete', () => {
|
884
|
-
const
|
937
|
+
const builder = new AbilityBuilder(Ability);
|
938
|
+
buildAbilityFromScopes({
|
885
939
|
...baseContext,
|
886
940
|
scopes: ['manage:virtual_view'],
|
887
|
-
});
|
941
|
+
}, builder);
|
942
|
+
const ability = builder.build();
|
888
943
|
// Should be able to manage (create and delete)
|
889
944
|
expect(ability.can('manage', subject('VirtualView', {
|
890
945
|
organizationUuid: 'org-123',
|
@@ -901,14 +956,16 @@ describe('scopeAbilityBuilder', () => {
|
|
901
956
|
}))).toBe(true);
|
902
957
|
});
|
903
958
|
it('should not allow virtual view actions for different organizations', () => {
|
904
|
-
const
|
905
|
-
|
959
|
+
const builder = new AbilityBuilder(Ability);
|
960
|
+
buildAbilityFromScopes({
|
961
|
+
...baseContextWithOrg,
|
906
962
|
scopes: [
|
907
963
|
'create:virtual_view',
|
908
964
|
'delete:virtual_view',
|
909
965
|
'manage:virtual_view',
|
910
966
|
],
|
911
|
-
});
|
967
|
+
}, builder);
|
968
|
+
const ability = builder.build();
|
912
969
|
// Should not access virtual views from different org
|
913
970
|
expect(ability.can('create', subject('VirtualView', {
|
914
971
|
organizationUuid: 'different-org',
|
@@ -924,14 +981,16 @@ describe('scopeAbilityBuilder', () => {
|
|
924
981
|
}))).toBe(false);
|
925
982
|
});
|
926
983
|
it('should allow virtual view actions for different projects within same organization', () => {
|
927
|
-
const
|
928
|
-
|
984
|
+
const builder = new AbilityBuilder(Ability);
|
985
|
+
buildAbilityFromScopes({
|
986
|
+
...baseContextWithOrg,
|
929
987
|
scopes: [
|
930
988
|
'create:virtual_view',
|
931
989
|
'delete:virtual_view',
|
932
990
|
'manage:virtual_view',
|
933
991
|
],
|
934
|
-
});
|
992
|
+
}, builder);
|
993
|
+
const ability = builder.build();
|
935
994
|
// Virtual view permissions are organization-scoped, not project-scoped
|
936
995
|
// So they should work across different projects within the same org
|
937
996
|
expect(ability.can('create', subject('VirtualView', {
|
@@ -950,25 +1009,29 @@ describe('scopeAbilityBuilder', () => {
|
|
950
1009
|
});
|
951
1010
|
describe('organization member profile permissions', () => {
|
952
1011
|
it('should handle view:organization_member_profile permissions', () => {
|
953
|
-
const
|
954
|
-
|
1012
|
+
const builder = new AbilityBuilder(Ability);
|
1013
|
+
buildAbilityFromScopes({
|
1014
|
+
...baseContextWithOrg,
|
955
1015
|
scopes: ['view:organization_member_profile'],
|
956
|
-
});
|
1016
|
+
}, builder);
|
1017
|
+
const ability = builder.build();
|
957
1018
|
expect(ability.can('view', subject('OrganizationMemberProfile', {
|
958
1019
|
organizationUuid: 'org-123',
|
959
1020
|
projectUuid: 'project-123',
|
960
1021
|
}))).toBe(true);
|
961
|
-
// Cannot view profiles from different
|
1022
|
+
// Cannot view profiles from different project
|
962
1023
|
expect(ability.can('view', subject('OrganizationMemberProfile', {
|
963
1024
|
organizationUuid: 'different-org',
|
964
1025
|
projectUuid: 'project-123',
|
965
1026
|
}))).toBe(false);
|
966
1027
|
});
|
967
1028
|
it('should handle manage:organization_member_profile permissions', () => {
|
968
|
-
const
|
1029
|
+
const builder = new AbilityBuilder(Ability);
|
1030
|
+
buildAbilityFromScopes({
|
969
1031
|
...baseContext,
|
970
1032
|
scopes: ['manage:organization_member_profile'],
|
971
|
-
});
|
1033
|
+
}, builder);
|
1034
|
+
const ability = builder.build();
|
972
1035
|
expect(ability.can('manage', subject('OrganizationMemberProfile', {
|
973
1036
|
organizationUuid: 'org-123',
|
974
1037
|
projectUuid: 'project-123',
|
@@ -977,7 +1040,8 @@ describe('scopeAbilityBuilder', () => {
|
|
977
1040
|
});
|
978
1041
|
describe('personal access token permissions', () => {
|
979
1042
|
it('should allow managing PAT when enabled and user has allowed role', () => {
|
980
|
-
const
|
1043
|
+
const builder = new AbilityBuilder(Ability);
|
1044
|
+
buildAbilityFromScopes({
|
981
1045
|
...baseContext,
|
982
1046
|
isEnterprise: true,
|
983
1047
|
organizationRole: 'admin',
|
@@ -988,11 +1052,13 @@ describe('scopeAbilityBuilder', () => {
|
|
988
1052
|
allowedOrgRoles: ['admin', 'developer'],
|
989
1053
|
},
|
990
1054
|
},
|
991
|
-
});
|
1055
|
+
}, builder);
|
1056
|
+
const ability = builder.build();
|
992
1057
|
expect(ability.can('manage', 'PersonalAccessToken')).toBe(true);
|
993
1058
|
});
|
994
1059
|
it('should not allow managing PAT when disabled', () => {
|
995
|
-
const
|
1060
|
+
const builder = new AbilityBuilder(Ability);
|
1061
|
+
buildAbilityFromScopes({
|
996
1062
|
...baseContext,
|
997
1063
|
isEnterprise: true,
|
998
1064
|
organizationRole: 'admin',
|
@@ -1003,11 +1069,13 @@ describe('scopeAbilityBuilder', () => {
|
|
1003
1069
|
allowedOrgRoles: ['admin', 'developer'],
|
1004
1070
|
},
|
1005
1071
|
},
|
1006
|
-
});
|
1072
|
+
}, builder);
|
1073
|
+
const ability = builder.build();
|
1007
1074
|
expect(ability.can('manage', subject('PersonalAccessToken', {}))).toBe(false);
|
1008
1075
|
});
|
1009
1076
|
it('should not allow managing PAT when user role not in allowed roles', () => {
|
1010
|
-
const
|
1077
|
+
const builder = new AbilityBuilder(Ability);
|
1078
|
+
buildAbilityFromScopes({
|
1011
1079
|
...baseContext,
|
1012
1080
|
isEnterprise: true,
|
1013
1081
|
organizationRole: 'developer',
|
@@ -1018,21 +1086,25 @@ describe('scopeAbilityBuilder', () => {
|
|
1018
1086
|
allowedOrgRoles: ['admin'],
|
1019
1087
|
},
|
1020
1088
|
},
|
1021
|
-
});
|
1089
|
+
}, builder);
|
1090
|
+
const ability = builder.build();
|
1022
1091
|
expect(ability.can('manage', subject('PersonalAccessToken', {}))).toBe(false);
|
1023
1092
|
});
|
1024
1093
|
it('should not allow managing PAT when no permissions config provided', () => {
|
1025
|
-
const
|
1094
|
+
const builder = new AbilityBuilder(Ability);
|
1095
|
+
buildAbilityFromScopes({
|
1026
1096
|
...baseContext,
|
1027
1097
|
isEnterprise: true,
|
1028
1098
|
organizationRole: 'admin',
|
1029
1099
|
scopes: ['manage:personal_access_token'],
|
1030
1100
|
// No permissionsConfig provided
|
1031
|
-
});
|
1101
|
+
}, builder);
|
1102
|
+
const ability = builder.build();
|
1032
1103
|
expect(ability.can('manage', subject('PersonalAccessToken', {}))).toBe(false);
|
1033
1104
|
});
|
1034
1105
|
it('should not allow managing PAT when no organization role provided', () => {
|
1035
|
-
const
|
1106
|
+
const builder = new AbilityBuilder(Ability);
|
1107
|
+
buildAbilityFromScopes({
|
1036
1108
|
...baseContext,
|
1037
1109
|
isEnterprise: true,
|
1038
1110
|
organizationRole: '', // Empty organization role
|
@@ -1043,7 +1115,8 @@ describe('scopeAbilityBuilder', () => {
|
|
1043
1115
|
allowedOrgRoles: ['admin', 'developer'],
|
1044
1116
|
},
|
1045
1117
|
},
|
1046
|
-
});
|
1118
|
+
}, builder);
|
1119
|
+
const ability = builder.build();
|
1047
1120
|
expect(ability.can('manage', subject('PersonalAccessToken', {}))).toBe(false);
|
1048
1121
|
});
|
1049
1122
|
});
|