Repository: casbin/casbin
Pull request #1305 (2023-08-25 07:40:13)

feat: add ContextAdapter interface to support context in adapters
fix: https://github.com/casbin/casbin/issues/1302

casbin-bot:  @tangyang9464 @JalinWang please review

Patch:
diff --git a/enforcer.go b/enforcer.go
index 9f9e550..9caed72 100644
--- a/enforcer.go
+++ b/enforcer.go
@@ -472,16 +472,20 @@ func (e *Enforcer) initRmMap() {
 			continue
 		}
 		if len(assertion.Tokens) <= 2 && len(assertion.ParamsTokens) == 0 {
-			e.rmMap[ptype] = defaultrolemanager.NewRoleManagerImpl(10)
+			assertion.RM = defaultrolemanager.NewRoleManagerImpl(10)
+			e.rmMap[ptype] = assertion.RM
 		}
 		if len(assertion.Tokens) <= 2 && len(assertion.ParamsTokens) != 0 {
-			e.condRmMap[ptype] = defaultrolemanager.NewConditionalRoleManager(10)
+			assertion.CondRM = defaultrolemanager.NewConditionalRoleManager(10)
+			e.condRmMap[ptype] = assertion.CondRM
 		}
 		if len(assertion.Tokens) > 2 {
 			if len(assertion.ParamsTokens) == 0 {
-				e.rmMap[ptype] = defaultrolemanager.NewRoleManager(10)
+				assertion.RM = defaultrolemanager.NewRoleManager(10)
+				e.rmMap[ptype] = assertion.RM
 			} else {
-				e.condRmMap[ptype] = defaultrolemanager.NewConditionalDomainManager(10)
+				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) {
@@ -584,10 +588,13 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 	functions := e.fm.GetFunctions()
 	if _, ok := e.model["g"]; ok {
 		for key, ast := range e.model["g"] {
-			if len(e.rmMap) != 0 {
+			// g must be a normal role definition (ast.RM != nil)
+			//   or a conditional role definition (ast.CondRM != nil)
+			// ast.RM and ast.CondRM shouldn't be nil at the same time
+			if ast.RM != nil {
 				functions[key] = util.GenerateGFunction(ast.RM)
 			}
-			if len(e.condRmMap) != 0 {
+			if ast.CondRM != nil {
 				functions[key] = util.GenerateConditionalGFunction(ast.CondRM)
 			}
 		}
diff --git a/examples/rbac_with_different_types_of_roles_model.conf b/examples/rbac_with_different_types_of_roles_model.conf
new file mode 100644
index 0000000..069f234
--- /dev/null
+++ b/examples/rbac_with_different_types_of_roles_model.conf
@@ -0,0 +1,15 @@
+[request_definition]
+r = sub, obj, act
+
+[policy_definition]
+p = sub, dom, obj, act
+
+[role_definition]
+g = _, _, _, (_, _)
+g2 = _, _
+
+[policy_effect]
+e = some(where (p.eft == allow))
+
+[matchers]
+m = g(r.sub, p.sub, p.dom) && g2(r.obj, p.dom) && regexMatch(r.act, p.act)
diff --git a/examples/rbac_with_different_types_of_roles_policy.csv b/examples/rbac_with_different_types_of_roles_policy.csv
new file mode 100644
index 0000000..67d090b
--- /dev/null
+++ b/examples/rbac_with_different_types_of_roles_policy.csv
@@ -0,0 +1,12 @@
+p, role:owner, domain1, _, (read|write)
+p, role:developer, domain1, _, read
+
+p, role:owner, domain2, _, (read|write)
+p, role:developer, domain2, _, read
+
+g, alice, role:owner, domain1, _, _
+g, bob, role:developer, domain2, _, 9999-12-30 00:00:00
+g, carol, role:owner, domain2, _, 0000-01-02 00:00:00
+
+g2, data1, domain1
+g2, data2, domain2
diff --git a/model/policy.go b/model/policy.go
index 23b16e9..48dcb13 100644
--- a/model/policy.go
+++ b/model/policy.go
@@ -47,10 +47,11 @@ func (model Model) BuildIncrementalRoleLinks(rmMap map[string]rbac.RoleManager,
 func (model Model) BuildRoleLinks(rmMap map[string]rbac.RoleManager) error {
 	model.PrintPolicy()
 	for ptype, ast := range model["g"] {
-		rm := rmMap[ptype]
-		err := ast.buildRoleLinks(rm)
-		if err != nil {
-			return err
+		if rm := rmMap[ptype]; rm != nil {
+			err := ast.buildRoleLinks(rm)
+			if err != nil {
+				return err
+			}
 		}
 	}
 
@@ -69,10 +70,11 @@ func (model Model) BuildIncrementalConditionalRoleLinks(condRmMap map[string]rba
 func (model Model) BuildConditionalRoleLinks(condRmMap map[string]rbac.ConditionalRoleManager) error {
 	model.PrintPolicy()
 	for ptype, ast := range model["g"] {
-		condRm := condRmMap[ptype]
-		err := ast.buildConditionalRoleLinks(condRm)
-		if err != nil {
-			return err
+		if condRm := condRmMap[ptype]; condRm != nil {
+			err := ast.buildConditionalRoleLinks(condRm)
+			if err != nil {
+				return err
+			}
 		}
 	}
 
diff --git a/persist/adapter_context.go b/persist/adapter_context.go
new file mode 100644
index 0000000..8e54ad0
--- /dev/null
+++ b/persist/adapter_context.go
@@ -0,0 +1,41 @@
+// Copyright 2023 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package persist
+
+import (
+	"context"
+
+	"github.com/casbin/casbin/v2/model"
+)
+
+// ContextAdapter provides a context-aware interface for Casbin adapters.
+type ContextAdapter interface {
+	Adapter
+
+	// LoadPolicyCtx loads all policy rules from the storage with context.
+	LoadPolicyCtx(ctx context.Context, model model.Model) error
+	// SavePolicyCtx saves all policy rules to the storage with context.
+	SavePolicyCtx(ctx context.Context, model model.Model) error
+
+	// AddPolicyCtx adds a policy rule to the storage with context.
+	// This is part of the Auto-Save feature.
+	AddPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error
+	// RemovePolicyCtx removes a policy rule from the storage with context.
+	// This is part of the Auto-Save feature.
+	RemovePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error
+	// RemoveFilteredPolicyCtx removes policy rules that match the filter from the storage with context.
+	// This is part of the Auto-Save feature.
+	RemoveFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues ...string) error
+}


Test patch:
diff --git a/model_test.go b/model_test.go
index be31d5f..b3ff964 100644
--- a/model_test.go
+++ b/model_test.go
@@ -330,6 +330,31 @@ func TestRBACModelWithPattern(t *testing.T) {
 	testEnforce(t, e, "bob", "/pen2/2", "GET", true)
 }
 
+func TestRBACModelWithDifferentTypesOfRoles(t *testing.T) {
+	e, _ := NewEnforcer("examples/rbac_with_different_types_of_roles_model.conf", "examples/rbac_with_different_types_of_roles_policy.csv")
+
+	g := e.GetNamedGroupingPolicy("g")
+	for _, gp := range g {
+		if len(gp) != 5 {
+			t.Error("g parameters' num isn't 5")
+			return
+		}
+		e.AddNamedDomainLinkConditionFunc("g", gp[0], gp[1], gp[2], util.TimeMatchFunc)
+	}
+	testEnforce(t, e, "alice", "data1", "read", true)
+	testEnforce(t, e, "alice", "data1", "write", true)
+	testEnforce(t, e, "alice", "data2", "read", false)
+	testEnforce(t, e, "alice", "data2", "write", false)
+	testEnforce(t, e, "bob", "data1", "read", false)
+	testEnforce(t, e, "bob", "data1", "write", false)
+	testEnforce(t, e, "bob", "data2", "read", true)
+	testEnforce(t, e, "bob", "data2", "write", false)
+	testEnforce(t, e, "carol", "data1", "read", false)
+	testEnforce(t, e, "carol", "data1", "write", false)
+	testEnforce(t, e, "carol", "data2", "read", false)
+	testEnforce(t, e, "carol", "data2", "write", false)
+}
+
 type testCustomRoleManager struct{}
 
 func NewRoleManager() rbac.RoleManager {