Casbin Policy Storage in Python: SQLite, Firestore, and SQLAlchemy Adapter Guide
Using SQLite as the database adapter for Casbin policy storage is the ideal solution for local development, unit testing, and small-scale applications due to its lightweight, file-based nature.
In the Python Casbin ecosystem, this is achieved using the casbin-sqlalchemy-adapter, as SQLAlchemy natively supports SQLite without needing separate driver installations [2].
1. Installation and Dependenciesโ
To set up Casbin with SQLite, you only need the core Casbin library and the Casbin SQLAlchemy Adapter:
# Install the core Casbin library
pip install pycasbin
# Install the SQLAlchemy Adapter (handles SQLite, PostgreSQL, MySQL, etc.)
pip install casbin-sqlalchemy-adapter
2. Setting Up the Casbin Enforcer with SQLiteโ
The setup requires defining the SQLAlchemy connection string that points to your SQLite file. If the file doesn't exist, SQLite and the adapter will automatically create it.
Python Code Exampleโ
import casbin
from casbin_sqlalchemy_adapter import Adapter as SQLAlchemyAdapter
# 1. Define the connection string for the SQLite database file.
# The 'sqlite:///' prefix indicates a file path (three slashes).
# 'policy.db' will be created in the current directory if it doesn't exist.
SQLITE_URL = 'sqlite:///policy.db'
# 2. Instantiate the Adapter
# This links Casbin's policy management to the SQLite file.
adapter = SQLAlchemyAdapter(SQLITE_URL)
# 3. Instantiate the Casbin Enforcer
# 'model.conf' defines the rules (e.g., RBAC, ABAC).
# The adapter is passed here, which triggers the LoadPolicy() operation.
e = casbin.Enforcer('model.conf', adapter)
# The Enforcer is now ready to perform checks against the policy stored in 'policy.db'.
# Example Enforcement
sub = "alice"
obj = "/users/101"
act = "edit"
if e.enforce(sub, obj, act):
print(f"{sub} is permitted to {act} {obj}.")
else:
print(f"{sub} is denied access to {obj}.")
Annotation: The SQLAlchemyAdapter translates Casbin policy rules (like p, alice, data1, read) into rows in a table named casbin_rule within the policy.db file [1].
3. Policy Managementโ
When using the Casbin management API, the adapter ensures changes are immediately saved to the SQLite file, providing persistence across application restarts.
Adding and Removing Policiesโ
# Adding a new policy rule
e.add_policy("bob", "/data/audit", "view")
# Adding a grouping rule (e.g., assigning a role)
e.add_grouping_policy("bob", "auditor")
# Removing a policy rule
e.remove_policy("alice", "/users/101", "edit")
Every successful call to the Casbin policy management API (e.g., add_policy, remove_grouping_policy) automatically calls the adapter's methods to update the SQLite database file.
Key Considerations for SQLiteโ
- No Watcher Needed: In a typical local development scenario, where only one instance of the Flask app is running, a Watcher (used for synchronizing policy changes across multiple services) is not necessary.
- Performance: SQLite is extremely fast for local, single-user access, making it highly efficient for unit and integration tests.
- Portability: The use of the SQLAlchemy Adapter means you can switch the policy storage to PostgreSQL or MySQL for staging/production simply by changing the connection string (
SQLITE_URL) and installing the corresponding driver, without altering your core Casbin or Flask application code.
4. Using Firestore as Policy Storage (Cloud-Native)โ
For production deployments on Google Cloud, you can use Firestore as the policy storage backend via a dedicated adapter. The setup involves the Firebase Admin SDK.
Prerequisites and Installationโ
pip install pycasbin
pip install pycasbin-firebase-adapter firebase-admin
Ensure the environment where your Flask app runs (e.g., GCP App Engine, Cloud Run) has the necessary IAM permissions to access Firestore, or that you have set up a service account key [5].
Setting Up the Firestore Adapterโ
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
import casbin
from pycasbin_firebase_adapter import Adapter as FirestoreAdapter
# --- 1. Load Credentials ---
try:
cred = credentials.ApplicationDefault()
firebase_admin.initialize_app(cred)
except:
cred = credentials.Certificate("path/to/serviceAccountKey.json")
firebase_admin.initialize_app(cred)
db = firestore.client()
# 2. Instantiate the Adapter
adapter = FirestoreAdapter(db)
# 3. Instantiate the Enforcer
e = casbin.Enforcer("model.conf", adapter)
# --- Policy is automatically loaded into memory here (LoadPolicy()) ---
sub = "alice"
obj = "/reports/finance"
act = "read"
if e.enforce(sub, obj, act):
print(f"{sub} is permitted to {act} {obj}.")
else:
print(f"{sub} is denied access to {obj}.")
Firestore Policy Managementโ
When you make changes using the Casbin management API, the adapter automatically translates those changes into Firestore documents.
| Casbin API Call | Firestore Document Action |
|---|---|
e.add_policy("editor", "/data", "write") | Creates a new document in the Casbin collection (e.g., ptype: p, v0: editor, v1: /data, v2: write). |
e.add_grouping_policy("user_x", "editor") | Creates a new document (e.g., ptype: g, v0: user_x, v1: editor). |
e.save_policy() | Overwrites the entire policy in Firestore with the current in-memory state. |
Firestore Policy Document Structureโ
| Field | Casbin Policy Line Component |
|---|---|
ptype | The policy type (p, g, g2, etc.) |
v0 | The first field (e.g., user or role) |
v1 | The second field (e.g., resource or parent_role) |
v2 to v5 | Subsequent fields (e.g., action or domain) |
