-
-
Notifications
You must be signed in to change notification settings - Fork 19
Description
Summary
The EctoAdapter automatically saves policies to the database but provides no clean way to load them back into the enforcer's memory on application startup. This creates an asymmetric API and forces developers to implement manual workarounds.
Current Behavior
What Works (Auto-Save)
# Configure adapter
adapter = EctoAdapter.new(Repo)
EnforcerServer.set_persist_adapter("my_enforcer", adapter)
# Add policy - automatically saved to DB
EnforcerServer.add_policy("my_enforcer", {:p, ["admin", "data", "write", "org:abc"]})
# ✅ Policy is now in both memory AND database
What Doesn't Work (No Auto-Load)
# Application restart...
# Re-configure adapter
adapter = EctoAdapter.new(Repo)
EnforcerServer.set_persist_adapter("my_enforcer", adapter)
# ❌ Policies from database are NOT loaded into memory
EnforcerServer.allow?("my_enforcer", ["admin", "data", "write", "org:abc"])
# => false (policies exist in DB but not in memory)
Expected API
There should be a clean, built-in way to load policies from the database:
# Option 1: Automatic on adapter setup
adapter = EctoAdapter.new(Repo)
EnforcerServer.set_persist_adapter("my_enforcer", adapter, load: true)
# Option 2: Explicit load function
EnforcerServer.load_policies_from_adapter("my_enforcer")
# Option 3: Enhanced load_policies that works with adapters
EnforcerServer.load_policies("my_enforcer", :from_adapter)
Current Workaround (Manual Implementation)
Developers must implement their own loading logic by querying the database directly and manually adding each policy:
defp load_policies_from_db do
# Manually query the database
rules = Repo.all(Acx.Persist.EctoAdapter.CasbinRule)
# Manually add each rule to the enforcer's memory
Enum.each(rules, fn rule ->
case rule.ptype do
"p" ->
attrs = build_attrs([rule.v0, rule.v1, rule.v2, rule.v3, rule.v4, rule.v5, rule.v6])
EnforcerServer.add_policy(@enforcer_name, {:p, attrs})
"g" ->
attrs = build_attrs([rule.v0, rule.v1, rule.v2])
case length(attrs) do
3 ->
[child, parent, domain] = attrs
EnforcerServer.add_mapping_policy(@enforcer_name, {:g, child, parent, domain})
2 ->
[child, parent] = attrs
EnforcerServer.add_mapping_policy(@enforcer_name, {:g, child, parent})
end
end
end)
end
defp build_attrs(values) do
Enum.reject(values, &is_nil/1)
end
Why This is Problematic
- The set_persist_adapter/2 function suggests persistence is fully configured, but it only handles saves, not loads.
- New users expect load_policies/2 to work with adapters, but it only accepts file paths:
# This signature is misleading:
EnforcerServer.load_policies(name, file)
# "file" parameter suggests it ONLY loads from files
- Comparison with PersistAdapter.load_policies/1
The library DOES have a function that retrieves policies from the adapter:
{:ok, {policies, grouping_policies}} = Acx.Persist.PersistAdapter.load_policies(adapter)
But: There's no function to feed these policies back into the enforcer's memory. Developers must manually loop through and add each one.
Proposed Solutions
Option 1: Add load_policies_from_adapter/1
EnforcerServer.load_policies_from_adapter("my_enforcer")
Option 2: Enhance set_persist_adapter/3 with options
EnforcerServer.set_persist_adapter("my_enforcer", adapter, auto_load: true)
Option 3: Make load_policies/2 adapter-aware
Accept atom :adapter as second parameter
EnforcerServer.load_policies("my_enforcer", :adapter)
Option 4: Add Enforcer.load_policies_from_adapter/2
enforcer = Enforcer.init(model_path)
enforcer = Enforcer.load_policies_from_adapter(enforcer, adapter)