@@ -298,6 +298,267 @@ func addFrame(trace *v1.CheckDebugTrace, foundFrames *mapz.Set[string]) {
298
298
}
299
299
}
300
300
301
+ func TestCheckPermissionOverSchema (t * testing.T ) {
302
+ testCases := []struct {
303
+ name string
304
+ schema string
305
+ relationships []* core.RelationTuple
306
+ resource * core.ObjectAndRelation
307
+ subject * core.ObjectAndRelation
308
+ expectedPermissionship v1.ResourceCheckResult_Membership
309
+ }{
310
+ {
311
+ "basic union" ,
312
+ `definition user {}
313
+
314
+ definition document {
315
+ relation editor: user
316
+ relation viewer: user
317
+ permission view = viewer + editor
318
+ }` ,
319
+ []* core.RelationTuple {
320
+ tuple .MustParse ("document:first#viewer@user:tom" ),
321
+ },
322
+ ONR ("document" , "first" , "view" ),
323
+ ONR ("user" , "tom" , "..." ),
324
+ v1 .ResourceCheckResult_MEMBER ,
325
+ },
326
+ {
327
+ "basic intersection" ,
328
+ `definition user {}
329
+
330
+ definition document {
331
+ relation editor: user
332
+ relation viewer: user
333
+ permission view = viewer & editor
334
+ }` ,
335
+ []* core.RelationTuple {
336
+ tuple .MustParse ("document:first#viewer@user:tom" ),
337
+ tuple .MustParse ("document:first#editor@user:tom" ),
338
+ },
339
+ ONR ("document" , "first" , "view" ),
340
+ ONR ("user" , "tom" , "..." ),
341
+ v1 .ResourceCheckResult_MEMBER ,
342
+ },
343
+ {
344
+ "basic exclusion" ,
345
+ `definition user {}
346
+
347
+ definition document {
348
+ relation editor: user
349
+ relation viewer: user
350
+ permission view = viewer - editor
351
+ }` ,
352
+ []* core.RelationTuple {
353
+ tuple .MustParse ("document:first#viewer@user:tom" ),
354
+ },
355
+ ONR ("document" , "first" , "view" ),
356
+ ONR ("user" , "tom" , "..." ),
357
+ v1 .ResourceCheckResult_MEMBER ,
358
+ },
359
+ {
360
+ "basic union, multiple branches" ,
361
+ `definition user {}
362
+
363
+ definition document {
364
+ relation editor: user
365
+ relation viewer: user
366
+ permission view = viewer + editor
367
+ }` ,
368
+ []* core.RelationTuple {
369
+ tuple .MustParse ("document:first#viewer@user:tom" ),
370
+ tuple .MustParse ("document:first#editor@user:tom" ),
371
+ },
372
+ ONR ("document" , "first" , "view" ),
373
+ ONR ("user" , "tom" , "..." ),
374
+ v1 .ResourceCheckResult_MEMBER ,
375
+ },
376
+ {
377
+ "basic union no permission" ,
378
+ `definition user {}
379
+
380
+ definition document {
381
+ relation editor: user
382
+ relation viewer: user
383
+ permission view = viewer + editor
384
+ }` ,
385
+ []* core.RelationTuple {},
386
+ ONR ("document" , "first" , "view" ),
387
+ ONR ("user" , "tom" , "..." ),
388
+ v1 .ResourceCheckResult_NOT_MEMBER ,
389
+ },
390
+ {
391
+ "basic intersection no permission" ,
392
+ `definition user {}
393
+
394
+ definition document {
395
+ relation editor: user
396
+ relation viewer: user
397
+ permission view = viewer & editor
398
+ }` ,
399
+ []* core.RelationTuple {
400
+ tuple .MustParse ("document:first#viewer@user:tom" ),
401
+ },
402
+ ONR ("document" , "first" , "view" ),
403
+ ONR ("user" , "tom" , "..." ),
404
+ v1 .ResourceCheckResult_NOT_MEMBER ,
405
+ },
406
+ {
407
+ "basic exclusion no permission" ,
408
+ `definition user {}
409
+
410
+ definition document {
411
+ relation banned: user
412
+ relation viewer: user
413
+ permission view = viewer - banned
414
+ }` ,
415
+ []* core.RelationTuple {
416
+ tuple .MustParse ("document:first#viewer@user:tom" ),
417
+ tuple .MustParse ("document:first#banned@user:tom" ),
418
+ },
419
+ ONR ("document" , "first" , "view" ),
420
+ ONR ("user" , "tom" , "..." ),
421
+ v1 .ResourceCheckResult_NOT_MEMBER ,
422
+ },
423
+ {
424
+ "exclusion with multiple branches" ,
425
+ `definition user {}
426
+
427
+ definition group {
428
+ relation member: user
429
+ relation banned: user
430
+ permission view = member - banned
431
+ }
432
+
433
+ definition document {
434
+ relation group: group
435
+ permission view = group->view
436
+ }` ,
437
+ []* core.RelationTuple {
438
+ tuple .MustParse ("document:first#group@group:first" ),
439
+ tuple .MustParse ("document:first#group@group:second" ),
440
+ tuple .MustParse ("group:first#member@user:tom" ),
441
+ tuple .MustParse ("group:first#banned@user:tom" ),
442
+ tuple .MustParse ("group:second#member@user:tom" ),
443
+ },
444
+ ONR ("document" , "first" , "view" ),
445
+ ONR ("user" , "tom" , "..." ),
446
+ v1 .ResourceCheckResult_MEMBER ,
447
+ },
448
+ {
449
+ "intersection with multiple branches" ,
450
+ `definition user {}
451
+
452
+ definition group {
453
+ relation member: user
454
+ relation other: user
455
+ permission view = member & other
456
+ }
457
+
458
+ definition document {
459
+ relation group: group
460
+ permission view = group->view
461
+ }` ,
462
+ []* core.RelationTuple {
463
+ tuple .MustParse ("document:first#group@group:first" ),
464
+ tuple .MustParse ("document:first#group@group:second" ),
465
+ tuple .MustParse ("group:first#member@user:tom" ),
466
+ tuple .MustParse ("group:first#other@user:tom" ),
467
+ tuple .MustParse ("group:second#member@user:tom" ),
468
+ },
469
+ ONR ("document" , "first" , "view" ),
470
+ ONR ("user" , "tom" , "..." ),
471
+ v1 .ResourceCheckResult_MEMBER ,
472
+ },
473
+ {
474
+ "exclusion with multiple branches no permission" ,
475
+ `definition user {}
476
+
477
+ definition group {
478
+ relation member: user
479
+ relation banned: user
480
+ permission view = member - banned
481
+ }
482
+
483
+ definition document {
484
+ relation group: group
485
+ permission view = group->view
486
+ }` ,
487
+ []* core.RelationTuple {
488
+ tuple .MustParse ("document:first#group@group:first" ),
489
+ tuple .MustParse ("document:first#group@group:second" ),
490
+ tuple .MustParse ("group:first#member@user:tom" ),
491
+ tuple .MustParse ("group:first#banned@user:tom" ),
492
+ tuple .MustParse ("group:second#member@user:tom" ),
493
+ tuple .MustParse ("group:second#banned@user:tom" ),
494
+ },
495
+ ONR ("document" , "first" , "view" ),
496
+ ONR ("user" , "tom" , "..." ),
497
+ v1 .ResourceCheckResult_NOT_MEMBER ,
498
+ },
499
+ {
500
+ "intersection with multiple branches no permission" ,
501
+ `definition user {}
502
+
503
+ definition group {
504
+ relation member: user
505
+ relation other: user
506
+ permission view = member & other
507
+ }
508
+
509
+ definition document {
510
+ relation group: group
511
+ permission view = group->view
512
+ }` ,
513
+ []* core.RelationTuple {
514
+ tuple .MustParse ("document:first#group@group:first" ),
515
+ tuple .MustParse ("document:first#group@group:second" ),
516
+ tuple .MustParse ("group:first#member@user:tom" ),
517
+ tuple .MustParse ("group:second#member@user:tom" ),
518
+ },
519
+ ONR ("document" , "first" , "view" ),
520
+ ONR ("user" , "tom" , "..." ),
521
+ v1 .ResourceCheckResult_NOT_MEMBER ,
522
+ },
523
+ }
524
+
525
+ for _ , tc := range testCases {
526
+ tc := tc
527
+ t .Run (tc .name , func (t * testing.T ) {
528
+ require := require .New (t )
529
+
530
+ dispatcher := NewLocalOnlyDispatcher (10 )
531
+
532
+ ds , err := memdb .NewMemdbDatastore (0 , 0 , memdb .DisableGC )
533
+ require .NoError (err )
534
+
535
+ ds , revision := testfixtures .DatastoreFromSchemaAndTestRelationships (ds , tc .schema , tc .relationships , require )
536
+
537
+ ctx := datastoremw .ContextWithHandle (context .Background ())
538
+ require .NoError (datastoremw .SetInContext (ctx , ds ))
539
+
540
+ resp , err := dispatcher .DispatchCheck (ctx , & v1.DispatchCheckRequest {
541
+ ResourceRelation : RR (tc .resource .Namespace , tc .resource .Relation ),
542
+ ResourceIds : []string {tc .resource .ObjectId },
543
+ Subject : tc .subject ,
544
+ Metadata : & v1.ResolverMeta {
545
+ AtRevision : revision .String (),
546
+ DepthRemaining : 50 ,
547
+ },
548
+ ResultsSetting : v1 .DispatchCheckRequest_ALLOW_SINGLE_RESULT ,
549
+ })
550
+ require .NoError (err )
551
+
552
+ membership := v1 .ResourceCheckResult_NOT_MEMBER
553
+ if r , ok := resp .ResultsByResourceId [tc .resource .ObjectId ]; ok {
554
+ membership = r .Membership
555
+ }
556
+
557
+ require .Equal (tc .expectedPermissionship , membership )
558
+ })
559
+ }
560
+ }
561
+
301
562
func TestCheckDebugging (t * testing.T ) {
302
563
type expectedFrame struct {
303
564
resourceType * core.RelationReference
0 commit comments