diff --git a/enforcer.go b/enforcer.go index 498680c07..f9bab13c5 100644 --- a/enforcer.go +++ b/enforcer.go @@ -496,6 +496,35 @@ func (e *Enforcer) SavePolicy() error { return nil } +// getDomainTokens extracts domain token names from request and policy definitions. +// Returns empty strings if tokens cannot be found. +func (e *Enforcer) getDomainTokens() (rDomainToken, pDomainToken string) { + if rAssertion, ok := e.model["r"]["r"]; ok && len(rAssertion.Tokens) > 1 { + rDomainToken = rAssertion.Tokens[1] + } + if pAssertion, ok := e.model["p"]["p"]; ok && len(pAssertion.Tokens) > 1 { + pDomainToken = pAssertion.Tokens[1] + } + return rDomainToken, pDomainToken +} + +// registerDomainMatchingFunc registers domain matching function if the matcher uses keyMatch for domains. +func (e *Enforcer) registerDomainMatchingFunc(ptype string) { + // Dynamically detect the domain token name from the model definition. + // In RBAC with domains, the domain is typically the second parameter (index 1) + // in both request and policy definitions (e.g., r = sub, dom, obj, act). + // We extract the actual token names to support arbitrary domain parameter names. + rDomainToken, pDomainToken := e.getDomainTokens() + if rDomainToken == "" || pDomainToken == "" { + return + } + + matchFun := fmt.Sprintf("keyMatch(%s, %s)", rDomainToken, pDomainToken) + if strings.Contains(e.model["m"]["m"].Value, matchFun) { + e.AddNamedDomainMatchingFunc(ptype, "g", util.KeyMatch) + } +} + func (e *Enforcer) initRmMap() { for ptype, assertion := range e.model["g"] { if rm, ok := e.rmMap[ptype]; ok { @@ -518,10 +547,7 @@ func (e *Enforcer) initRmMap() { assertion.CondRM = defaultrolemanager.NewConditionalDomainManager(10) e.condRmMap[ptype] = assertion.CondRM } - matchFun := "keyMatch(r_dom, p_dom)" - if strings.Contains(e.model["m"]["m"].Value, matchFun) { - e.AddNamedDomainMatchingFunc(ptype, "g", util.KeyMatch) - } + e.registerDomainMatchingFunc(ptype) } } } diff --git a/model_test.go b/model_test.go index 79126bcfc..be00f4d9c 100644 --- a/model_test.go +++ b/model_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/casbin/casbin/v3/log" + "github.com/casbin/casbin/v3/model" fileadapter "github.com/casbin/casbin/v3/persist/file-adapter" "github.com/casbin/casbin/v3/rbac" "github.com/casbin/casbin/v3/util" @@ -241,6 +242,87 @@ func TestRBACModelWithDomainsAtRuntimeMockAdapter(t *testing.T) { testDomainEnforce(t, e, "bob", "domain2", "data2", "read", false) } +func TestRBACModelWithDomainTokenRename(t *testing.T) { + // Test that renaming the domain token from "dom" to another name (e.g., "dom1") + // still works correctly. This is a regression test for the issue where the + // hardcoded "r_dom" and "p_dom" strings prevented proper domain matching. + + // Test with standard "dom" token + modelText1 := ` +[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) && keyMatch(r.dom, p.dom) && r.obj == p.obj && r.act == p.act +` + m1, _ := model.NewModelFromString(modelText1) + e1, _ := NewEnforcer(m1) + _, _ = e1.AddPolicy("admin", "domain1", "data1", "read") + _, _ = e1.AddGroupingPolicy("alice", "admin", "domain*") + + testDomainEnforce(t, e1, "alice", "domain1", "data1", "read", true) + testDomainEnforce(t, e1, "alice", "domain2", "data1", "read", false) + + // Test with renamed "dom1" token + modelText2 := ` +[request_definition] +r = sub, dom1, obj, act + +[policy_definition] +p = sub, dom1, obj, act + +[role_definition] +g = _, _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub, r.dom1) && keyMatch(r.dom1, p.dom1) && r.obj == p.obj && r.act == p.act +` + m2, _ := model.NewModelFromString(modelText2) + e2, _ := NewEnforcer(m2) + _, _ = e2.AddPolicy("admin", "domain1", "data1", "read") + _, _ = e2.AddGroupingPolicy("alice", "admin", "domain*") + + testDomainEnforce(t, e2, "alice", "domain1", "data1", "read", true) + testDomainEnforce(t, e2, "alice", "domain2", "data1", "read", false) + + // Test with renamed "tenant" token + modelText3 := ` +[request_definition] +r = sub, tenant, obj, act + +[policy_definition] +p = sub, tenant, obj, act + +[role_definition] +g = _, _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub, r.tenant) && keyMatch(r.tenant, p.tenant) && r.obj == p.obj && r.act == p.act +` + m3, _ := model.NewModelFromString(modelText3) + e3, _ := NewEnforcer(m3) + _, _ = e3.AddPolicy("admin", "domain1", "data1", "read") + _, _ = e3.AddGroupingPolicy("alice", "admin", "domain*") + + testDomainEnforce(t, e3, "alice", "domain1", "data1", "read", true) + testDomainEnforce(t, e3, "alice", "domain2", "data1", "read", false) +} + func TestRBACModelWithDeny(t *testing.T) { e, _ := NewEnforcer("examples/rbac_with_deny_model.conf", "examples/rbac_with_deny_policy.csv")