snapshot of https://github.com/casbin/casbin.git (master, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
204
enforcer_cached_gfunction_test.go
Normal file
204
enforcer_cached_gfunction_test.go
Normal file
@@ -0,0 +1,204 @@
|
||||
// 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 casbin
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestCachedGFunctionAfterAddingGroupingPolicy tests that adding new grouping policies
|
||||
// at runtime correctly updates permission evaluation without requiring a restart or manual cache clearing.
|
||||
// This is a regression test for the issue where GenerateGFunction's memoization cache
|
||||
// caused stale permission results after adding new grouping policies.
|
||||
func TestCachedGFunctionAfterAddingGroupingPolicy(t *testing.T) {
|
||||
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create enforcer: %v", err)
|
||||
}
|
||||
|
||||
// Initial state: bob should not have access to data2 (read)
|
||||
// bob has no roles initially
|
||||
ok, err := e.Enforce("bob", "data2", "read")
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce failed: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Error("bob should not have read access to data2 initially")
|
||||
}
|
||||
|
||||
// Add a new grouping policy: bob becomes data2_admin
|
||||
// data2_admin has read and write access to data2
|
||||
_, err = e.AddGroupingPolicy("bob", "data2_admin")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add grouping policy: %v", err)
|
||||
}
|
||||
|
||||
// Now bob should have read access to data2 through the data2_admin role
|
||||
// This should work immediately without needing to clear cache or restart
|
||||
ok, err = e.Enforce("bob", "data2", "read")
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce failed after adding grouping policy: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Error("bob should have read access to data2 after being added to data2_admin role")
|
||||
}
|
||||
|
||||
// Also verify write access works
|
||||
ok, err = e.Enforce("bob", "data2", "write")
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce failed: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Error("bob should have write access to data2 after being added to data2_admin role")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCachedGFunctionWithMultipleEnforceCalls tests that the g() function cache
|
||||
// properly invalidates when grouping policies change, even after multiple enforce calls.
|
||||
func TestCachedGFunctionWithMultipleEnforceCalls(t *testing.T) {
|
||||
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create enforcer: %v", err)
|
||||
}
|
||||
|
||||
// Make multiple enforce calls to ensure the g() function closure is cached
|
||||
for i := 0; i < 5; i++ {
|
||||
ok, enforceErr := e.Enforce("charlie", "data2", "read")
|
||||
if enforceErr != nil {
|
||||
t.Fatalf("Enforce failed on iteration %d: %v", i, enforceErr)
|
||||
}
|
||||
if ok {
|
||||
t.Errorf("charlie should not have read access to data2 on iteration %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
// Add grouping policy
|
||||
_, err = e.AddGroupingPolicy("charlie", "data2_admin")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add grouping policy: %v", err)
|
||||
}
|
||||
|
||||
// Immediately verify the change took effect
|
||||
ok, err := e.Enforce("charlie", "data2", "read")
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce failed after adding grouping policy: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Error("charlie should have read access to data2 immediately after being added to data2_admin role")
|
||||
}
|
||||
|
||||
// Make multiple calls to ensure it stays consistent
|
||||
for i := 0; i < 5; i++ {
|
||||
ok, enforceErr := e.Enforce("charlie", "data2", "read")
|
||||
if enforceErr != nil {
|
||||
t.Fatalf("Enforce failed on iteration %d after policy change: %v", i, enforceErr)
|
||||
}
|
||||
if !ok {
|
||||
t.Errorf("charlie should have read access to data2 on iteration %d after policy change", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCachedGFunctionAfterRemovingGroupingPolicy tests that removing grouping policies
|
||||
// also properly invalidates the g() function cache.
|
||||
func TestCachedGFunctionAfterRemovingGroupingPolicy(t *testing.T) {
|
||||
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create enforcer: %v", err)
|
||||
}
|
||||
|
||||
// alice initially has the data2_admin role
|
||||
ok, err := e.Enforce("alice", "data2", "read")
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce failed: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Error("alice should have read access to data2 initially")
|
||||
}
|
||||
|
||||
// Remove alice from data2_admin role
|
||||
_, err = e.RemoveGroupingPolicy("alice", "data2_admin")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to remove grouping policy: %v", err)
|
||||
}
|
||||
|
||||
// Now alice should not have access to data2 (she only has access to data1)
|
||||
ok, err = e.Enforce("alice", "data2", "read")
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce failed after removing grouping policy: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Error("alice should not have read access to data2 after being removed from data2_admin role")
|
||||
}
|
||||
|
||||
// Verify alice still has access to data1 (direct policy)
|
||||
ok, err = e.Enforce("alice", "data1", "read")
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce failed: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Error("alice should still have read access to data1 (direct policy)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCachedGFunctionAfterBuildRoleLinks tests the specific scenario mentioned in the bug report:
|
||||
// adding grouping policies and calling BuildRoleLinks() manually should properly invalidate the cache.
|
||||
func TestCachedGFunctionAfterBuildRoleLinks(t *testing.T) {
|
||||
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create enforcer: %v", err)
|
||||
}
|
||||
|
||||
// First, make some enforce calls to ensure the g() function closure is created and cached
|
||||
// This will cache "bob" NOT having data2_admin role in the g() function's sync.Map
|
||||
for i := 0; i < 3; i++ {
|
||||
ok, enforceErr := e.Enforce("bob", "data2", "read")
|
||||
if enforceErr != nil {
|
||||
t.Fatalf("Enforce failed on iteration %d: %v", i, enforceErr)
|
||||
}
|
||||
if ok {
|
||||
t.Errorf("bob should not have read access to data2 on iteration %d (before adding role)", i)
|
||||
}
|
||||
}
|
||||
|
||||
// Disable autoBuildRoleLinks to manually control when role links are rebuilt
|
||||
e.EnableAutoBuildRoleLinks(false)
|
||||
|
||||
// Manually add the grouping policy to the model (bypassing BuildIncrementalRoleLinks)
|
||||
// This simulates the scenario where policies are loaded from database
|
||||
err = e.model.AddPolicy("g", "g", []string{"bob", "data2_admin"})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add grouping policy to model: %v", err)
|
||||
}
|
||||
|
||||
// Manually build role links as mentioned in the issue
|
||||
// This is the key part - BuildRoleLinks() should invalidate the matcher map cache
|
||||
err = e.BuildRoleLinks()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build role links: %v", err)
|
||||
}
|
||||
|
||||
// Now bob should have read access to data2 through the data2_admin role
|
||||
// This is where the bug would manifest - if BuildRoleLinks() doesn't invalidate the cache,
|
||||
// the old g() function closure with "bob->data2_admin = false" cached will still be used
|
||||
ok, err := e.Enforce("bob", "data2", "read")
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce failed after BuildRoleLinks: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Error("bob should have read access to data2 after BuildRoleLinks() - this indicates the g() function cache was not properly invalidated")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user