Skip to content

Commit 0462a2e

Browse files
authored
Merge pull request #2763 from Permify/fix/lookup-entity-recursive-attributes
fix: support recursive attributes in lookup-entity
2 parents 5ca8050 + f2b70e0 commit 0462a2e

5 files changed

Lines changed: 1061 additions & 14 deletions

File tree

internal/engines/check_test.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)