205 lines
7.3 KiB
Go
205 lines
7.3 KiB
Go
// 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")
|
|
}
|
|
}
|