Finally:
p, 1, /something, GET, sol-*
Why if there are zero groups does it pass. There should be nothing to match right?
But if I do:
p, r1, /something, GET, sol-*
It again gives false as it should be
I found something strange, here is my model (I stripped all the fluff):
[request_definition]
r = user, path, method, tag
[policy_definition]
p = role, path, method, tag
[policy_effect]
e = some(where (p.eft == allow))
[role_definition]
g = _, _, _
[matchers]
m = g(r.user, p.role, r.tag)
Using the following policy:
p, 1, /something, GET, sol-*
g, 2, 1, sol-1
g, 3, 1, sol-1
And my request:
1, /something, GET, sol-1
I get true even though I expect it to be false
If I change my policy:
p, r1, /something, GET, sol-*
g, 2, r1, sol-1
g, 3, r1, sol-1
Then in results into false as it should
Why is this the case?
Is it best practice to prefix ids if those are used in the policy?
You can test it in the editor
Not the most efficient but gaurantee that I get the latest data
Yeah currently not worrying about perfomance
I am not using the default structure like in this adapter:
https://github.com/casbin/xorm-adapter
So I can gather the data I need efficiently and generate the policy + role group at runtime
My example is just an example to make it understandable. In the end it will all just be id's that are matched
In my case only the model is static all others things are loaded from db or cache at runtime per request
I went from this model:
const stringModel = `
[request_definition]
r = user, dom, obj, act, tag
[policy_definition]
p = role, dom, obj, act, tag
[policy_effect]
e = some(where (p.eft == allow))
[role_definition]
g = _, _, _
g2 = _, _
[matchers]
m = g(r.user, p.role, r.tag) && g2(p.role, r.dom) && keyMatch(r.tag, p.tag) && keyMatch(r.act, p.act) && (keyMatch(r.obj, p.obj) || keyMatch2(r.obj, p.obj))
`
func TestDefaultRbac(t *testing.T) {
m, _ := model.NewModelFromString(stringModel)
e, _ := casbin.NewEnforcer(m, false)
e.AddPolicies([][]string{
{"reader", "org1", "/project", "GET", "project_*"},
{"editor", "org1", "/project", "DELETE", "project_*"},
{"reader2", "org2", "/project", "GET", "project_*"},
})
e.AddNamedGroupingPolicies("g", [][]string{
{"tom", "reader", "project_1"},
{"tom", "reader", "project_2"},
{"tom", "reader", "project_3"},
{"tom", "editor", "project_5"},
{"henk", "reader2", "project_6"},
})
e.AddNamedGroupingPolicies("g2", [][]string{
{"reader", "org1"},
{"reader2", "org2"},
{"editor", "org1"},
})
enforcer := func(user, dom, obj, act, tag string, expect bool) {
resp, _ := e.Enforce(user, dom, obj, act, tag)
if resp != expect {
t.Errorf("Response is for %s %s %s %s %s %t expected to be %t", user, dom, obj, act, tag, resp, expect)
}
}
enforcer("tom", "org1", "/project", "GET", "project_1", true)
enforcer("tom", "org1", "/project", "DELETE", "project_1", false)
enforcer("tom", "org1", "/project", "DELETE", "project_5", true)
enforcer("tom", "org2", "/project", "GET", "project_6", false)
enforcer("henk", "org2", "/project", "GET", "project_6", true)
}
to this, I left out the whole org part since the roles will be unique per org and are therefore implicity tied to an org:
const stringModel2 = `
[request_definition]
r = user, obj, act, tag
[policy_definition]
p = role, obj, act, tag
[policy_effect]
e = some(where (p.eft == allow))
[role_definition]
g = _, _, _
[matchers]
m = g(r.user, p.role, r.tag) && keyMatch(r.tag, p.tag) && keyMatch(r.act, p.act) && (keyMatch(r.obj, p.obj) || keyMatch2(r.obj, p.obj))
`
func TestDefaultRbac2(t *testing.T) {
m, _ := model.NewModelFromString(stringModel2)
e, _ := casbin.NewEnforcer(m, false)
e.AddPolicies([][]string{
{"reader", "/project", "GET", "project_*"},
{"editor", "/project", "DELETE", "project_*"},
{"reader2", "/project", "GET", "project_*"},
})
e.AddNamedGroupingPolicies("g", [][]string{
{"tom", "reader", "project_1"},
{"tom", "reader", "project_2"},
{"tom", "reader", "project_3"},
{"tom", "editor", "project_5"},
{"henk", "reader2", "project_6"},
})
enforcer := func(user, obj, act, tag string, expect bool) {
resp, _ := e.Enforce(user, obj, act, tag)
if resp != expect {
t.Errorf("Response is for %s %s %s %s %t expected to be %t", user, obj, act, tag, resp, expect)
}
}
enforcer("tom", "/project", "GET", "project_1", true)
enforcer("tom", "/project", "DELETE", "project_1", false)
enforcer("tom", "/project", "DELETE", "project_5", true)
enforcer("tom", "/project", "GET", "project_6", false)
enforcer("henk", "/project", "GET", "project_6", true)
}
Both do the same thing one is explicit with role + org combination and the other is implicit. E.g. reader is part of org1 and reader2 is part of org2
I plan to make a custom adapter https://casbin.org/docs/adapters/ that loads the filtered policy for each request and checks it.
that loads the filtered policy for each request and checks it. for endpoints that need it
Anyone know if it is more efficient to have many role_definiton mappings or is it better to have many policy_definiton rules
So whats better:
g _, _, _
user,1 role_1, project_1
user1000, role_1, project_1
Or many policy definitons
policy_definitions:
r = sub, dom, act
user1, project1, read
...
user1000, project1, read
I can implement my usecase with both options. But from some benchmarks it seems that having many role_defintion mappings is more efficient.
To add some numbers to this: there can be 20000 role user mappings and just few lines of policy definitions
I also found this so that seems to verify what I found:
Grant permissions to RBAC roles instead of users directly. Casbin's RBAC is implemented by a role inheritance tree (as a cache). So, given a user like Alice, Casbin only takes O(1) time to query the RBAC tree for the role-user relationship and perform enforcement. If your g rules don't change often, then the RBAC tree won't need to be constantly updated. See the details of this discussion here: https://github.com/casbin/casbin/issues/681#issuecomment-763801583

