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

feat: support domain hierarchical and GetNamedRoleManager #1040

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

tangyang9464
Copy link
Member

Signed-off-by: tangyang9464 tangyang9464@163.com

for: #1032 (comment)

@casbin-bot
Copy link
Member

@closetool @sagilio please review

@tangyang9464
Copy link
Member Author

tangyang9464 commented Jun 25, 2022

@shipovalovyuriy plz review. You can ues it like the example in the test code

func TestDomainHierarchical(t *testing.T) {
	e, _ := NewEnforcer("examples/rbac_with_domains_hierarchical_model.conf", "examples/rbac_with_domains_hierarchical_policy.csv")
	origin := e.GetNamedRoleManager("g").(*defaultrolemanager.RoleManager)
	dest := e.GetNamedRoleManager("g2").(*defaultrolemanager.RoleManager)
	origin.SetTenantManager(dest)

	testDomainEnforce(t, e, "alice", "dom2", "data", "read", true)
}

@zsh1995
Copy link
Contributor

zsh1995 commented Jun 30, 2022

Great job! Could you add SetNamedRoleManager method for semantic consistency?

@tangyang9464
Copy link
Member Author

Great job! Could you add SetNamedRoleManager method for semantic consistency?

done

@hsluoyz
Copy link
Member

hsluoyz commented Jul 3, 2022

@zsh1995

Copy link
Contributor

@Sefriol Sefriol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice that people are finally taking this into consideration. I am not a golang expert, but I gave my two cents about this pull request.

Reference issues:
#718
#872
#1032

@@ -557,6 +574,17 @@ func (dm *DomainManager) getRoleManager(domain string, store bool) *RoleManagerI
return true
})
}
if dm.tenantManager != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though I do not know much about golang, this feels little repetitive, don't you think?

I think some comments would also be warranted. These specific conditions should be explained, otherwise somebody would just scratch their head in the future when they try to figure out why these are done as they are.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did some refactoring

@@ -0,0 +1,6 @@
p, admin, dom3, data, read
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also little more complexity to the domain is warranted. At least one extra user to test out that they do not have access to domain1 if they only have access to domain2

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more tests

@@ -267,3 +268,12 @@ func TestGetAllDomains(t *testing.T) {

testGetAllDomains(t, e, []string{"domain1", "domain2"})
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think singular test case is not sufficient for such a complex feature. At least one which supports deep hierarchy should also be done. Like here:

#718 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More specifically: Multiple subjects, multiple objects, multiple domains

  1. Test cases for accessing data in an upper domain from a lower domain (Failure)
  2. Test cases for accessing data in a lower domain from a middle domain (Success)
  3. Test cases for accessing data in a sibling domain from another sibling (Failure)
  4. Test cases for accessing data in a sibling domain from parent (success)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more tests

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to implement this in casbin/node-casbin#380

I think it's not possible to define g2 in the format of child, parent. At least in nodeJS, it provided incorrect results, but maybe golang has a different RoleManager definition.

You can try to replicate the test case I did for nodeJS. I think it provides quite good coverage on all the possible use cases. Especially the 4 domain structure is essential.

I have tried to comment on why certain decisions were made in the pull request and a little bit about the thought behind the hierarchy model.

dest := e.GetNamedRoleManager("g2").(*defaultrolemanager.RoleManager)
origin.SetTenantManager(dest)

testDomainEnforce(t, e, "alice", "dom2", "data", "read", true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in terms of the nature of this test case, alice should be tested from domains 1, 2 and 3 since she has access to all of them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more tests

@@ -528,6 +535,16 @@ func (dm *DomainManager) rangeAffectedRoleManagers(domain string, fn func(rm *Ro
return true
})
}
if dm.tenantManager != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing about repetition as in my review.

@@ -449,6 +451,11 @@ func NewDomainManager(maxHierarchyLevel int) *DomainManager {
return dm
}

func (dm *DomainManager) SetTenantManager(tm rbac.RoleManager) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs documentation on how to use this. Though at the same time, shouldn't the hierarchy be enabled by default?

What is base definition for Named Grouping Policies? If you set up a grouping policy for domains shouldn't they be hierarchical by default? @hsluoyz

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand what you are saying. This PR is to use the RoleManager to manage the hierarchy of the domain

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking out loud and more broadly how hierarchy should be enabled for the DomainManager. Not something that is necessary to solve in this pull request.

@@ -438,6 +439,7 @@ type DomainManager struct {
domainMatchingFunc MatchingFunc
logger log.Logger
matchingFuncCache *util.SyncLRUCache
tenantManager rbac.RoleManager // Used to manage hierarchical relationships between domains, see
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

, see what? I think the naming should also be OKed by @hsluoyz, so that it's clear for the purpose.

hierarchyManager could also be a name option. Or something similar. Tenant feels little bit generic and might cause confusion. What's the definition of tenant in casbin?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename

@tangyang9464
Copy link
Member Author

@Sefriol Some changes have been made in response to your suggestion, please review again.

Copy link
Contributor

@Sefriol Sefriol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need somebody else to review this than just me 😀.

@@ -267,3 +268,12 @@ func TestGetAllDomains(t *testing.T) {

testGetAllDomains(t, e, []string{"domain1", "domain2"})
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to implement this in casbin/node-casbin#380

I think it's not possible to define g2 in the format of child, parent. At least in nodeJS, it provided incorrect results, but maybe golang has a different RoleManager definition.

You can try to replicate the test case I did for nodeJS. I think it provides quite good coverage on all the possible use cases. Especially the 4 domain structure is essential.

I have tried to comment on why certain decisions were made in the pull request and a little bit about the thought behind the hierarchy model.

@@ -449,6 +451,11 @@ func NewDomainManager(maxHierarchyLevel int) *DomainManager {
return dm
}

func (dm *DomainManager) SetTenantManager(tm rbac.RoleManager) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking out loud and more broadly how hierarchy should be enabled for the DomainManager. Not something that is necessary to solve in this pull request.

rbac/default-role-manager/role_manager.go Show resolved Hide resolved
rbac/default-role-manager/role_manager.go Show resolved Hide resolved
e, _ := NewEnforcer("examples/rbac_with_domains_hierarchical_model.conf", "examples/rbac_with_domains_hierarchical_policy.csv")
origin := e.GetNamedRoleManager("g").(*defaultrolemanager.RoleManager)
dest := e.GetNamedRoleManager("g2").(*defaultrolemanager.RoleManager)
origin.SetDomainHierarchyManager(dest)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a helper function, so that you can enable the hierarchy without digging up the RoleManager? Like EnableDomainHierarchy which would set DomainHierachy to g with g2 by default.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be good, but I don't have time to do it right now

@tangyang9464
Copy link
Member Author

@Sefriol

I think it's not possible to define g2 in the format of child, parent. At least in nodeJS, it provided incorrect results, but maybe golang has a different RoleManager definition.
You can try to replicate the test case I did for nodeJS. I think it provides quite good coverage on all the possible use cases. Especially the 4 domain structure is essential.
I have tried to comment on why certain decisions were made in the pull request and a little bit about the thought behind the hierarchy model.

Maybe you should use conf and csv files so that we can replicate each other's use cases

@Sefriol
Copy link
Contributor

Sefriol commented Aug 14, 2022

Maybe you should use conf and csv files so that we can replicate each other's use cases

@tangyang9464 Sure. I changed the implementation to use csv and conf files.

https://github.com/casbin/node-casbin/pull/380/files

@tangyang9464
Copy link
Member Author

tangyang9464 commented Aug 14, 2022

Maybe you should use conf and csv files so that we can replicate each other's use cases

@tangyang9464 Sure. I changed the implementation to use csv and conf files.

https://github.com/casbin/node-casbin/pull/380/files

@zsh1995 I tested all your use cases and all passed. Maybe we don't need to change g2 to g2(parent, child).
You may need to note that the definition of g2 in the model cannot be reversed: g2(p.dom, r.dom) instead of g2(r.dom, p.dom)

@tangyang9464
Copy link
Member Author

@zsh1995 At the same time, I want to discuss the difference between m = g(r.sub, p.sub, r.dom) && r.dom==p.dom && r.obj == p.obj && regexMatch(r.act, p.act) and m = g(r.sub, p.sub, r.dom) && g2(p.dom, r.dom) && r.obj == p.obj && regexMatch(r.act, p.act). The former means that users can be automatically assigned roles in multiple domains according to the domain hierarchy, but defining what permissions role has in each domain is still required. The latter is a role that automatically assigns multiple domains to the user according to the domain hierarchy, and the permissions of the role will also be automatically assigned according to the domain hierarchy. Please pay attention to the similarities and differences of the results of your test cases expect(e.enforceSync('alice', 'domain1', 'data2', 'read')).toBe(true) under the two models

@Sefriol
Copy link
Contributor

Sefriol commented Aug 14, 2022

Yeah I am not sure what kind of exact solution we should be looking for. My intent with following policy lines:

p, admin, domain1, data1, (read|write)
p, admin, domain2, data2, (read|write)
p, admin, domain3, data3, (write)
p, admin, sibling2, sdata2, (read|write)

g, alice, admin, domain1
g, bob, admin, domain2
g2, domain1, domain2
g2, domain1, sibling2
g2, domain2, domain3

is to create domain structure like

domain1
├── domain2
│   └── domain3
└── sibling2

Which would result that Alice is an admin in all domains and Bob only in domains 2 and 3 with as few g definitions as possible.

Key things I am trying to achieve here:

  1. Each domain can have their custom permission table and can define their roles as they want to.
  2. Easy role addition / removal from an user

Policy testing should include that:

  1. Bob has no rights to domain1, sibling2´, but does for domain2anddomain3`
  2. Alice has access to all domains

Checks like expect(e.enforceSync('alice', 'domain1', 'data2', 'read')).toBe(true) do not matter too much. Personally just expect(e.enforceSync('alice', 'domain2, 'data2', 'read')).toBe(true) is good enough.

I guess more important factor in this case is performance. In a case of not knowing which domain object dataX belongs to, is it faster to check all domains or just use reversed g2 model instead? Sometimes we are interested whether subject has access to an object and not if subject has access to an object in specific domain.

I derived my implementation from a model used in our production, so using hierarchy would not require us to do any conversation work.

I'll add your model too to node-casbin test cases to see that it works as well. I might need to add some performance testing as well to see how much it takes time in each model.

@Sefriol
Copy link
Contributor

Sefriol commented Aug 16, 2022

For some reason these tests in node-casbin only work when HasLink inputs are implemented in reverse order compared to this (this.domainHierarchyManager.syncedHasLink(domain, domain2) vs dm.domainHierarchyManager.HasLink(domain2, domain)). But maybe I just cannot read the golang code correctly.

@tangyang9464 Can you send me the results of golang HasLink function per domain from your example? Like domain, domain2, hasLinkResult. I need to investigate whether the function works correctly.

Signed-off-by: tangyang9464 <tangyang9464@163.com>
@Sefriol
Copy link
Contributor

Sefriol commented Sep 4, 2022

@nodece Commented on this in the node-casbin pull request. casbin/node-casbin#380 (comment)

In general I am in favor of implementing this directly to the model definition.

@tangyang9464
Copy link
Member Author

@nodece Commented on this in the node-casbin pull request. casbin/node-casbin#380 (comment)

In general I am in favor of implementing this directly to the model definition.

@Sefriol Agree, modify later

@Sefriol
Copy link
Contributor

Sefriol commented Oct 25, 2022

@tangyang9464 Have you heard any updates on the issue? Is there currently a plan on how this could be implemented?

@hsluoyz Pinging you as well if there is anything to update :)

@hsluoyz
Copy link
Member

hsluoyz commented Oct 25, 2022

@tangyang9464 @imp2002

@sujit-baniya
Copy link

@tangyang9464 on latest casbin code, following test cases are not working.

	testDomainEnforce(t, e, "alice", "domain1", "data2", "read", true)  // Failed
	testDomainEnforce(t, e, "alice", "domain1", "sdata2", "read", true) // Failed
	testDomainEnforce(t, e, "alice", "domain1", "data3", "write", true) // Failed
	testDomainEnforce(t, e, "alice", "domain2", "data3", "write", true) // Failed
	testDomainEnforce(t, e, "bob", "domain2", "data3", "write", true)   // Failed

@Sefriol
Copy link
Contributor

Sefriol commented Dec 4, 2022

@sujit-baniya Current code is unlikely to be accepted anyway since there was more preferred idea to support this directly in the model definition.

@sujit-baniya
Copy link

@Sefriol Can you please suggest preferred ways to implement via model definition?

@nodece
Copy link
Member

nodece commented Dec 5, 2022

@Sefriol Can you please suggest preferred ways to implement via model definition?

I suggest we should do so to avoid diversification.

@Sefriol
Copy link
Contributor

Sefriol commented Dec 5, 2022

@Sefriol Can you please suggest preferred ways to implement via model definition?

I think Casbin Team should decide what kind of approach we should be going into. They have a better idea than I do on what needs to be taken into account. I can be part of the implementation on the NodeJS side, but

@Sefriol
Copy link
Contributor

Sefriol commented Dec 27, 2022

@hsluoyz @nodece Any progress on the design front? This seems to be quite a popular feature request among users.

@Sefriol
Copy link
Contributor

Sefriol commented Jan 16, 2023

@nodece @tangyang9464 @hsluoyz Have you had a discussion about this? NodeJS version of this was kinda just merged with unsolved questions.

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

Successfully merging this pull request may close these issues.

None yet

7 participants