Add LoadPolicy API to load policies from database on startup
The EctoAdapter auto-persists policy changes to the database but had no built-in way to load them back on application startup, forcing manual database queries and loops.
Changes
-
Added
EnforcerServer.load_policies/1- loads policies from configured persist adapter -
Added
EnforcerServer.load_mapping_policies/1- loads role mappings from adapter (RBAC) -
Maintains backward compatibility - existing file-based
load_policies/2unchanged - Test coverage - ACL/RBAC models, role inheritance, workflows
- Documentation - database setup, production startup patterns
Usage
Before (manual workaround):
rules = Repo.all(CasbinRule)
Enum.each(rules, fn rule ->
case rule.ptype do
"p" -> EnforcerServer.add_policy(name, {:p, build_attrs([rule.v0, ...])})
"g" -> # complex mapping logic
end
end)
After (built-in):
adapter = EctoAdapter.new(MyApp.Repo)
EnforcerServer.set_persist_adapter("my_enforcer", adapter)
EnforcerServer.load_policies("my_enforcer")
EnforcerServer.load_mapping_policies("my_enforcer") # if using RBAC
Implements LoadPolicy pattern from Golang Casbin adapters per @hsluoyz's suggestion.
[!WARNING]
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
esm.ubuntu.com
- Triggering command:
/usr/lib/apt/methods/https /usr/lib/apt/methods/https(dns block)repo.hex.pm
- Triggering command:
/usr/lib/erlang/erts-13.2.2.5/bin/inet_gethost 4(dns block)If you need me to access, download, or install something from one of these locations, you can either:
- Configure Actions setup steps to set up my environment, which run before the firewall is enabled
- Add the appropriate URLs or hosts to the custom allowlist in this repository's Copilot coding agent settings (admins only)
Original prompt
This section details on the original issue you should resolve
<issue_title>EctoAdapter: No Built-in Way to Load Policies from Database on Startup</issue_title> <issue_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 databaseWhat 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) endWhy 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)</issue_description>
Comments on the Issue (you are @copilot in this section)
@hsluoyz Casbin adapters use LoadPolicy API to load policy from DBWhy not use LoadPolicy? If LoadPolicy is not supported, need to support it
Refer to Golang's adapters:
- https://github.com/casbin/gorm-adapter
- https://github.com/casbin/ent-adapter
- https://github.com/casbin/xorm-adapter
- https://github.com/casbin/redis-adapter</comment_new> <comment_new>
@nomeguy @sushilbansal already supported, see: https://github.com/casbin/casbin-ex/pull/41</comment_new>
- Fixes casbin/casbin-ex#30
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.