@@ -2372,4 +2372,221 @@ var _ = Describe("check-engine", func() {
23722372 }
23732373 })
23742374 })
2375+
2376+ Context ("Recursive Attribute Permissions" , func () {
2377+ It ("should allow same-type recursive attribute permissions" , func () {
2378+ schema := `
2379+ entity user {}
2380+
2381+ entity resource {
2382+ relation parent @resource
2383+ attribute is_public boolean
2384+ permission view = is_public or parent.view
2385+ }
2386+ `
2387+
2388+ db , err := factories .DatabaseFactory (config.Database {Engine : "memory" })
2389+ Expect (err ).ShouldNot (HaveOccurred ())
2390+
2391+ conf , err := newSchema (schema )
2392+ Expect (err ).ShouldNot (HaveOccurred ())
2393+
2394+ schemaWriter := factories .SchemaWriterFactory (db )
2395+ err = schemaWriter .WriteSchema (context .Background (), conf )
2396+ Expect (err ).ShouldNot (HaveOccurred ())
2397+
2398+ schemaReader := factories .SchemaReaderFactory (db )
2399+ dataReader := factories .DataReaderFactory (db )
2400+ dataWriter := factories .DataWriterFactory (db )
2401+
2402+ checkEngine := NewCheckEngine (schemaReader , dataReader )
2403+ lookupEngine := NewLookupEngine (checkEngine , schemaReader , dataReader )
2404+ invoker := invoke .NewDirectInvoker (schemaReader , dataReader , checkEngine , nil , lookupEngine , nil )
2405+ checkEngine .SetInvoker (invoker )
2406+
2407+ relationships := []string {
2408+ "resource:r1#parent@resource:default" ,
2409+ }
2410+
2411+ var tuples []* base.Tuple
2412+ for _ , relationship := range relationships {
2413+ t , err := tuple .Tuple (relationship )
2414+ Expect (err ).ShouldNot (HaveOccurred ())
2415+ tuples = append (tuples , t )
2416+ }
2417+
2418+ publicAttr , err := attribute .Attribute ("resource:default$is_public|boolean:true" )
2419+ Expect (err ).ShouldNot (HaveOccurred ())
2420+
2421+ _ , err = dataWriter .Write (
2422+ context .Background (),
2423+ "t1" ,
2424+ database .NewTupleCollection (tuples ... ),
2425+ database .NewAttributeCollection (publicAttr ),
2426+ )
2427+ Expect (err ).ShouldNot (HaveOccurred ())
2428+
2429+ resp , err := invoker .Check (context .Background (), & base.PermissionCheckRequest {
2430+ TenantId : "t1" ,
2431+ Entity : & base.Entity {Type : "resource" , Id : "r1" },
2432+ Permission : "view" ,
2433+ Subject : & base.Subject {Type : "user" , Id : "u1" },
2434+ Metadata : & base.PermissionCheckRequestMetadata {
2435+ SnapToken : token .NewNoopToken ().Encode ().String (),
2436+ SchemaVersion : "" ,
2437+ Depth : 20 ,
2438+ },
2439+ })
2440+ Expect (err ).ShouldNot (HaveOccurred ())
2441+ Expect (resp .GetCan ()).To (Equal (base .CheckResult_CHECK_RESULT_ALLOWED ))
2442+ })
2443+
2444+ It ("should allow cross-type recursive attribute permissions" , func () {
2445+ schema := `
2446+ entity user {}
2447+
2448+ entity org {
2449+ attribute is_public boolean
2450+ permission view = is_public
2451+ }
2452+
2453+ entity folder {
2454+ relation parent @org
2455+ attribute is_public boolean
2456+ permission view = is_public or parent.view
2457+ }
2458+
2459+ entity resource {
2460+ relation parent @folder
2461+ permission view = parent.view
2462+ }
2463+ `
2464+
2465+ db , err := factories .DatabaseFactory (config.Database {Engine : "memory" })
2466+ Expect (err ).ShouldNot (HaveOccurred ())
2467+
2468+ conf , err := newSchema (schema )
2469+ Expect (err ).ShouldNot (HaveOccurred ())
2470+
2471+ schemaWriter := factories .SchemaWriterFactory (db )
2472+ err = schemaWriter .WriteSchema (context .Background (), conf )
2473+ Expect (err ).ShouldNot (HaveOccurred ())
2474+
2475+ schemaReader := factories .SchemaReaderFactory (db )
2476+ dataReader := factories .DataReaderFactory (db )
2477+ dataWriter := factories .DataWriterFactory (db )
2478+
2479+ checkEngine := NewCheckEngine (schemaReader , dataReader )
2480+ lookupEngine := NewLookupEngine (checkEngine , schemaReader , dataReader )
2481+ invoker := invoke .NewDirectInvoker (schemaReader , dataReader , checkEngine , nil , lookupEngine , nil )
2482+ checkEngine .SetInvoker (invoker )
2483+
2484+ relationships := []string {
2485+ "folder:f1#parent@org:o1" ,
2486+ "resource:r1#parent@folder:f1" ,
2487+ }
2488+
2489+ var tuples []* base.Tuple
2490+ for _ , relationship := range relationships {
2491+ t , err := tuple .Tuple (relationship )
2492+ Expect (err ).ShouldNot (HaveOccurred ())
2493+ tuples = append (tuples , t )
2494+ }
2495+
2496+ publicAttr , err := attribute .Attribute ("org:o1$is_public|boolean:true" )
2497+ Expect (err ).ShouldNot (HaveOccurred ())
2498+
2499+ _ , err = dataWriter .Write (
2500+ context .Background (),
2501+ "t1" ,
2502+ database .NewTupleCollection (tuples ... ),
2503+ database .NewAttributeCollection (publicAttr ),
2504+ )
2505+ Expect (err ).ShouldNot (HaveOccurred ())
2506+
2507+ resp , err := invoker .Check (context .Background (), & base.PermissionCheckRequest {
2508+ TenantId : "t1" ,
2509+ Entity : & base.Entity {Type : "resource" , Id : "r1" },
2510+ Permission : "view" ,
2511+ Subject : & base.Subject {Type : "user" , Id : "u1" },
2512+ Metadata : & base.PermissionCheckRequestMetadata {
2513+ SnapToken : token .NewNoopToken ().Encode ().String (),
2514+ SchemaVersion : "" ,
2515+ Depth : 20 ,
2516+ },
2517+ })
2518+ Expect (err ).ShouldNot (HaveOccurred ())
2519+ Expect (resp .GetCan ()).To (Equal (base .CheckResult_CHECK_RESULT_ALLOWED ))
2520+ })
2521+
2522+ It ("should allow mixed-entrance recursive attribute permissions" , func () {
2523+ schema := `
2524+ entity user {}
2525+
2526+ entity resource {
2527+ relation viewer @user
2528+ relation parent @resource
2529+ attribute is_public boolean
2530+ permission view = viewer or is_public or parent.view
2531+ }
2532+ `
2533+
2534+ db , err := factories .DatabaseFactory (config.Database {Engine : "memory" })
2535+ Expect (err ).ShouldNot (HaveOccurred ())
2536+
2537+ conf , err := newSchema (schema )
2538+ Expect (err ).ShouldNot (HaveOccurred ())
2539+
2540+ schemaWriter := factories .SchemaWriterFactory (db )
2541+ err = schemaWriter .WriteSchema (context .Background (), conf )
2542+ Expect (err ).ShouldNot (HaveOccurred ())
2543+
2544+ schemaReader := factories .SchemaReaderFactory (db )
2545+ dataReader := factories .DataReaderFactory (db )
2546+ dataWriter := factories .DataWriterFactory (db )
2547+
2548+ checkEngine := NewCheckEngine (schemaReader , dataReader )
2549+ lookupEngine := NewLookupEngine (checkEngine , schemaReader , dataReader )
2550+ invoker := invoke .NewDirectInvoker (schemaReader , dataReader , checkEngine , nil , lookupEngine , nil )
2551+ checkEngine .SetInvoker (invoker )
2552+
2553+ relationships := []string {
2554+ "resource:za#viewer@user:u1" ,
2555+ "resource:zb#parent@resource:za" ,
2556+ "resource:zc#parent@resource:zb" ,
2557+ }
2558+
2559+ var tuples []* base.Tuple
2560+ for _ , relationship := range relationships {
2561+ t , err := tuple .Tuple (relationship )
2562+ Expect (err ).ShouldNot (HaveOccurred ())
2563+ tuples = append (tuples , t )
2564+ }
2565+
2566+ publicAttr , err := attribute .Attribute ("resource:za$is_public|boolean:true" )
2567+ Expect (err ).ShouldNot (HaveOccurred ())
2568+
2569+ _ , err = dataWriter .Write (
2570+ context .Background (),
2571+ "t1" ,
2572+ database .NewTupleCollection (tuples ... ),
2573+ database .NewAttributeCollection (publicAttr ),
2574+ )
2575+ Expect (err ).ShouldNot (HaveOccurred ())
2576+
2577+ resp , err := invoker .Check (context .Background (), & base.PermissionCheckRequest {
2578+ TenantId : "t1" ,
2579+ Entity : & base.Entity {Type : "resource" , Id : "zc" },
2580+ Permission : "view" ,
2581+ Subject : & base.Subject {Type : "user" , Id : "u1" },
2582+ Metadata : & base.PermissionCheckRequestMetadata {
2583+ SnapToken : token .NewNoopToken ().Encode ().String (),
2584+ SchemaVersion : "" ,
2585+ Depth : 20 ,
2586+ },
2587+ })
2588+ Expect (err ).ShouldNot (HaveOccurred ())
2589+ Expect (resp .GetCan ()).To (Equal (base .CheckResult_CHECK_RESULT_ALLOWED ))
2590+ })
2591+ })
23752592})
0 commit comments