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