snapshot of https://github.com/casbin/casbin.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
Build / test (1.21) (push) Successful in 2m21s
Build / benchmark (1.21) (push) Failing after 2m28s
golangci-lint / golangci (push) Successful in 19m40s

This commit is contained in:
upstream-mirror
2026-09-15 21:46:19 +05:00
commit d8971e16fb
234 changed files with 28137 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 rbac
import "context"
// ContextRoleManager provides a context-aware interface to define the operations for managing roles.
// Prefer this over RoleManager interface for context propagation, which is useful for things like handling
// request timeouts.
type ContextRoleManager interface {
RoleManager
// ClearCtx clears all stored data and resets the role manager to the initial state with context.
ClearCtx(ctx context.Context) error
// AddLinkCtx adds the inheritance link between two roles. role: name1 and role: name2 with context.
// domain is a prefix to the roles (can be used for other purposes).
AddLinkCtx(ctx context.Context, name1 string, name2 string, domain ...string) error
// DeleteLinkCtx deletes the inheritance link between two roles. role: name1 and role: name2 with context.
// domain is a prefix to the roles (can be used for other purposes).
DeleteLinkCtx(ctx context.Context, name1 string, name2 string, domain ...string) error
// HasLinkCtx determines whether a link exists between two roles. role: name1 inherits role: name2 with context.
// domain is a prefix to the roles (can be used for other purposes).
HasLinkCtx(ctx context.Context, name1 string, name2 string, domain ...string) (bool, error)
// GetRolesCtx gets the roles that a user inherits with context.
// domain is a prefix to the roles (can be used for other purposes).
GetRolesCtx(ctx context.Context, name string, domain ...string) ([]string, error)
// GetUsersCtx gets the users that inherits a role with context.
// domain is a prefix to the users (can be used for other purposes).
GetUsersCtx(ctx context.Context, name string, domain ...string) ([]string, error)
// GetDomainsCtx gets domains that a user has with context.
GetDomainsCtx(ctx context.Context, name string) ([]string, error)
// GetAllDomainsCtx gets all domains with context.
GetAllDomainsCtx(ctx context.Context) ([]string, error)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,436 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 defaultrolemanager
import (
"fmt"
"sync"
"sync/atomic"
"testing"
"github.com/casbin/casbin/v3/rbac"
"github.com/casbin/casbin/v3/util"
)
func testRole(t *testing.T, rm rbac.RoleManager, name1 string, name2 string, res bool) {
t.Helper()
myRes, _ := rm.HasLink(name1, name2)
t.Logf("%s, %s: %t", name1, name2, myRes)
if myRes != res {
t.Errorf("%s < %s: %t, supposed to be %t", name1, name2, !res, res)
}
}
func testDomainRole(t *testing.T, rm rbac.RoleManager, name1 string, name2 string, domain string, res bool) {
t.Helper()
myRes, _ := rm.HasLink(name1, name2, domain)
t.Logf("%s :: %s, %s: %t", domain, name1, name2, myRes)
if myRes != res {
t.Errorf("%s :: %s < %s: %t, supposed to be %t", domain, name1, name2, !res, res)
}
}
func testPrintRoles(t *testing.T, rm rbac.RoleManager, name string, res []string) {
t.Helper()
myRes, _ := rm.GetRoles(name)
t.Logf("%s: %s", name, myRes)
if !util.SetEquals(myRes, res) {
t.Errorf("%s: %s, supposed to be %s", name, myRes, res)
}
}
func testPrintUsers(t *testing.T, rm rbac.RoleManager, name string, res []string) {
t.Helper()
myRes, _ := rm.GetUsers(name)
t.Logf("%s: %s", name, myRes)
if !util.SetEquals(myRes, res) {
t.Errorf("%s: %s, supposed to be %s", name, myRes, res)
}
}
func testPrintRolesWithDomain(t *testing.T, rm rbac.RoleManager, name string, domain string, res []string) {
t.Helper()
myRes, _ := rm.GetRoles(name, domain)
if !util.SetEquals(myRes, res) {
t.Errorf("%s: %s, supposed to be %s", name, myRes, res)
}
}
func TestRole(t *testing.T) {
rm := NewRoleManager(3)
_ = rm.AddLink("u1", "g1")
_ = rm.AddLink("u2", "g1")
_ = rm.AddLink("u3", "g2")
_ = rm.AddLink("u4", "g2")
_ = rm.AddLink("u4", "g3")
_ = rm.AddLink("g1", "g3")
// Current role inheritance tree:
// g3 g2
// / \ / \
// g1 u4 u3
// / \
// u1 u2
testRole(t, rm, "u1", "g1", true)
testRole(t, rm, "u1", "g2", false)
testRole(t, rm, "u1", "g3", true)
testRole(t, rm, "u2", "g1", true)
testRole(t, rm, "u2", "g2", false)
testRole(t, rm, "u2", "g3", true)
testRole(t, rm, "u3", "g1", false)
testRole(t, rm, "u3", "g2", true)
testRole(t, rm, "u3", "g3", false)
testRole(t, rm, "u4", "g1", false)
testRole(t, rm, "u4", "g2", true)
testRole(t, rm, "u4", "g3", true)
testPrintRoles(t, rm, "u1", []string{"g1"})
testPrintRoles(t, rm, "u2", []string{"g1"})
testPrintRoles(t, rm, "u3", []string{"g2"})
testPrintRoles(t, rm, "u4", []string{"g2", "g3"})
testPrintRoles(t, rm, "g1", []string{"g3"})
testPrintRoles(t, rm, "g2", []string{})
testPrintRoles(t, rm, "g3", []string{})
_ = rm.DeleteLink("g1", "g3")
_ = rm.DeleteLink("u4", "g2")
// Current role inheritance tree after deleting the links:
// g3 g2
// \ \
// g1 u4 u3
// / \
// u1 u2
testRole(t, rm, "u1", "g1", true)
testRole(t, rm, "u1", "g2", false)
testRole(t, rm, "u1", "g3", false)
testRole(t, rm, "u2", "g1", true)
testRole(t, rm, "u2", "g2", false)
testRole(t, rm, "u2", "g3", false)
testRole(t, rm, "u3", "g1", false)
testRole(t, rm, "u3", "g2", true)
testRole(t, rm, "u3", "g3", false)
testRole(t, rm, "u4", "g1", false)
testRole(t, rm, "u4", "g2", false)
testRole(t, rm, "u4", "g3", true)
testPrintRoles(t, rm, "u1", []string{"g1"})
testPrintRoles(t, rm, "u2", []string{"g1"})
testPrintRoles(t, rm, "u3", []string{"g2"})
testPrintRoles(t, rm, "u4", []string{"g3"})
testPrintRoles(t, rm, "g1", []string{})
testPrintRoles(t, rm, "g2", []string{})
testPrintRoles(t, rm, "g3", []string{})
rm = NewRoleManager(3)
rm.AddMatchingFunc("keyMatch", util.KeyMatch)
_ = rm.AddLink("u1", "g1")
_ = rm.AddLink("u1", "*")
_ = rm.AddLink("u2", "g2")
// Current role inheritance tree after deleting the links:
// g1 g2
// \ / \
// * u2
// |
// u1
testRole(t, rm, "u1", "g1", true)
testRole(t, rm, "u1", "g2", true)
testRole(t, rm, "u2", "g2", true)
testRole(t, rm, "u2", "g1", false)
testPrintRoles(t, rm, "u1", []string{"*", "u1", "u2", "g1", "g2"})
testPrintUsers(t, rm, "*", []string{"u1"})
}
func TestDomainRole(t *testing.T) {
rm := NewRoleManager(3)
_ = rm.AddLink("u1", "g1", "domain1")
_ = rm.AddLink("u2", "g1", "domain1")
_ = rm.AddLink("u3", "admin", "domain2")
_ = rm.AddLink("u4", "admin", "domain2")
_ = rm.AddLink("u4", "admin", "domain1")
_ = rm.AddLink("g1", "admin", "domain1")
// Current role inheritance tree:
// domain1:admin domain2:admin
// / \ / \
// domain1:g1 u4 u3
// / \
// u1 u2
testDomainRole(t, rm, "u1", "g1", "domain1", true)
testDomainRole(t, rm, "u1", "g1", "domain2", false)
testDomainRole(t, rm, "u1", "admin", "domain1", true)
testDomainRole(t, rm, "u1", "admin", "domain2", false)
testDomainRole(t, rm, "u2", "g1", "domain1", true)
testDomainRole(t, rm, "u2", "g1", "domain2", false)
testDomainRole(t, rm, "u2", "admin", "domain1", true)
testDomainRole(t, rm, "u2", "admin", "domain2", false)
testDomainRole(t, rm, "u3", "g1", "domain1", false)
testDomainRole(t, rm, "u3", "g1", "domain2", false)
testDomainRole(t, rm, "u3", "admin", "domain1", false)
testDomainRole(t, rm, "u3", "admin", "domain2", true)
testDomainRole(t, rm, "u4", "g1", "domain1", false)
testDomainRole(t, rm, "u4", "g1", "domain2", false)
testDomainRole(t, rm, "u4", "admin", "domain1", true)
testDomainRole(t, rm, "u4", "admin", "domain2", true)
_ = rm.DeleteLink("g1", "admin", "domain1")
_ = rm.DeleteLink("u4", "admin", "domain2")
// Current role inheritance tree after deleting the links:
// domain1:admin domain2:admin
// \ \
// domain1:g1 u4 u3
// / \
// u1 u2
testDomainRole(t, rm, "u1", "g1", "domain1", true)
testDomainRole(t, rm, "u1", "g1", "domain2", false)
testDomainRole(t, rm, "u1", "admin", "domain1", false)
testDomainRole(t, rm, "u1", "admin", "domain2", false)
testDomainRole(t, rm, "u2", "g1", "domain1", true)
testDomainRole(t, rm, "u2", "g1", "domain2", false)
testDomainRole(t, rm, "u2", "admin", "domain1", false)
testDomainRole(t, rm, "u2", "admin", "domain2", false)
testDomainRole(t, rm, "u3", "g1", "domain1", false)
testDomainRole(t, rm, "u3", "g1", "domain2", false)
testDomainRole(t, rm, "u3", "admin", "domain1", false)
testDomainRole(t, rm, "u3", "admin", "domain2", true)
testDomainRole(t, rm, "u4", "g1", "domain1", false)
testDomainRole(t, rm, "u4", "g1", "domain2", false)
testDomainRole(t, rm, "u4", "admin", "domain1", true)
testDomainRole(t, rm, "u4", "admin", "domain2", false)
}
func TestClear(t *testing.T) {
rm := NewRoleManager(3)
_ = rm.AddLink("u1", "g1")
_ = rm.AddLink("u2", "g1")
_ = rm.AddLink("u3", "g2")
_ = rm.AddLink("u4", "g2")
_ = rm.AddLink("u4", "g3")
_ = rm.AddLink("g1", "g3")
// Current role inheritance tree:
// g3 g2
// / \ / \
// g1 u4 u3
// / \
// u1 u2
_ = rm.Clear()
// All data is cleared.
// No role inheritance now.
testRole(t, rm, "u1", "g1", false)
testRole(t, rm, "u1", "g2", false)
testRole(t, rm, "u1", "g3", false)
testRole(t, rm, "u2", "g1", false)
testRole(t, rm, "u2", "g2", false)
testRole(t, rm, "u2", "g3", false)
testRole(t, rm, "u3", "g1", false)
testRole(t, rm, "u3", "g2", false)
testRole(t, rm, "u3", "g3", false)
testRole(t, rm, "u4", "g1", false)
testRole(t, rm, "u4", "g2", false)
testRole(t, rm, "u4", "g3", false)
}
func TestDomainPatternRole(t *testing.T) {
rm := NewRoleManager(10)
rm.AddDomainMatchingFunc("keyMatch2", util.KeyMatch2)
_ = rm.AddLink("u1", "g1", "domain1")
_ = rm.AddLink("u2", "g1", "domain2")
_ = rm.AddLink("u3", "g1", "*")
_ = rm.AddLink("u4", "g2", "domain3")
// Current role inheritance tree after deleting the links:
// domain1:g1 domain2:g1 domain3:g2
// / \ / \ |
// domain1:u1 *:g1 domain2:u2 domain3:u4
// |
// *:u3
testDomainRole(t, rm, "u1", "g1", "domain1", true)
testDomainRole(t, rm, "u2", "g1", "domain1", false)
testDomainRole(t, rm, "u2", "g1", "domain2", true)
testDomainRole(t, rm, "u3", "g1", "domain1", true)
testDomainRole(t, rm, "u3", "g1", "domain2", true)
testDomainRole(t, rm, "u1", "g2", "domain1", false)
testDomainRole(t, rm, "u4", "g2", "domain3", true)
testDomainRole(t, rm, "u3", "g2", "domain3", false)
testPrintRolesWithDomain(t, rm, "u3", "domain1", []string{"g1"})
testPrintRolesWithDomain(t, rm, "u1", "domain1", []string{"g1"})
testPrintRolesWithDomain(t, rm, "u3", "domain2", []string{"g1"})
testPrintRolesWithDomain(t, rm, "u1", "domain2", []string{})
testPrintRolesWithDomain(t, rm, "u4", "domain3", []string{"g2"})
}
func TestAllMatchingFunc(t *testing.T) {
rm := NewRoleManager(10)
rm.AddMatchingFunc("keyMatch2", util.KeyMatch2)
rm.AddDomainMatchingFunc("keyMatch2", util.KeyMatch2)
_ = rm.AddLink("/book/:id", "book_group", "*")
// Current role inheritance tree after deleting the links:
// *:book_group
// |
// *:/book/:id
testDomainRole(t, rm, "/book/1", "book_group", "domain1", true)
testDomainRole(t, rm, "/book/2", "book_group", "domain1", true)
}
func TestMatchingFuncOrder(t *testing.T) {
rm := NewRoleManager(10)
rm.AddMatchingFunc("regexMatch", util.RegexMatch)
_ = rm.AddLink("g\\d+", "root")
_ = rm.AddLink("u1", "g1")
testRole(t, rm, "u1", "root", true)
_ = rm.Clear()
_ = rm.AddLink("u1", "g1")
_ = rm.AddLink("g\\d+", "root")
testRole(t, rm, "u1", "root", true)
_ = rm.Clear()
_ = rm.AddLink("u1", "g\\d+")
testRole(t, rm, "u1", "g1", true)
testRole(t, rm, "u1", "g2", true)
}
func TestDomainMatchingFuncWithDifferentDomain(t *testing.T) {
rm := NewRoleManager(10)
rm.AddDomainMatchingFunc("keyMatch", util.KeyMatch)
_ = rm.AddLink("alice", "editor", "*")
_ = rm.AddLink("editor", "admin", "domain1")
testDomainRole(t, rm, "alice", "admin", "domain1", true)
testDomainRole(t, rm, "alice", "admin", "domain2", false)
}
func TestTemporaryRoles(t *testing.T) {
rm := NewRoleManager(10)
rm.AddMatchingFunc("regexMatch", util.RegexMatch)
_ = rm.AddLink("u\\d+", "user")
for i := 0; i < 10; i++ {
testRole(t, rm, fmt.Sprintf("u%d", i), "user", true)
}
testPrintUsers(t, rm, "user", []string{"u\\d+"})
testPrintRoles(t, rm, "u1", []string{"user"})
_ = rm.AddLink("u1", "manager")
for i := 10; i < 20; i++ {
testRole(t, rm, fmt.Sprintf("u%d", i), "user", true)
}
testPrintUsers(t, rm, "user", []string{"u\\d+", "u1"})
testPrintRoles(t, rm, "u1", []string{"user", "manager"})
}
func TestMaxHierarchyLevel(t *testing.T) {
rm := NewRoleManager(1)
_ = rm.AddLink("level0", "level1")
_ = rm.AddLink("level1", "level2")
_ = rm.AddLink("level2", "level3")
testRole(t, rm, "level0", "level0", true)
testRole(t, rm, "level0", "level1", true)
testRole(t, rm, "level0", "level2", false)
testRole(t, rm, "level0", "level3", false)
testRole(t, rm, "level1", "level2", true)
testRole(t, rm, "level1", "level3", false)
rm = NewRoleManager(2)
_ = rm.AddLink("level0", "level1")
_ = rm.AddLink("level1", "level2")
_ = rm.AddLink("level2", "level3")
testRole(t, rm, "level0", "level0", true)
testRole(t, rm, "level0", "level1", true)
testRole(t, rm, "level0", "level2", true)
testRole(t, rm, "level0", "level3", false)
testRole(t, rm, "level1", "level2", true)
testRole(t, rm, "level1", "level3", true)
}
// TestConcurrentHasLink tests concurrent HasLink calls for race conditions.
// This test verifies that concurrent HasLink calls with matching functions
// don't produce inconsistent results due to temporary role creation/deletion races.
// Regression test for issue #1318.
func TestConcurrentHasLink(t *testing.T) {
rm := NewRoleManager(10)
rm.AddMatchingFunc("keyMatch2", util.KeyMatch2)
_ = rm.AddLink("alice", "admin")
_ = rm.AddLink("admin", "/data/*")
expected, _ := rm.HasLink("alice", "/data/123")
const numGoroutines = 20
const numIterations = 100
var inconsistencies int64
var wg sync.WaitGroup
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < numIterations; j++ {
result, err := rm.HasLink("alice", "/data/123")
if err != nil {
t.Errorf("HasLink failed: %v", err)
return
} else if result != expected {
atomic.AddInt64(&inconsistencies, 1)
}
}
}()
}
wg.Wait()
if inconsistencies > 0 {
t.Errorf("Found %d inconsistencies in %d total operations",
inconsistencies, numGoroutines*numIterations)
}
}

83
rbac/role_manager.go Normal file
View File

@@ -0,0 +1,83 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 rbac
type MatchingFunc func(arg1 string, arg2 string) bool
type LinkConditionFunc = func(args ...string) (bool, error)
// RoleManager provides interface to define the operations for managing roles.
type RoleManager interface {
// Clear clears all stored data and resets the role manager to the initial state.
Clear() error
// AddLink adds the inheritance link between two roles. role: name1 and role: name2.
// domain is a prefix to the roles (can be used for other purposes).
AddLink(name1 string, name2 string, domain ...string) error
// Deprecated: BuildRelationship is no longer required
BuildRelationship(name1 string, name2 string, domain ...string) error
// DeleteLink deletes the inheritance link between two roles. role: name1 and role: name2.
// domain is a prefix to the roles (can be used for other purposes).
DeleteLink(name1 string, name2 string, domain ...string) error
// HasLink determines whether a link exists between two roles. role: name1 inherits role: name2.
// domain is a prefix to the roles (can be used for other purposes).
HasLink(name1 string, name2 string, domain ...string) (bool, error)
// GetRoles gets the roles that a user inherits.
// domain is a prefix to the roles (can be used for other purposes).
GetRoles(name string, domain ...string) ([]string, error)
// GetUsers gets the users that inherits a role.
// domain is a prefix to the users (can be used for other purposes).
GetUsers(name string, domain ...string) ([]string, error)
// GetImplicitRoles gets the implicit roles that a user inherits, respecting maxHierarchyLevel.
// domain is a prefix to the roles (can be used for other purposes).
GetImplicitRoles(name string, domain ...string) ([]string, error)
// GetImplicitUsers gets the implicit users that inherits a role, respecting maxHierarchyLevel.
// domain is a prefix to the users (can be used for other purposes).
GetImplicitUsers(name string, domain ...string) ([]string, error)
// GetDomains gets domains that a user has
GetDomains(name string) ([]string, error)
// GetAllDomains gets all domains
GetAllDomains() ([]string, error)
// PrintRoles prints all the roles to log.
PrintRoles() error
// Match matches the domain with the pattern
Match(str string, pattern string) bool
// AddMatchingFunc adds the matching function
AddMatchingFunc(name string, fn MatchingFunc)
// AddDomainMatchingFunc adds the domain matching function
AddDomainMatchingFunc(name string, fn MatchingFunc)
// DeleteDomain deletes all data of a domain in the role manager.
DeleteDomain(domain string) error
}
// ConditionalRoleManager provides interface to define the operations for managing roles.
// Link with conditions is supported.
type ConditionalRoleManager interface {
RoleManager
// AddLinkConditionFunc Add condition function fn for Link userName->roleName,
// when fn returns true, Link is valid, otherwise invalid
AddLinkConditionFunc(userName, roleName string, fn LinkConditionFunc)
// SetLinkConditionFuncParams Sets the parameters of the condition function fn for Link userName->roleName
SetLinkConditionFuncParams(userName, roleName string, params ...string)
// AddDomainLinkConditionFunc Add condition function fn for Link userName-> {roleName, domain},
// when fn returns true, Link is valid, otherwise invalid
AddDomainLinkConditionFunc(user string, role string, domain string, fn LinkConditionFunc)
// SetDomainLinkConditionFuncParams Sets the parameters of the condition function fn
// for Link userName->{roleName, domain}
SetDomainLinkConditionFuncParams(user string, role string, domain string, params ...string)
}