Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] Why doesn't this model work? #1237

Open
JoshSharpe opened this issue Apr 23, 2023 · 16 comments
Open

[Question] Why doesn't this model work? #1237

JoshSharpe opened this issue Apr 23, 2023 · 16 comments

Comments

@JoshSharpe
Copy link

JoshSharpe commented Apr 23, 2023

Want to prioritize this issue? Try:

issuehunt-to-marktext


What's your scenario? What do you want to achieve?
I'm trying to get a basic model running to understand how this authorization works. The examples aren't practical enough to help me grasp these concepts well enough. I feel like I'm missing something so any help or guidance would be appreciated.

My hope:
Resource = /organizations/:orgId or /organizations/:orgId/workspaces/:wkspId
By doing the full path, I can ensure uniqueness across all and maintain a hierarchical structure. ie make sure a wkspId is inside a given org.

Actions = create_workspace, read_users, update_users, etc. So just various actions that can be done within these resources.

Subject = either a userId or groupId. I want to allow permissions to be assigned based on user-level and group-level.

Domain = I believe I should be skipping this based on how I did resource structure.

Model

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition] # This I keep changing to try different things. 
g = _, _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub, obj) && keyMatch2(obj == obj) && r.act == p.act

Policy

p, admin, organizations/:org, create_workspace  # An admin for an org can create a workspace
p, admin, organizations/:org, view_users  # An admin for an org can create a workspace
p, alice, organizations/org2, view_users # Alice can view users for org 2

g, alice, admin, organizations/org1 # Alice is an admin for org 1 (therefore can create workspaces and view users)

Enforce appears to work as expected:

Enforce("alice", "organizations/org1", "view_users") ---> true (expected: true)
Enforce("alice", "organizations/org1", "create_workspace") ---> true (expected: true)
Enforce("alice", "organizations/org2", "view_users") ---> true (expected: true)
Enforce("alice", "organizations/org2", "create_workspace") ---> false (expected: false)
Enforce("alice", "organizations/org3", "view_users") ---> false (expected: false)

However,

e.GetAllObjects() ---> [organizations/:org organizations/org2] # Missing org1 because that is in domain

Questions:
How do I see a list of organizations that the user Alice has access to?
How do I see a list of users and roles that an organization includes?
Am I on the right track for setting this model up correctly?

@casbin-bot
Copy link
Member

@tangyang9464 @JalinWang

@PokIsemaine
Copy link
Member

@JoshSharpe I tried to copy your model and policy, but the results are not consistent with yours. Are you sure that the model you provided is the same as the one you are using locally?
image
image

image

@PokIsemaine
Copy link
Member

Regarding the model, if you don't need a domain, you can use g =_,_ instead of changing the meaning of g =_,_,_.

@JoshSharpe
Copy link
Author

@JoshSharpe I tried to copy your model and policy, but the results are not consistent with yours. Are you sure that the model you provided is the same as the one you are using locally? image image

image

This is interesting. Here are my screenshots. They do appear to match from what I can tell.

image

image

image

image

@JoshSharpe
Copy link
Author

Regarding the model, if you don't need a domain, you can use g =_,_ instead of changing the meaning of g =_,_,_.

Struggling to understand domain vs obj. Based on my use case, I need to map subject based on group + object? So that is why I include g=_,_,_

@PokIsemaine
Copy link
Member

@JoshSharpe There are some differences in matchers.
The model provided in issues(I copied it)

m = g(r.sub, p.sub, obj) && keyMatch2(obj == obj) && r.act == p.act

The model you actually use

m = g(r.sub, p.sub, r.obj) && keyMatch2(r.obj, p.obj) && r.act == p.act

image

image

After synchronizing with the model you actually used, their results are consistent

@PokIsemaine
Copy link
Member

@JoshSharpe I've drawn some diagrams that I hope can help you
image
An explanation of why the following result is true,<nil>

e, err := casbin.NewEnforcer("model.conf", "policy.csv")
if err != nil {
  panic(err)
}

fmt.Println(e.Enforce("alice", "organizations/org1", "create_workspace"))

First, NewEnforcer will generate an Enforcer based on the model and policy you provide
In this process, because g = _,_,_ exists, it will build the relationship between alice and admin in domain:organizations/org1 based on the policy you provided

e.Enforce("alice", "organizations/org1", "create_workspace")

Then when enforced, it will first determine whether alice can be considered admin according to the g function

There is some ambiguity here, and I think that's why you're confused

According to Casbin's default rules, g = _, _, _ should correspond to sub, role, domain, respectively. To enhance the flexibility and extensibility of casbin, they are not directly distinguished but expressed as strings.

g("alice","admin","organizations/org1")

Depending on the matcher in the model, you might think that organizations/org1 should be r.obj. But in reality, casbin gets the value of the string from r.obj, and inside casbin, it considers the string value passed in to be a domain.

Also, since the value of r.obj happens to be the same as the domain in the previously established relationship, alice is inferred to be admin.

This matching only happens in matchers, and at the model level organizations/org1 is still just domain instead of object

image

@JoshSharpe
Copy link
Author

@JoshSharpe I've drawn some diagrams that I hope can help you

image

An explanation of why the following result is true,<nil>

e, err := casbin.NewEnforcer("model.conf", "policy.csv")

if err != nil {

  panic(err)

}



fmt.Println(e.Enforce("alice", "organizations/org1", "create_workspace"))

First, NewEnforcer will generate an Enforcer based on the model and policy you provide

In this process, because g = _,_,_ exists, it will build the relationship between alice and admin in domain:organizations/org1 based on the policy you provided

e.Enforce("alice", "organizations/org1", "create_workspace")

Then when enforced, it will first determine whether alice can be considered admin according to the g function

There is some ambiguity here, and I think that's why you're confused

According to Casbin's default rules, g = _, _, _ should correspond to sub, role, domain, respectively. To enhance the flexibility and extensibility of casbin, they are not directly distinguished but expressed as strings.


g("alice","admin","organizations/org1")

Depending on the matcher in the model, you might think that organizations/org1 should be r.obj. But in reality, casbin gets the value of the string from r.obj, and inside casbin, it considers the string value passed in to be a domain.

Also, since the value of r.obj happens to be the same as the domain in the previously established relationship, alice is inferred to be admin.

This matching only happens in matchers, and at the model level organizations/org1 is still just domain instead of object

image

Is this a bug or intended behavior?

So I saw the g function as:

g(a,b) described how a is grouped to b
g(a,b,c) just describes how a is ground to <b,c>.

It seems more like g(a,b,c) has additional underlying context. It isn't that it can be used for domain but it must be used for domains.

Given that assumption, would my best approach here just to have a star for object and treat the object as the domain? So that a group/role can map to <permission, context> where context is "organizations/:id" as either the object or domain?

@PokIsemaine
Copy link
Member

@JoshSharpe I'm sorry for the delay in responding. This issue is quite complicated for me.
I cannot directly say whether this is a bug or intended behavior. I think your original idea was reasonable, but currently, the code implementation of g = _,_, _ does not directly consider the situation where the third parameter is not a domain. Although it may meet your expectations in some cases, doing so may cause other incompatible issues.

You can achieve the desired effect by changing the naming method or using a matching function, or you can try to replace your model configuration.

If you have a better method or suggestion, please share it with us as it would be very helpful.

@PokIsemaine
Copy link
Member

PokIsemaine commented May 8, 2023

Looking forward to your updates and wishing you good progress. I will also be paying attention to this part recently.

@JoshSharpe
Copy link
Author

@PokIsemaine i am going to spend some time this week or weekend sketching out different approaches and diving into the source code. I’m not familiar enough with it or the proper ways of doing rbac so it will be a good learning experience. But I will update this ticket as I go just so you know I’m still actively working on it. I might end up with a proposed change or simply update with my approach for others who see my use case to also replicate.

Copying to my correct account.

@JoshSharpe
Copy link
Author

Here's where I'm at now. Looking at model examples a little more, this one kind of works for my use case. But I think I'm still misunderstanding some concepts if you don't mind clearing things up.

Model:

[request_definition]
r = sub, dom, obj, act

[policy_definition]
p = sub, dom, obj, act

[role_definition]
g = _, _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub, r.dom) && keyMatch4(r.dom, p.dom) && r.obj == p.obj && regexMatch(r.act, p.act)

Policy

p, org:admin, *, organization, (create_workspace|delete_workspace)
p, org:admin, *, organization, (edit_workspace) 
p, org:user, *, organization, (view_users) 
p, wksp:admin, */*, workspace, create_project

g, org:admin, org:user, *
g, alice, org:admin, org1
g, bob, org:user, org2
g, alice, wksp:admin, org1/f32fg545

The thoughts here are that the roles are scoped to an object through the abbreviation:role. The domain is the id for the given object. And the action is a regex grouping of activities. Object is the "type" of domain.

This produces the enforcement that I'm seeking. However, I'm not sure what the best approach is for searching this data. Say I want to find the organizations that Alice is has access to. Right now I can GetDomainsForUser. However, that includes domains for all objects (types). Do I need to manually filter data or is there some option I am missing in documentation to help me here?

@hsluoyz
Copy link
Member

hsluoyz commented May 10, 2023

@PokIsemaine

@PokIsemaine
Copy link
Member

PokIsemaine commented May 11, 2023

@JoshSharpe
It seems like a good approach.
Do you expect e.GetDomainsForUser("alice") to only return org of type organization and not org1/f32fg545 of type workspace?
In fact, APIs like GetDomainsForUser don't do the actual operations like Enforce, which means that p-type policy and expressions in matchers in the model generally do not affect its results, and its results are basically determined by g-type policy.
image

You may consider doing further screening manually.

@JoshSharpe
Copy link
Author

@JoshSharpe It seems like a good approach. Do you expect e.GetDomainsForUser("alice") to only return org of type organization and not org1/f32fg545 of type workspace? In fact, APIs like GetDomainsForUser don't do the actual operations like Enforce, which means that p-type policy and expressions in matchers in the model generally do not affect its results, and its results are basically determined by g-type policy. image

You may consider doing further screening manually.

No I expect it to behave how it does. However, I am curious how people typically solve the following problems. Using my use-case as an example, how would one:

  • List all organizations that Alice belongs to?
  • List all users that are user or admins to a given organization?

@JoshSharpe
Copy link
Author

JoshSharpe commented May 15, 2023

I'm going to just write my own mongo queries to do this for now. And probably abandon roles to keep the queries simpler. But if anyone can help suggest a model and/or policy that fits and the appropriate casbin functions, here is a summary of what I am trying to do.

Alice has access to read org/A, which is an organization
Bob has access to admin org/B, which is an organization
Alice is part of role admin_c
admin_c has access to admin org/C, which is an organization
Bob has access to read org/C

I'd also like to be able to do queries on the policy:

  • Fetch all organizations for a user. Example, fetching organizations for Alice would return org/c and org/A
  • Fetch all users and their role for an organization. Example, fetching users for org/C returns Bob+read and Alice+admin.

The problems I've run into:

  • I'm only able to grant a role permission to a domain, not a resource. This means I can provide access to 'organization' but not the specific resources of 'organization/:id'. For g(a, b, c), c is not respected and is assumed to be a domain.
  • The functions appear to be limited. If I switch these, which is counter-intuititive to the purpose, it breaks casbin functions. I'm unable to query the data how I would expect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

4 participants