@lightdash/common 0.1946.0 → 0.1948.0
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/projectMemberAbility.d.ts.map +1 -1
- package/dist/cjs/authorization/projectMemberAbility.js +0 -4
- package/dist/cjs/authorization/projectMemberAbility.js.map +1 -1
- package/dist/cjs/authorization/roleToScopeMapping.d.ts.map +1 -1
- package/dist/cjs/authorization/roleToScopeMapping.js +11 -10
- package/dist/cjs/authorization/roleToScopeMapping.js.map +1 -1
- package/dist/cjs/authorization/roleToScopeParity.test.d.ts +2 -0
- package/dist/cjs/authorization/roleToScopeParity.test.d.ts.map +1 -0
- package/dist/cjs/authorization/roleToScopeParity.test.js +195 -0
- package/dist/cjs/authorization/roleToScopeParity.test.js.map +1 -0
- package/dist/cjs/authorization/scopeAbilityBuilder.d.ts.map +1 -1
- package/dist/cjs/authorization/scopeAbilityBuilder.js +10 -7
- package/dist/cjs/authorization/scopeAbilityBuilder.js.map +1 -1
- package/dist/cjs/authorization/scopeAbilityBuilder.test.js +312 -89
- 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 +102 -90
- package/dist/cjs/authorization/scopes.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/scopes.d.ts +2 -2
- package/dist/cjs/types/scopes.d.ts.map +1 -1
- package/dist/esm/authorization/projectMemberAbility.d.ts.map +1 -1
- package/dist/esm/authorization/projectMemberAbility.js +0 -4
- package/dist/esm/authorization/projectMemberAbility.js.map +1 -1
- package/dist/esm/authorization/roleToScopeMapping.d.ts.map +1 -1
- package/dist/esm/authorization/roleToScopeMapping.js +11 -10
- package/dist/esm/authorization/roleToScopeMapping.js.map +1 -1
- package/dist/esm/authorization/roleToScopeParity.test.d.ts +2 -0
- package/dist/esm/authorization/roleToScopeParity.test.d.ts.map +1 -0
- package/dist/esm/authorization/roleToScopeParity.test.js +193 -0
- package/dist/esm/authorization/roleToScopeParity.test.js.map +1 -0
- package/dist/esm/authorization/scopeAbilityBuilder.d.ts.map +1 -1
- package/dist/esm/authorization/scopeAbilityBuilder.js +10 -7
- package/dist/esm/authorization/scopeAbilityBuilder.js.map +1 -1
- package/dist/esm/authorization/scopeAbilityBuilder.test.js +312 -89
- 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 +102 -90
- package/dist/esm/authorization/scopes.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/scopes.d.ts +2 -2
- package/dist/esm/types/scopes.d.ts.map +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/authorization/projectMemberAbility.d.ts.map +1 -1
- package/dist/types/authorization/roleToScopeMapping.d.ts.map +1 -1
- package/dist/types/authorization/roleToScopeParity.test.d.ts +2 -0
- package/dist/types/authorization/roleToScopeParity.test.d.ts.map +1 -0
- package/dist/types/authorization/scopeAbilityBuilder.d.ts.map +1 -1
- package/dist/types/authorization/scopes.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types/scopes.d.ts +2 -2
- package/dist/types/types/scopes.d.ts.map +1 -1
- package/package.json +1 -1
@@ -20,7 +20,7 @@ describe('scopeAbilityBuilder', () => {
|
|
20
20
|
const builder = new AbilityBuilder(Ability);
|
21
21
|
buildAbilityFromScopes({
|
22
22
|
...baseContextWithOrg,
|
23
|
-
scopes: ['view:
|
23
|
+
scopes: ['view:Organization'],
|
24
24
|
}, builder);
|
25
25
|
const ability = builder.build();
|
26
26
|
expect(ability.can('view', subject('Organization', {
|
@@ -36,7 +36,7 @@ describe('scopeAbilityBuilder', () => {
|
|
36
36
|
const builder = new AbilityBuilder(Ability);
|
37
37
|
buildAbilityFromScopes({
|
38
38
|
...baseContext,
|
39
|
-
scopes: ['view:
|
39
|
+
scopes: ['view:Dashboard'],
|
40
40
|
}, builder);
|
41
41
|
const ability = builder.build();
|
42
42
|
// Should be able to view public dashboards
|
@@ -60,7 +60,7 @@ describe('scopeAbilityBuilder', () => {
|
|
60
60
|
const builder = new AbilityBuilder(Ability);
|
61
61
|
buildAbilityFromScopes({
|
62
62
|
...contextWithUser,
|
63
|
-
scopes: ['view:
|
63
|
+
scopes: ['view:Dashboard'],
|
64
64
|
}, builder);
|
65
65
|
const ability = builder.build();
|
66
66
|
// Can view dashboards with user access
|
@@ -78,7 +78,7 @@ describe('scopeAbilityBuilder', () => {
|
|
78
78
|
const builder = new AbilityBuilder(Ability);
|
79
79
|
buildAbilityFromScopes({
|
80
80
|
...projectContext,
|
81
|
-
scopes: ['view:
|
81
|
+
scopes: ['view:Project'],
|
82
82
|
}, builder);
|
83
83
|
const ability = builder.build();
|
84
84
|
expect(ability.can('view', subject('Project', {
|
@@ -90,7 +90,7 @@ describe('scopeAbilityBuilder', () => {
|
|
90
90
|
const builder = new AbilityBuilder(Ability);
|
91
91
|
buildAbilityFromScopes({
|
92
92
|
...baseContextWithOrg,
|
93
|
-
scopes: ['create:
|
93
|
+
scopes: ['create:Project'],
|
94
94
|
}, builder);
|
95
95
|
const ability = builder.build();
|
96
96
|
// Can create preview projects
|
@@ -112,7 +112,7 @@ describe('scopeAbilityBuilder', () => {
|
|
112
112
|
const builder = new AbilityBuilder(Ability);
|
113
113
|
buildAbilityFromScopes({
|
114
114
|
...editorContext,
|
115
|
-
scopes: ['manage:
|
115
|
+
scopes: ['manage:Dashboard'],
|
116
116
|
}, builder);
|
117
117
|
const ability = builder.build();
|
118
118
|
// Can manage dashboards where user is editor
|
@@ -134,7 +134,7 @@ describe('scopeAbilityBuilder', () => {
|
|
134
134
|
const builder = new AbilityBuilder(Ability);
|
135
135
|
buildAbilityFromScopes({
|
136
136
|
...adminContext,
|
137
|
-
scopes: ['manage:
|
137
|
+
scopes: ['manage:Space'],
|
138
138
|
}, builder);
|
139
139
|
const ability = builder.build();
|
140
140
|
// Can manage spaces where user is admin
|
@@ -156,7 +156,7 @@ describe('scopeAbilityBuilder', () => {
|
|
156
156
|
const builder = new AbilityBuilder(Ability);
|
157
157
|
buildAbilityFromScopes({
|
158
158
|
...userContext,
|
159
|
-
scopes: ['view:
|
159
|
+
scopes: ['view:JobStatus@self'],
|
160
160
|
}, builder);
|
161
161
|
const ability = builder.build();
|
162
162
|
// Can view job status created by the user
|
@@ -177,17 +177,17 @@ describe('scopeAbilityBuilder', () => {
|
|
177
177
|
const builder = new AbilityBuilder(Ability);
|
178
178
|
buildAbilityFromScopes({
|
179
179
|
...userContext,
|
180
|
-
scopes: ['manage:
|
180
|
+
scopes: ['manage:AiAgentThread@self'],
|
181
181
|
}, builder);
|
182
182
|
const ability = builder.build();
|
183
183
|
// Can manage user's own AI agent threads
|
184
184
|
expect(ability.can('manage', subject('AiAgentThread', {
|
185
|
-
|
185
|
+
projectUuid: 'project-123',
|
186
186
|
userUuid: 'user-456',
|
187
187
|
}))).toBe(true);
|
188
188
|
// Cannot manage another user's threads
|
189
189
|
expect(ability.can('manage', subject('AiAgentThread', {
|
190
|
-
|
190
|
+
projectUuid: 'project-123',
|
191
191
|
userUuid: 'other-user',
|
192
192
|
}))).toBe(false);
|
193
193
|
});
|
@@ -196,7 +196,7 @@ describe('scopeAbilityBuilder', () => {
|
|
196
196
|
const builder = new AbilityBuilder(Ability);
|
197
197
|
buildAbilityFromScopes({
|
198
198
|
...baseContext,
|
199
|
-
scopes: ['view:
|
199
|
+
scopes: ['view:Analytics', 'manage:Tags'],
|
200
200
|
}, builder);
|
201
201
|
const ability = builder.build();
|
202
202
|
expect(ability.can('view', subject('Analytics', {
|
@@ -232,11 +232,7 @@ describe('scopeAbilityBuilder', () => {
|
|
232
232
|
organizationUuid: 'org-123',
|
233
233
|
isEnterprise: false,
|
234
234
|
organizationRole: 'admin',
|
235
|
-
scopes: [
|
236
|
-
'view:dashboard',
|
237
|
-
'manage:saved_chart',
|
238
|
-
'view:project',
|
239
|
-
],
|
235
|
+
scopes: ['view:Dashboard', 'manage:SavedChart', 'view:Project'],
|
240
236
|
};
|
241
237
|
const builder = new AbilityBuilder(Ability);
|
242
238
|
buildAbilityFromScopes(context, builder);
|
@@ -267,7 +263,7 @@ describe('scopeAbilityBuilder', () => {
|
|
267
263
|
const contextWithOrgManage = {
|
268
264
|
...baseContext,
|
269
265
|
userUuid: 'user-456',
|
270
|
-
scopes: ['manage:
|
266
|
+
scopes: ['manage:Organization', 'manage:SavedChart'],
|
271
267
|
};
|
272
268
|
const builder = new AbilityBuilder(Ability);
|
273
269
|
buildAbilityFromScopes(contextWithOrgManage, builder);
|
@@ -288,7 +284,7 @@ describe('scopeAbilityBuilder', () => {
|
|
288
284
|
const contextWithoutOrgManage = {
|
289
285
|
...baseContext,
|
290
286
|
userUuid: 'user-456',
|
291
|
-
scopes: ['manage:
|
287
|
+
scopes: ['manage:SavedChart@space'],
|
292
288
|
};
|
293
289
|
const builder = new AbilityBuilder(Ability);
|
294
290
|
buildAbilityFromScopes(contextWithoutOrgManage, builder);
|
@@ -315,7 +311,7 @@ describe('scopeAbilityBuilder', () => {
|
|
315
311
|
const contextWithProjectManage = {
|
316
312
|
...baseContext,
|
317
313
|
userUuid: 'user-456',
|
318
|
-
scopes: ['manage:
|
314
|
+
scopes: ['manage:Project', 'manage:Space'],
|
319
315
|
};
|
320
316
|
const builder = new AbilityBuilder(Ability);
|
321
317
|
buildAbilityFromScopes(contextWithProjectManage, builder);
|
@@ -342,12 +338,12 @@ describe('scopeAbilityBuilder', () => {
|
|
342
338
|
const contextWithOrgManage = {
|
343
339
|
...baseContext,
|
344
340
|
userUuid: 'user-456',
|
345
|
-
scopes: ['manage:
|
341
|
+
scopes: ['manage:Organization', 'promote:Dashboard'],
|
346
342
|
};
|
347
343
|
const contextWithoutOrgManage = {
|
348
344
|
...baseContext,
|
349
345
|
userUuid: 'user-456',
|
350
|
-
scopes: ['promote:
|
346
|
+
scopes: ['promote:Dashboard@space'],
|
351
347
|
};
|
352
348
|
// Test dashboard promotion with organization management
|
353
349
|
const builder = new AbilityBuilder(Ability);
|
@@ -379,6 +375,82 @@ describe('scopeAbilityBuilder', () => {
|
|
379
375
|
}))).toBe(false);
|
380
376
|
});
|
381
377
|
});
|
378
|
+
describe('AI agent thread permissions with modifiers', () => {
|
379
|
+
it('should handle view:ai_agent_thread@self permissions', () => {
|
380
|
+
const contextWithUser = {
|
381
|
+
...baseContext,
|
382
|
+
userUuid: 'user-456',
|
383
|
+
isEnterprise: true,
|
384
|
+
};
|
385
|
+
const builder = new AbilityBuilder(Ability);
|
386
|
+
buildAbilityFromScopes({
|
387
|
+
...contextWithUser,
|
388
|
+
scopes: ['view:AiAgentThread@self'],
|
389
|
+
}, builder);
|
390
|
+
const ability = builder.build();
|
391
|
+
// Can view own AI agent threads
|
392
|
+
expect(ability.can('view', subject('AiAgentThread', {
|
393
|
+
projectUuid: 'project-123',
|
394
|
+
userUuid: 'user-456',
|
395
|
+
}))).toBe(true);
|
396
|
+
// Cannot view other users' threads
|
397
|
+
expect(ability.can('view', subject('AiAgentThread', {
|
398
|
+
projectUuid: 'project-123',
|
399
|
+
userUuid: 'other-user',
|
400
|
+
}))).toBe(false);
|
401
|
+
});
|
402
|
+
it('should handle manage:ai_agent_thread@self permissions', () => {
|
403
|
+
const contextWithUser = {
|
404
|
+
...baseContext,
|
405
|
+
userUuid: 'user-456',
|
406
|
+
isEnterprise: true,
|
407
|
+
};
|
408
|
+
const builder = new AbilityBuilder(Ability);
|
409
|
+
buildAbilityFromScopes({
|
410
|
+
...contextWithUser,
|
411
|
+
userUuid: 'user-456',
|
412
|
+
scopes: ['manage:AiAgentThread@self'],
|
413
|
+
}, builder);
|
414
|
+
const ability = builder.build();
|
415
|
+
// Can manage own AI agent threads
|
416
|
+
expect(ability.can('manage', subject('AiAgentThread', {
|
417
|
+
projectUuid: 'project-123',
|
418
|
+
userUuid: 'user-456',
|
419
|
+
}))).toBe(true);
|
420
|
+
// Cannot manage other users' threads
|
421
|
+
expect(ability.can('manage', subject('AiAgentThread', {
|
422
|
+
userUuid: 'other-user',
|
423
|
+
}))).toBe(false);
|
424
|
+
});
|
425
|
+
it('should handle view:ai_agent_thread permissions for all threads', () => {
|
426
|
+
const builder = new AbilityBuilder(Ability);
|
427
|
+
buildAbilityFromScopes({
|
428
|
+
...baseContextWithOrg,
|
429
|
+
isEnterprise: true,
|
430
|
+
scopes: ['view:AiAgentThread'],
|
431
|
+
}, builder);
|
432
|
+
const ability = builder.build();
|
433
|
+
// Can view any AI agent thread
|
434
|
+
expect(ability.can('view', subject('AiAgentThread', {
|
435
|
+
organizationUuid: 'org-123',
|
436
|
+
userUuid: 'any-user',
|
437
|
+
}))).toBe(true);
|
438
|
+
});
|
439
|
+
it('should handle manage:ai_agent_thread permissions for all threads', () => {
|
440
|
+
const builder = new AbilityBuilder(Ability);
|
441
|
+
buildAbilityFromScopes({
|
442
|
+
...baseContextWithOrg,
|
443
|
+
isEnterprise: true,
|
444
|
+
scopes: ['manage:AiAgentThread'],
|
445
|
+
}, builder);
|
446
|
+
const ability = builder.build();
|
447
|
+
// Can manage any AI agent thread
|
448
|
+
expect(ability.can('manage', subject('AiAgentThread', {
|
449
|
+
organizationUuid: 'org-123',
|
450
|
+
userUuid: 'any-user',
|
451
|
+
}))).toBe(true);
|
452
|
+
});
|
453
|
+
});
|
382
454
|
describe('edge cases and error handling', () => {
|
383
455
|
it('should handle empty scope array', () => {
|
384
456
|
const builder = new AbilityBuilder(Ability);
|
@@ -393,7 +465,7 @@ describe('scopeAbilityBuilder', () => {
|
|
393
465
|
const builder = new AbilityBuilder(Ability);
|
394
466
|
buildAbilityFromScopes({
|
395
467
|
...contextWithoutUser,
|
396
|
-
scopes: ['view:
|
468
|
+
scopes: ['view:Dashboard'],
|
397
469
|
}, builder);
|
398
470
|
const ability = builder.build();
|
399
471
|
// Should only allow viewing public dashboards
|
@@ -413,8 +485,8 @@ describe('scopeAbilityBuilder', () => {
|
|
413
485
|
buildAbilityFromScopes({
|
414
486
|
...baseContext,
|
415
487
|
scopes: [
|
416
|
-
'view:
|
417
|
-
'view:
|
488
|
+
'view:Dashboard',
|
489
|
+
'view:Project',
|
418
490
|
'invalid:scope',
|
419
491
|
],
|
420
492
|
}, builder);
|
@@ -431,9 +503,9 @@ describe('scopeAbilityBuilder', () => {
|
|
431
503
|
buildAbilityFromScopes({
|
432
504
|
...baseContext,
|
433
505
|
scopes: [
|
434
|
-
'view:
|
435
|
-
'manage:
|
436
|
-
'view:
|
506
|
+
'view:Dashboard',
|
507
|
+
'manage:SavedChart',
|
508
|
+
'view:Space',
|
437
509
|
],
|
438
510
|
}, builder);
|
439
511
|
const ability = builder.build();
|
@@ -456,7 +528,7 @@ describe('scopeAbilityBuilder', () => {
|
|
456
528
|
const builder = new AbilityBuilder(Ability);
|
457
529
|
buildAbilityFromScopes({
|
458
530
|
...baseContext,
|
459
|
-
scopes: ['view:
|
531
|
+
scopes: ['view:SavedChart'],
|
460
532
|
}, builder);
|
461
533
|
const ability = builder.build();
|
462
534
|
// Should not access saved chart from different project
|
@@ -476,7 +548,7 @@ describe('scopeAbilityBuilder', () => {
|
|
476
548
|
const builder = new AbilityBuilder(Ability);
|
477
549
|
buildAbilityFromScopes({
|
478
550
|
...contextWithUser,
|
479
|
-
scopes: ['view:
|
551
|
+
scopes: ['view:Dashboard'],
|
480
552
|
}, builder);
|
481
553
|
const ability = builder.build();
|
482
554
|
// Can view private dashboard with viewer access
|
@@ -516,7 +588,7 @@ describe('scopeAbilityBuilder', () => {
|
|
516
588
|
const builder = new AbilityBuilder(Ability);
|
517
589
|
buildAbilityFromScopes({
|
518
590
|
...contextWithUser,
|
519
|
-
scopes: ['manage:
|
591
|
+
scopes: ['manage:Dashboard@space'],
|
520
592
|
}, builder);
|
521
593
|
const ability = builder.build();
|
522
594
|
// Can manage dashboard with editor role
|
@@ -563,7 +635,7 @@ describe('scopeAbilityBuilder', () => {
|
|
563
635
|
const builder = new AbilityBuilder(Ability);
|
564
636
|
buildAbilityFromScopes({
|
565
637
|
...contextWithUser,
|
566
|
-
scopes: ['manage:
|
638
|
+
scopes: ['manage:Space@assigned'],
|
567
639
|
}, builder);
|
568
640
|
const ability = builder.build();
|
569
641
|
// Can manage space with admin role
|
@@ -601,7 +673,7 @@ describe('scopeAbilityBuilder', () => {
|
|
601
673
|
});
|
602
674
|
});
|
603
675
|
describe('job and job status permissions', () => {
|
604
|
-
it('should handle view:job permissions', () => {
|
676
|
+
it('should handle view:job@self permissions', () => {
|
605
677
|
const contextWithUser = {
|
606
678
|
...baseContext,
|
607
679
|
userUuid: 'user-456',
|
@@ -609,7 +681,7 @@ describe('scopeAbilityBuilder', () => {
|
|
609
681
|
const builder = new AbilityBuilder(Ability);
|
610
682
|
buildAbilityFromScopes({
|
611
683
|
...contextWithUser,
|
612
|
-
scopes: ['view:
|
684
|
+
scopes: ['view:Job@self'],
|
613
685
|
}, builder);
|
614
686
|
const ability = builder.build();
|
615
687
|
// Can view own jobs
|
@@ -621,30 +693,31 @@ describe('scopeAbilityBuilder', () => {
|
|
621
693
|
userUuid: 'other-user',
|
622
694
|
}))).toBe(false);
|
623
695
|
});
|
624
|
-
it('should handle view:job_status permissions for
|
696
|
+
it('should handle view:job_status@self permissions for user context', () => {
|
625
697
|
const builder = new AbilityBuilder(Ability);
|
626
698
|
buildAbilityFromScopes({
|
627
699
|
...baseContext,
|
628
|
-
|
700
|
+
userUuid: 'user-456',
|
701
|
+
scopes: ['view:JobStatus@self'],
|
629
702
|
}, builder);
|
630
703
|
const ability = builder.build();
|
631
|
-
//
|
704
|
+
// Can view own job status
|
632
705
|
expect(ability.can('view', subject('JobStatus', {
|
633
|
-
|
634
|
-
}))).toBe(
|
635
|
-
// Cannot view job status
|
706
|
+
createdByUserUuid: 'user-456',
|
707
|
+
}))).toBe(true);
|
708
|
+
// Cannot view other users' job status
|
636
709
|
expect(ability.can('view', subject('JobStatus', {
|
637
|
-
|
710
|
+
createdByUserUuid: 'other-user',
|
638
711
|
}))).toBe(false);
|
639
712
|
});
|
640
|
-
it('should handle view:job_status permissions
|
713
|
+
it('should handle view:job_status permissions for all job status', () => {
|
641
714
|
const builder = new AbilityBuilder(Ability);
|
642
715
|
buildAbilityFromScopes({
|
643
716
|
...baseContextWithOrg,
|
644
|
-
scopes: ['view:
|
717
|
+
scopes: ['view:JobStatus'],
|
645
718
|
}, builder);
|
646
719
|
const ability = builder.build();
|
647
|
-
// Can view all job status in organization
|
720
|
+
// Can view all job status in organization
|
648
721
|
expect(ability.can('view', subject('JobStatus', {
|
649
722
|
organizationUuid: 'org-123',
|
650
723
|
}))).toBe(true);
|
@@ -653,7 +726,70 @@ describe('scopeAbilityBuilder', () => {
|
|
653
726
|
organizationUuid: 'different-org',
|
654
727
|
}))).toBe(false);
|
655
728
|
});
|
656
|
-
it('should handle view:
|
729
|
+
it('should handle view:job permissions for all jobs', () => {
|
730
|
+
const builder = new AbilityBuilder(Ability);
|
731
|
+
buildAbilityFromScopes({
|
732
|
+
...baseContext,
|
733
|
+
scopes: ['view:Job'],
|
734
|
+
}, builder);
|
735
|
+
const ability = builder.build();
|
736
|
+
// Can view any job
|
737
|
+
expect(ability.can('view', subject('Job', {
|
738
|
+
organizationUuid: 'org-123',
|
739
|
+
projectUuid: 'project-123',
|
740
|
+
userUuid: 'any-user',
|
741
|
+
}))).toBe(true);
|
742
|
+
});
|
743
|
+
});
|
744
|
+
describe('space-based permissions modifiers', () => {
|
745
|
+
it('should handle manage:dashboard@space permissions', () => {
|
746
|
+
const contextWithUser = {
|
747
|
+
...baseContextWithOrg,
|
748
|
+
userUuid: 'user-456',
|
749
|
+
};
|
750
|
+
const builder = new AbilityBuilder(Ability);
|
751
|
+
buildAbilityFromScopes({
|
752
|
+
...contextWithUser,
|
753
|
+
scopes: ['manage:Dashboard@space'],
|
754
|
+
}, builder);
|
755
|
+
const ability = builder.build();
|
756
|
+
// Can manage dashboard with editor role
|
757
|
+
expect(ability.can('manage', subject('Dashboard', {
|
758
|
+
organizationUuid: 'org-123',
|
759
|
+
access: [
|
760
|
+
{
|
761
|
+
userUuid: 'user-456',
|
762
|
+
role: SpaceMemberRole.EDITOR,
|
763
|
+
},
|
764
|
+
],
|
765
|
+
}))).toBe(true);
|
766
|
+
// Can manage dashboard with admin role
|
767
|
+
expect(ability.can('manage', subject('Dashboard', {
|
768
|
+
organizationUuid: 'org-123',
|
769
|
+
access: [
|
770
|
+
{
|
771
|
+
userUuid: 'user-456',
|
772
|
+
role: SpaceMemberRole.ADMIN,
|
773
|
+
},
|
774
|
+
],
|
775
|
+
}))).toBe(true);
|
776
|
+
// Cannot manage dashboard with viewer role
|
777
|
+
expect(ability.can('manage', subject('Dashboard', {
|
778
|
+
organizationUuid: 'org-123',
|
779
|
+
access: [
|
780
|
+
{
|
781
|
+
userUuid: 'user-456',
|
782
|
+
role: SpaceMemberRole.VIEWER,
|
783
|
+
},
|
784
|
+
],
|
785
|
+
}))).toBe(false);
|
786
|
+
// Cannot manage dashboard without access
|
787
|
+
expect(ability.can('manage', subject('Dashboard', {
|
788
|
+
organizationUuid: 'org-123',
|
789
|
+
access: [],
|
790
|
+
}))).toBe(false);
|
791
|
+
});
|
792
|
+
it('should handle manage:saved_chart@space permissions', () => {
|
657
793
|
const contextWithUser = {
|
658
794
|
...baseContext,
|
659
795
|
userUuid: 'user-456',
|
@@ -661,16 +797,102 @@ describe('scopeAbilityBuilder', () => {
|
|
661
797
|
const builder = new AbilityBuilder(Ability);
|
662
798
|
buildAbilityFromScopes({
|
663
799
|
...contextWithUser,
|
664
|
-
scopes: ['
|
800
|
+
scopes: ['manage:SavedChart@space'],
|
665
801
|
}, builder);
|
666
802
|
const ability = builder.build();
|
667
|
-
// Can
|
668
|
-
expect(ability.can('
|
669
|
-
|
803
|
+
// Can manage saved chart with editor role
|
804
|
+
expect(ability.can('manage', subject('SavedChart', {
|
805
|
+
projectUuid: 'project-123',
|
806
|
+
access: [
|
807
|
+
{
|
808
|
+
userUuid: 'user-456',
|
809
|
+
role: SpaceMemberRole.EDITOR,
|
810
|
+
},
|
811
|
+
],
|
670
812
|
}))).toBe(true);
|
671
|
-
//
|
672
|
-
expect(ability.can('
|
673
|
-
|
813
|
+
// Can manage saved chart with admin role
|
814
|
+
expect(ability.can('manage', subject('SavedChart', {
|
815
|
+
projectUuid: 'project-123',
|
816
|
+
access: [
|
817
|
+
{
|
818
|
+
userUuid: 'user-456',
|
819
|
+
role: SpaceMemberRole.ADMIN,
|
820
|
+
},
|
821
|
+
],
|
822
|
+
}))).toBe(true);
|
823
|
+
// Cannot manage without proper access
|
824
|
+
expect(ability.can('manage', subject('SavedChart', {
|
825
|
+
projectUuid: 'project-123',
|
826
|
+
access: [
|
827
|
+
{
|
828
|
+
userUuid: 'other-user',
|
829
|
+
role: SpaceMemberRole.EDITOR,
|
830
|
+
},
|
831
|
+
],
|
832
|
+
}))).toBe(false);
|
833
|
+
});
|
834
|
+
it('should handle promote:dashboard@space permissions', () => {
|
835
|
+
const contextWithUser = {
|
836
|
+
...baseContext,
|
837
|
+
userUuid: 'user-456',
|
838
|
+
};
|
839
|
+
const builder = new AbilityBuilder(Ability);
|
840
|
+
buildAbilityFromScopes({
|
841
|
+
...contextWithUser,
|
842
|
+
scopes: ['promote:Dashboard@space'],
|
843
|
+
}, builder);
|
844
|
+
const ability = builder.build();
|
845
|
+
// Can promote dashboard with editor access
|
846
|
+
expect(ability.can('promote', subject('Dashboard', {
|
847
|
+
projectUuid: 'project-123',
|
848
|
+
access: [
|
849
|
+
{
|
850
|
+
userUuid: 'user-456',
|
851
|
+
role: SpaceMemberRole.EDITOR,
|
852
|
+
},
|
853
|
+
],
|
854
|
+
}))).toBe(true);
|
855
|
+
// Cannot promote without editor access
|
856
|
+
expect(ability.can('promote', subject('Dashboard', {
|
857
|
+
projectUuid: 'project-123',
|
858
|
+
access: [
|
859
|
+
{
|
860
|
+
userUuid: 'user-456',
|
861
|
+
role: SpaceMemberRole.VIEWER,
|
862
|
+
},
|
863
|
+
],
|
864
|
+
}))).toBe(false);
|
865
|
+
});
|
866
|
+
it('should handle manage:semantic_viewer@space permissions', () => {
|
867
|
+
const contextWithUser = {
|
868
|
+
...baseContextWithOrg,
|
869
|
+
userUuid: 'user-456',
|
870
|
+
};
|
871
|
+
const builder = new AbilityBuilder(Ability);
|
872
|
+
buildAbilityFromScopes({
|
873
|
+
...contextWithUser,
|
874
|
+
scopes: ['manage:SemanticViewer@space'],
|
875
|
+
}, builder);
|
876
|
+
const ability = builder.build();
|
877
|
+
// Can manage semantic viewer with editor role
|
878
|
+
expect(ability.can('manage', subject('SemanticViewer', {
|
879
|
+
organizationUuid: 'org-123',
|
880
|
+
access: [
|
881
|
+
{
|
882
|
+
userUuid: 'user-456',
|
883
|
+
role: SpaceMemberRole.EDITOR,
|
884
|
+
},
|
885
|
+
],
|
886
|
+
}))).toBe(true);
|
887
|
+
// Cannot manage without editor role
|
888
|
+
expect(ability.can('manage', subject('SemanticViewer', {
|
889
|
+
organizationUuid: 'org-123',
|
890
|
+
access: [
|
891
|
+
{
|
892
|
+
userUuid: 'user-456',
|
893
|
+
role: SpaceMemberRole.VIEWER,
|
894
|
+
},
|
895
|
+
],
|
674
896
|
}))).toBe(false);
|
675
897
|
});
|
676
898
|
});
|
@@ -679,7 +901,7 @@ describe('scopeAbilityBuilder', () => {
|
|
679
901
|
const builder = new AbilityBuilder(Ability);
|
680
902
|
buildAbilityFromScopes({
|
681
903
|
...baseContext,
|
682
|
-
scopes: ['view:
|
904
|
+
scopes: ['view:SemanticViewer'],
|
683
905
|
}, builder);
|
684
906
|
const ability = builder.build();
|
685
907
|
expect(ability.can('view', subject('SemanticViewer', {
|
@@ -691,14 +913,14 @@ describe('scopeAbilityBuilder', () => {
|
|
691
913
|
const contextWithOrgManage = {
|
692
914
|
...baseContextWithOrg,
|
693
915
|
userUuid: 'user-456',
|
694
|
-
scopes: ['manage:
|
916
|
+
scopes: ['manage:Organization'],
|
695
917
|
};
|
696
918
|
const builder = new AbilityBuilder(Ability);
|
697
919
|
buildAbilityFromScopes({
|
698
920
|
...contextWithOrgManage,
|
699
921
|
scopes: [
|
700
|
-
'manage:
|
701
|
-
'manage:
|
922
|
+
'manage:Organization',
|
923
|
+
'manage:SemanticViewer',
|
702
924
|
],
|
703
925
|
}, builder);
|
704
926
|
const ability = builder.build();
|
@@ -715,7 +937,7 @@ describe('scopeAbilityBuilder', () => {
|
|
715
937
|
const builder = new AbilityBuilder(Ability);
|
716
938
|
buildAbilityFromScopes({
|
717
939
|
...contextWithUser,
|
718
|
-
scopes: ['manage:
|
940
|
+
scopes: ['manage:SemanticViewer@space'],
|
719
941
|
}, builder);
|
720
942
|
const ability = builder.build();
|
721
943
|
// Can manage semantic viewer with editor role
|
@@ -745,7 +967,7 @@ describe('scopeAbilityBuilder', () => {
|
|
745
967
|
const builder = new AbilityBuilder(Ability);
|
746
968
|
buildAbilityFromScopes({
|
747
969
|
...baseContext,
|
748
|
-
scopes: ['create:
|
970
|
+
scopes: ['create:Space'],
|
749
971
|
}, builder);
|
750
972
|
const ability = builder.build();
|
751
973
|
expect(ability.can('create', subject('Space', {
|
@@ -759,7 +981,7 @@ describe('scopeAbilityBuilder', () => {
|
|
759
981
|
const builder = new AbilityBuilder(Ability);
|
760
982
|
buildAbilityFromScopes({
|
761
983
|
...baseContext,
|
762
|
-
scopes: ['manage:
|
984
|
+
scopes: ['manage:ExportCsv'],
|
763
985
|
}, builder);
|
764
986
|
const ability = builder.build();
|
765
987
|
expect(ability.can('manage', subject('ExportCsv', {
|
@@ -771,7 +993,7 @@ describe('scopeAbilityBuilder', () => {
|
|
771
993
|
const builder = new AbilityBuilder(Ability);
|
772
994
|
buildAbilityFromScopes({
|
773
995
|
...baseContext,
|
774
|
-
scopes: ['manage:
|
996
|
+
scopes: ['manage:ChangeCsvResults'],
|
775
997
|
}, builder);
|
776
998
|
const ability = builder.build();
|
777
999
|
expect(ability.can('manage', subject('ChangeCsvResults', {
|
@@ -785,7 +1007,7 @@ describe('scopeAbilityBuilder', () => {
|
|
785
1007
|
const builder = new AbilityBuilder(Ability);
|
786
1008
|
buildAbilityFromScopes({
|
787
1009
|
...baseContext,
|
788
|
-
scopes: ['view:
|
1010
|
+
scopes: ['view:UnderlyingData'],
|
789
1011
|
}, builder);
|
790
1012
|
const ability = builder.build();
|
791
1013
|
expect(ability.can('view', subject('UnderlyingData', {
|
@@ -799,7 +1021,7 @@ describe('scopeAbilityBuilder', () => {
|
|
799
1021
|
const builder = new AbilityBuilder(Ability);
|
800
1022
|
buildAbilityFromScopes({
|
801
1023
|
...baseContext,
|
802
|
-
scopes: ['manage:
|
1024
|
+
scopes: ['manage:SqlRunner'],
|
803
1025
|
}, builder);
|
804
1026
|
const ability = builder.build();
|
805
1027
|
expect(ability.can('manage', subject('SqlRunner', {
|
@@ -811,7 +1033,7 @@ describe('scopeAbilityBuilder', () => {
|
|
811
1033
|
const builder = new AbilityBuilder(Ability);
|
812
1034
|
buildAbilityFromScopes({
|
813
1035
|
...baseContext,
|
814
|
-
scopes: ['manage:
|
1036
|
+
scopes: ['manage:CustomSql'],
|
815
1037
|
}, builder);
|
816
1038
|
const ability = builder.build();
|
817
1039
|
expect(ability.can('manage', subject('CustomSql', {
|
@@ -826,7 +1048,7 @@ describe('scopeAbilityBuilder', () => {
|
|
826
1048
|
buildAbilityFromScopes({
|
827
1049
|
...baseContext,
|
828
1050
|
userUuid: 'user-456',
|
829
|
-
scopes: ['delete:
|
1051
|
+
scopes: ['delete:Project@self'],
|
830
1052
|
}, builder);
|
831
1053
|
const ability = builder.build();
|
832
1054
|
// Can delete specific project
|
@@ -839,21 +1061,22 @@ describe('scopeAbilityBuilder', () => {
|
|
839
1061
|
type: ProjectType.PREVIEW,
|
840
1062
|
}))).toBe(false);
|
841
1063
|
});
|
842
|
-
it('should handle delete:project for preview projects', () => {
|
1064
|
+
it('should handle delete:project@self for own preview projects', () => {
|
843
1065
|
const builder = new AbilityBuilder(Ability);
|
844
1066
|
buildAbilityFromScopes({
|
845
|
-
...
|
846
|
-
|
1067
|
+
...baseContext,
|
1068
|
+
userUuid: 'user-456',
|
1069
|
+
scopes: ['delete:Project@self'],
|
847
1070
|
}, builder);
|
848
1071
|
const ability = builder.build();
|
849
|
-
// Can delete preview projects in
|
1072
|
+
// Can delete preview projects in a project
|
850
1073
|
expect(ability.can('delete', subject('Project', {
|
851
|
-
|
1074
|
+
createdByUserUuid: 'user-456',
|
852
1075
|
type: ProjectType.PREVIEW,
|
853
1076
|
}))).toBe(true);
|
854
1077
|
// Cannot delete default projects
|
855
1078
|
expect(ability.can('delete', subject('Project', {
|
856
|
-
|
1079
|
+
createdByUserUuid: 'user-456',
|
857
1080
|
type: ProjectType.DEFAULT,
|
858
1081
|
}))).toBe(false);
|
859
1082
|
});
|
@@ -863,7 +1086,7 @@ describe('scopeAbilityBuilder', () => {
|
|
863
1086
|
const builder = new AbilityBuilder(Ability);
|
864
1087
|
buildAbilityFromScopes({
|
865
1088
|
...baseContext,
|
866
|
-
scopes: ['view:
|
1089
|
+
scopes: ['view:PinnedItems'],
|
867
1090
|
}, builder);
|
868
1091
|
const ability = builder.build();
|
869
1092
|
expect(ability.can('view', subject('PinnedItems', {
|
@@ -875,7 +1098,7 @@ describe('scopeAbilityBuilder', () => {
|
|
875
1098
|
const builder = new AbilityBuilder(Ability);
|
876
1099
|
buildAbilityFromScopes({
|
877
1100
|
...baseContext,
|
878
|
-
scopes: ['manage:
|
1101
|
+
scopes: ['manage:PinnedItems'],
|
879
1102
|
}, builder);
|
880
1103
|
const ability = builder.build();
|
881
1104
|
expect(ability.can('manage', subject('PinnedItems', {
|
@@ -889,7 +1112,7 @@ describe('scopeAbilityBuilder', () => {
|
|
889
1112
|
const builder = new AbilityBuilder(Ability);
|
890
1113
|
buildAbilityFromScopes({
|
891
1114
|
...baseContext,
|
892
|
-
scopes: ['manage:
|
1115
|
+
scopes: ['manage:Explore'],
|
893
1116
|
}, builder);
|
894
1117
|
const ability = builder.build();
|
895
1118
|
expect(ability.can('manage', subject('Explore', {
|
@@ -903,7 +1126,7 @@ describe('scopeAbilityBuilder', () => {
|
|
903
1126
|
const builder = new AbilityBuilder(Ability);
|
904
1127
|
buildAbilityFromScopes({
|
905
1128
|
...baseContext,
|
906
|
-
scopes: ['create:
|
1129
|
+
scopes: ['create:VirtualView'],
|
907
1130
|
}, builder);
|
908
1131
|
const ability = builder.build();
|
909
1132
|
expect(ability.can('create', subject('VirtualView', {
|
@@ -920,7 +1143,7 @@ describe('scopeAbilityBuilder', () => {
|
|
920
1143
|
const builder = new AbilityBuilder(Ability);
|
921
1144
|
buildAbilityFromScopes({
|
922
1145
|
...baseContext,
|
923
|
-
scopes: ['delete:
|
1146
|
+
scopes: ['delete:VirtualView'],
|
924
1147
|
}, builder);
|
925
1148
|
const ability = builder.build();
|
926
1149
|
expect(ability.can('delete', subject('VirtualView', {
|
@@ -937,7 +1160,7 @@ describe('scopeAbilityBuilder', () => {
|
|
937
1160
|
const builder = new AbilityBuilder(Ability);
|
938
1161
|
buildAbilityFromScopes({
|
939
1162
|
...baseContext,
|
940
|
-
scopes: ['manage:
|
1163
|
+
scopes: ['manage:VirtualView'],
|
941
1164
|
}, builder);
|
942
1165
|
const ability = builder.build();
|
943
1166
|
// Should be able to manage (create and delete)
|
@@ -960,9 +1183,9 @@ describe('scopeAbilityBuilder', () => {
|
|
960
1183
|
buildAbilityFromScopes({
|
961
1184
|
...baseContextWithOrg,
|
962
1185
|
scopes: [
|
963
|
-
'create:
|
964
|
-
'delete:
|
965
|
-
'manage:
|
1186
|
+
'create:VirtualView',
|
1187
|
+
'delete:VirtualView',
|
1188
|
+
'manage:VirtualView',
|
966
1189
|
],
|
967
1190
|
}, builder);
|
968
1191
|
const ability = builder.build();
|
@@ -985,9 +1208,9 @@ describe('scopeAbilityBuilder', () => {
|
|
985
1208
|
buildAbilityFromScopes({
|
986
1209
|
...baseContextWithOrg,
|
987
1210
|
scopes: [
|
988
|
-
'create:
|
989
|
-
'delete:
|
990
|
-
'manage:
|
1211
|
+
'create:VirtualView',
|
1212
|
+
'delete:VirtualView',
|
1213
|
+
'manage:VirtualView',
|
991
1214
|
],
|
992
1215
|
}, builder);
|
993
1216
|
const ability = builder.build();
|
@@ -1012,7 +1235,7 @@ describe('scopeAbilityBuilder', () => {
|
|
1012
1235
|
const builder = new AbilityBuilder(Ability);
|
1013
1236
|
buildAbilityFromScopes({
|
1014
1237
|
...baseContextWithOrg,
|
1015
|
-
scopes: ['view:
|
1238
|
+
scopes: ['view:OrganizationMemberProfile'],
|
1016
1239
|
}, builder);
|
1017
1240
|
const ability = builder.build();
|
1018
1241
|
expect(ability.can('view', subject('OrganizationMemberProfile', {
|
@@ -1029,7 +1252,7 @@ describe('scopeAbilityBuilder', () => {
|
|
1029
1252
|
const builder = new AbilityBuilder(Ability);
|
1030
1253
|
buildAbilityFromScopes({
|
1031
1254
|
...baseContext,
|
1032
|
-
scopes: ['manage:
|
1255
|
+
scopes: ['manage:OrganizationMemberProfile'],
|
1033
1256
|
}, builder);
|
1034
1257
|
const ability = builder.build();
|
1035
1258
|
expect(ability.can('manage', subject('OrganizationMemberProfile', {
|
@@ -1045,7 +1268,7 @@ describe('scopeAbilityBuilder', () => {
|
|
1045
1268
|
...baseContext,
|
1046
1269
|
isEnterprise: true,
|
1047
1270
|
organizationRole: 'admin',
|
1048
|
-
scopes: ['manage:
|
1271
|
+
scopes: ['manage:PersonalAccessToken'],
|
1049
1272
|
permissionsConfig: {
|
1050
1273
|
pat: {
|
1051
1274
|
enabled: true,
|
@@ -1062,7 +1285,7 @@ describe('scopeAbilityBuilder', () => {
|
|
1062
1285
|
...baseContext,
|
1063
1286
|
isEnterprise: true,
|
1064
1287
|
organizationRole: 'admin',
|
1065
|
-
scopes: ['manage:
|
1288
|
+
scopes: ['manage:PersonalAccessToken'],
|
1066
1289
|
permissionsConfig: {
|
1067
1290
|
pat: {
|
1068
1291
|
enabled: false,
|
@@ -1079,7 +1302,7 @@ describe('scopeAbilityBuilder', () => {
|
|
1079
1302
|
...baseContext,
|
1080
1303
|
isEnterprise: true,
|
1081
1304
|
organizationRole: 'developer',
|
1082
|
-
scopes: ['manage:
|
1305
|
+
scopes: ['manage:PersonalAccessToken'],
|
1083
1306
|
permissionsConfig: {
|
1084
1307
|
pat: {
|
1085
1308
|
enabled: true,
|
@@ -1096,7 +1319,7 @@ describe('scopeAbilityBuilder', () => {
|
|
1096
1319
|
...baseContext,
|
1097
1320
|
isEnterprise: true,
|
1098
1321
|
organizationRole: 'admin',
|
1099
|
-
scopes: ['manage:
|
1322
|
+
scopes: ['manage:PersonalAccessToken'],
|
1100
1323
|
// No permissionsConfig provided
|
1101
1324
|
}, builder);
|
1102
1325
|
const ability = builder.build();
|
@@ -1108,7 +1331,7 @@ describe('scopeAbilityBuilder', () => {
|
|
1108
1331
|
...baseContext,
|
1109
1332
|
isEnterprise: true,
|
1110
1333
|
organizationRole: '', // Empty organization role
|
1111
|
-
scopes: ['manage:
|
1334
|
+
scopes: ['manage:PersonalAccessToken'],
|
1112
1335
|
permissionsConfig: {
|
1113
1336
|
pat: {
|
1114
1337
|
enabled: true,
|