Skip to content

Commit 6cfc463

Browse files
committed
feat: integrate default detector into Enforcer (#1665)
1 parent f1f03cc commit 6cfc463

File tree

4 files changed

+157
-2
lines changed

4 files changed

+157
-2
lines changed

detector/default_detector.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,15 @@ func (d *DefaultDetector) Check(rm rbac.RoleManager) error {
6868

6969
// buildGraph builds an adjacency list representation of the role inheritance graph.
7070
// It uses the Range method (via type assertion) to iterate through all role links.
71-
func (d *DefaultDetector) buildGraph(rm rbac.RoleManager) (map[string][]string, error) {
72-
graph := make(map[string][]string)
71+
func (d *DefaultDetector) buildGraph(rm rbac.RoleManager) (graph map[string][]string, err error) {
72+
graph = make(map[string][]string)
73+
74+
// Recover from any panics during Range iteration (e.g., nil pointer dereferences)
75+
defer func() {
76+
if r := recover(); r != nil {
77+
err = fmt.Errorf("RoleManager is not properly initialized: %v", r)
78+
}
79+
}()
7380

7481
// Try to cast to a RoleManager implementation that supports Range
7582
// This works with RoleManagerImpl and similar implementations

enforcer.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"strings"
2222
"sync"
2323

24+
"github.com/casbin/casbin/v3/detector"
2425
"github.com/casbin/casbin/v3/effector"
2526
"github.com/casbin/casbin/v3/log"
2627
"github.com/casbin/casbin/v3/model"
@@ -47,6 +48,7 @@ type Enforcer struct {
4748
condRmMap map[string]rbac.ConditionalRoleManager
4849
matcherMap sync.Map
4950
logger log.Logger
51+
detectors []detector.Detector
5052

5153
enabled bool
5254
autoSave bool
@@ -190,6 +192,11 @@ func (e *Enforcer) initialize() {
190192
e.autoNotifyWatcher = true
191193
e.autoNotifyDispatcher = true
192194
e.initRmMap()
195+
196+
// Initialize detectors with default detector if not already set
197+
if e.detectors == nil {
198+
e.detectors = []detector.Detector{detector.NewDefaultDetector()}
199+
}
193200
}
194201

195202
// LoadModel reloads the model from the model CONF file.
@@ -288,6 +295,57 @@ func (e *Enforcer) SetLogger(logger log.Logger) {
288295
e.logger = logger
289296
}
290297

298+
// SetDetector sets a single detector for the enforcer.
299+
func (e *Enforcer) SetDetector(d detector.Detector) {
300+
e.detectors = []detector.Detector{d}
301+
}
302+
303+
// SetDetectors sets multiple detectors for the enforcer.
304+
func (e *Enforcer) SetDetectors(detectors []detector.Detector) {
305+
e.detectors = detectors
306+
}
307+
308+
// RunDetections runs all detectors on all role managers.
309+
// Returns the first error encountered, or nil if all checks pass.
310+
// Silently skips role managers that don't support the required iteration methods.
311+
func (e *Enforcer) RunDetections() error {
312+
if e.detectors == nil || len(e.detectors) == 0 {
313+
return nil
314+
}
315+
316+
// Run detectors on all role managers
317+
for _, rm := range e.rmMap {
318+
for _, d := range e.detectors {
319+
err := d.Check(rm)
320+
// Skip if the role manager doesn't support the required iteration or is not initialized
321+
if err != nil && (strings.Contains(err.Error(), "does not support Range iteration") ||
322+
strings.Contains(err.Error(), "not properly initialized")) {
323+
continue
324+
}
325+
if err != nil {
326+
return err
327+
}
328+
}
329+
}
330+
331+
// Run detectors on all conditional role managers
332+
for _, crm := range e.condRmMap {
333+
for _, d := range e.detectors {
334+
err := d.Check(crm)
335+
// Skip if the role manager doesn't support the required iteration or is not initialized
336+
if err != nil && (strings.Contains(err.Error(), "does not support Range iteration") ||
337+
strings.Contains(err.Error(), "not properly initialized")) {
338+
continue
339+
}
340+
if err != nil {
341+
return err
342+
}
343+
}
344+
}
345+
346+
return nil
347+
}
348+
291349
// ClearPolicy clears all policy.
292350
func (e *Enforcer) ClearPolicy() {
293351
e.invalidateMatcherMap()
@@ -316,6 +374,12 @@ func (e *Enforcer) LoadPolicy() error {
316374

317375
e.onLogAfterEventInLoadPolicy(logEntry, newModel)
318376

377+
// Run detectors after all policy rules are loaded
378+
err = e.RunDetections()
379+
if err != nil {
380+
return err
381+
}
382+
319383
return nil
320384
}
321385

enforcer_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
package casbin
1616

1717
import (
18+
"strings"
1819
"sync"
1920
"testing"
2021

22+
"github.com/casbin/casbin/v3/detector"
2123
"github.com/casbin/casbin/v3/model"
2224
fileadapter "github.com/casbin/casbin/v3/persist/file-adapter"
2325
"github.com/casbin/casbin/v3/util"
@@ -724,3 +726,78 @@ func TestLinkConditionFunc(t *testing.T) {
724726
testDomainEnforce(t, e, "alice", "domain5", "data5", "read", false)
725727
testDomainEnforce(t, e, "alice", "domain5", "data5", "write", false)
726728
}
729+
730+
func TestEnforcerWithDefaultDetector(t *testing.T) {
731+
// Test that default detector is enabled and detects cycles
732+
_, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_with_cycle_policy.csv")
733+
734+
// Expect an error because the policy contains a cycle
735+
if err == nil {
736+
t.Error("Expected cycle detection error when loading policy with cycle, but got nil")
737+
} else {
738+
errMsg := err.Error()
739+
if !strings.Contains(errMsg, "cycle detected") {
740+
t.Errorf("Expected error message to contain 'cycle detected', got: %s", errMsg)
741+
}
742+
}
743+
}
744+
745+
func TestEnforcerRunDetections(t *testing.T) {
746+
// Test explicit RunDetections() call
747+
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
748+
749+
// Should not error on valid policy
750+
err := e.RunDetections()
751+
if err != nil {
752+
t.Errorf("Expected no error when running detections on valid policy, but got: %v", err)
753+
}
754+
755+
// Now add a cycle manually
756+
_, _ = e.AddGroupingPolicy("alice", "data2_admin")
757+
_, _ = e.AddGroupingPolicy("data2_admin", "super_admin")
758+
_, _ = e.AddGroupingPolicy("super_admin", "alice")
759+
760+
// Should detect the cycle
761+
err = e.RunDetections()
762+
if err == nil {
763+
t.Error("Expected cycle detection error, but got nil")
764+
} else {
765+
errMsg := err.Error()
766+
if !strings.Contains(errMsg, "cycle detected") {
767+
t.Errorf("Expected error message to contain 'cycle detected', got: %s", errMsg)
768+
}
769+
}
770+
}
771+
772+
func TestEnforcerSetDetector(t *testing.T) {
773+
// Test SetDetector() method
774+
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
775+
776+
// Create a custom detector
777+
customDetector := detector.NewDefaultDetector()
778+
e.SetDetector(customDetector)
779+
780+
// Should still work with custom detector
781+
err := e.RunDetections()
782+
if err != nil {
783+
t.Errorf("Expected no error with custom detector, but got: %v", err)
784+
}
785+
}
786+
787+
func TestEnforcerSetDetectors(t *testing.T) {
788+
// Test SetDetectors() method
789+
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
790+
791+
// Create multiple detectors
792+
detectors := []detector.Detector{
793+
detector.NewDefaultDetector(),
794+
detector.NewDefaultDetector(),
795+
}
796+
e.SetDetectors(detectors)
797+
798+
// Should work with multiple detectors
799+
err := e.RunDetections()
800+
if err != nil {
801+
t.Errorf("Expected no error with multiple detectors, but got: %v", err)
802+
}
803+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
p, alice, data1, read
2+
p, bob, data2, write
3+
p, data2_admin, data2, read
4+
p, data2_admin, data2, write
5+
g, alice, data2_admin
6+
g, data2_admin, super_admin
7+
g, super_admin, alice

0 commit comments

Comments
 (0)