blob: 7d80b51a64319d900368f343b86227e23d0f18b2 [file] [log] [blame]
// Copyright 2017 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 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")
}
}