casbin / Casbin.NET

An authorization library that supports access control models like ACL, RBAC, ABAC in .NET (C#)
https://casbin.org
Apache License 2.0
1.13k stars 110 forks source link

InvalidOperationException when Enforcing #301

Closed herkit closed 1 year ago

herkit commented 1 year ago

We've created a rbac as a service api that registers the enforcer and model as a singleton in our DI container. The policy model is populated incrementally by a worker service that is triggering on changes in our backend database, we're not using an Adapter or a Watcher to populate model. Our issue seems to occur intermittently. We'll suddenly start getting InvalidOperationExceptions on Enforce.

We've seen a lot of InvalidOperationExceptions on enforce lately. After upgrading from 1.4. to 1.12. it became extremely frequent, then we upgraded to 2.0.0.-preview.4 and it seemed ok, but now it has started failing again.

Our policy definition:

[request_definition]
r = sub, aud, obj, act

[policy_definition]
p = sub, aud, obj, act

[role_definition]
g = _, _
g2 = _, _
g3 = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = (p.aud == ""*"" || r.aud == p.aud) && (r.sub == p.sub || g(r.sub, p.sub)) && (p.obj == ""*"" || r.obj == p.obj || g2(r.obj, p.obj)) && (r.act == p.act || g3(r.act, p.act))

Code that updates our model (abbreviated):

public class PolicyLoader {
        private readonly IEnforcer enforcer;

        public PolicyLoader(IEnforcer enforcer)
        {
            this.enforcer = enforcer;
        }

        public void ApplyUpdates(IPolicyProducer policyProducer, bool isUpdate = false)
        {
            if (isUpdate)
            { 
                foreach (var filter in policyProducer.GetPolicyRemoveFilters())
                {
                    var section = filter.PType.Substring(0, 1);
                    var policyType = filter.PType;

                    if (section == "g")
                        enforcer.RemoveFilteredNamedGroupingPolicy(policyType, filter.FieldIndex, filter.Filter);
                    else
                        enforcer.RemoveFilteredNamedPolicy(policyType, filter.FieldIndex, filter.Filter);
                }
            }

            foreach (var policyGroup in policyProducer.GetPolicies().GroupBy(p => p.PType))
            {
                var policies = policyGroup.Select(p => p.Policy);
                var section = policyGroup.Key.Substring(0, 1);
                var policyType = policyGroup.Key;
                policiesCount += policies.Count();
                foreach (var policy in policies)
                {
                    if (section == "g")
                        enforcer.AddNamedGroupingPolicy(policyType, policy);
                    else if (section == "p")
                        enforcer.AddNamedPolicy(policyType, policy);
                }
            }
        }
}

This is our stack trace (from Enfore() and onward):

System.InvalidOperationException:
   at System.ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported (System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Collections.Generic.Dictionary`2.FindValue (System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Collections.Generic.Dictionary`2.TryGetValue (System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Casbin.Util.BuiltInFunctions+<>c__DisplayClass12_0.<GenerateGFunction>g__GFunction|0 (Casbin, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null)
   at lambda_method59 (Anonymously Hosted DynamicMethods Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)
   at Casbin.Evaluation.ExpressionHandler.Invoke (Casbin, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null)
   at Casbin.Enforcer.InternalEnforce (Casbin, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null)
   at Casbin.Enforcer.InternalEnforce (Casbin, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null)
   at Casbin.Enforcer.Enforce (Casbin, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null)
   at Casbin.EnforcerExtension.Enforce (Casbin, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null)
<Stack trace clipped>

I suspect that the issue is with the current IGFunctionCache implemetation not using a ConcurrentDictionary.

I suggest either using a ConcurrentDictionary by default or giving us the option to replace the IGFunctionCachePool and IGFunctionCache with a custom implementations that does this.

casbin-bot commented 1 year ago

@sagilio @sociometry @AsakusaRinne

AsakusaRinne commented 1 year ago

Thank you for telling us this issue. I agree with you that it's caused by not using concurrent dictionary in GFunctionCache. We also noticed it two weeks ago and fixed it in #297 . However, this fix is not included in 2.0.0.-preview.4. If you don't mind using an unstable release, I think it can be fixed with the version 2.0.0-preview.4-build.332.preview.a6c464e by specifying the following config file as nuget source. We'll publish a stable release in the future. :)

<configuration>
    <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
        <add key="casbin-net.myget.org" value="https://www.myget.org/F/casbin-net/api/v3/index.json" />
    </packageSources>
</configuration>
herkit commented 1 year ago

@AsakusaRinne Thank you, I will try this, however it seems SyncedEnforcer is left out of this package, is the default Enforcer thread safe?

AsakusaRinne commented 1 year ago

Yes, there are some break changes in this package, including making DefaultEnforcer thread safe.