-
Couldn't load subscription status.
- Fork 5
Set Up Casbin RBAC Foundation with DB Table, Model, Policies, and Tests #135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
fa613a4
intial setup for rbac
avirajsingh7 db5db53
enforcer for testing
avirajsingh7 740ae71
fix import
avirajsingh7 c3bd485
matcher improved
avirajsingh7 19e6c69
fix migration
avirajsingh7 07367f0
Added rbac policies
avirajsingh7 d8d3879
rename rbac.py to casbin.py
avirajsingh7 84a643a
update doc string in casbin table
avirajsingh7 45d2b6a
Added test cases for permission check
avirajsingh7 2f77ed0
remove crud from project file
avirajsingh7 b0bc7fc
edited doc string
avirajsingh7 967e555
edited doc string in casbin rule
avirajsingh7 a5c18b0
format file using precomit
avirajsingh7 ae05921
file handling for json policies
avirajsingh7 b165697
precommit formatting
avirajsingh7 864d2c7
remove unused import
avirajsingh7 be598d1
addes script to update casbin policies of p type
avirajsingh7 e63f989
format file using precommit
avirajsingh7 cf54b17
update prestart script
avirajsingh7 778a708
update test cases to have policies
avirajsingh7 7cc74c7
added test cases for script for updating casbin policies
avirajsingh7 6d8ce28
precommit format file
avirajsingh7 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
46 changes: 46 additions & 0 deletions
46
backend/app/alembic/versions/b5055f0b4a4d_added_casbin_rule.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| """added casbin rule | ||
|
|
||
| Revision ID: b5055f0b4a4d | ||
| Revises: 8d7a05fd0ad4 | ||
| Create Date: 2025-04-09 13:45:43.511977 | ||
|
|
||
| """ | ||
| from alembic import op | ||
| import sqlalchemy as sa | ||
| import sqlmodel.sql.sqltypes | ||
|
|
||
|
|
||
| # revision identifiers, used by Alembic. | ||
| revision = "b5055f0b4a4d" | ||
| down_revision = "8d7a05fd0ad4" | ||
| branch_labels = None | ||
| depends_on = None | ||
|
|
||
|
|
||
| def upgrade(): | ||
| # ### commands auto generated by Alembic - please adjust! ### | ||
| op.create_table( | ||
| "casbin_rule", | ||
| sa.Column("id", sa.Integer(), nullable=False), | ||
| sa.Column( | ||
| "ptype", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False | ||
| ), | ||
| sa.Column("v0", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True), | ||
| sa.Column("v1", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True), | ||
| sa.Column("v2", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True), | ||
| sa.Column("v3", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True), | ||
| sa.Column("v4", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True), | ||
| sa.Column("v5", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True), | ||
| sa.PrimaryKeyConstraint("id"), | ||
| ) | ||
| op.create_index( | ||
| op.f("ix_casbin_rule_ptype"), "casbin_rule", ["ptype"], unique=False | ||
| ) | ||
| # ### end Alembic commands ### | ||
|
|
||
|
|
||
| def downgrade(): | ||
| # ### commands auto generated by Alembic - please adjust! ### | ||
| op.drop_index(op.f("ix_casbin_rule_ptype"), table_name="casbin_rule") | ||
| op.drop_table("casbin_rule") | ||
| # ### end Alembic commands ### |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import os | ||
| from casbin import Enforcer | ||
| from casbin_sqlalchemy_adapter import Adapter | ||
| from app.core.db import engine | ||
| from fastapi.concurrency import run_in_threadpool | ||
|
|
||
|
|
||
| config_path = os.path.join(os.path.dirname(__file__), "rbac_model.conf") | ||
|
|
||
| adapter = Adapter(engine) | ||
| enforcer = Enforcer(config_path, adapter) | ||
| enforcer.enable_auto_save(True) | ||
|
|
||
|
|
||
| async def load_policy(): | ||
| await run_in_threadpool(enforcer.load_policy) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| [request_definition] | ||
| r = sub, org, proj, obj, act | ||
|
|
||
| [policy_definition] | ||
| p = role, obj, act | ||
|
|
||
| [role_definition] | ||
| g = _, _, _ # user → role → org | ||
| g2 = _, _, _ # user → role → project | ||
|
|
||
| [policy_effect] | ||
| e = some(where (p.eft == allow)) | ||
|
|
||
| [matchers] | ||
| # Matcher logic: | ||
| # - If project ID is not present → check org-level role (g). | ||
| # - If project ID is present: | ||
| # - Check if user has equivalent org-level role (e.g., org_admin → project_admin). | ||
| # - Otherwise, fall back to project-level role check (g2). | ||
| # Final match only happens if object and action match as well. | ||
|
|
||
| m = ((r.proj == "" && g(r.sub, p.role, r.org)) || (r.proj != "" && (g(r.sub, "org_admin", r.org) && p.role == "project_admin" || g(r.sub, "org_manager", r.org) && p.role == "project_manager" || g(r.sub, "org_reader", r.org) && p.role == "project_reader" || g2(r.sub, p.role, r.proj)))) && r.obj == p.obj && r.act == p.act |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| { | ||
| "permissions": [ | ||
| { | ||
| "role": "org_admin", | ||
| "resource": "org_data", | ||
| "actions": ["delete", "write", "read"] | ||
| }, | ||
| { | ||
| "role": "org_manager", | ||
| "resource": "org_data", | ||
| "actions": ["write", "read"] | ||
| }, | ||
| { | ||
| "role": "org_reader", | ||
| "resource": "org_data", | ||
| "actions": ["read"] | ||
| }, | ||
| { | ||
| "role": "project_admin", | ||
| "resource": "project_data", | ||
| "actions": ["delete", "write", "read"] | ||
| }, | ||
| { | ||
| "role": "project_manager", | ||
| "resource": "project_data", | ||
| "actions": ["write", "read"] | ||
| }, | ||
| { | ||
| "role": "project_reader", | ||
| "resource": "project_data", | ||
| "actions": ["read"] | ||
| } | ||
| ] | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import os | ||
| import json | ||
| import logging | ||
|
|
||
| from sqlmodel import Session | ||
| from sqlalchemy.sql import text | ||
|
|
||
| from app.core.db import engine | ||
|
|
||
| logging.basicConfig(level=logging.INFO) | ||
| logger = logging.getLogger(__name__) | ||
|
|
||
| policies_file_path = os.path.join(os.path.dirname(__file__), "rbac_policies.json") | ||
|
|
||
|
|
||
| def update_policies(session: Session) -> None: | ||
| """ | ||
| Update Casbin policies from the local JSON file. | ||
| This deletes all existing 'p' policies and inserts new ones. | ||
| """ | ||
| try: | ||
| with open(policies_file_path) as f: | ||
| data = json.load(f) | ||
| except FileNotFoundError: | ||
| raise ValueError(f"Policy file not found: {policies_file_path}") | ||
|
|
||
| conn = session.connection() | ||
|
|
||
| try: | ||
| # Clear existing policies | ||
| logger.info("Deleting existing Casbin policies") | ||
| conn.execute(text("DELETE FROM casbin_rule WHERE ptype = 'p'")) | ||
|
|
||
| # Insert new policies | ||
| for policy in data.get("permissions", []): | ||
| role = policy.get("role") | ||
| resource = policy.get("resource") | ||
| actions = policy.get("actions") | ||
|
|
||
| if not role or not resource or not isinstance(actions, list): | ||
| raise ValueError(f"Invalid policy entry: {policy}") | ||
|
|
||
| for action in actions: | ||
| conn.execute( | ||
| text( | ||
| """ | ||
| INSERT INTO casbin_rule (ptype, v0, v1, v2) | ||
| VALUES (:ptype, :v0, :v1, :v2) | ||
| """ | ||
| ), | ||
| {"ptype": "p", "v0": role, "v1": resource, "v2": action}, | ||
| ) | ||
|
|
||
| session.commit() | ||
| logger.info("Casbin policies updated successfully.") | ||
|
|
||
| except Exception as e: | ||
| logger.error(f"Error updating Casbin policies: {e}") | ||
kodus-ai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| session.rollback() | ||
| raise | ||
|
|
||
|
|
||
| def main() -> None: | ||
| logger.info("Starting Casbin policy update") | ||
| with Session(engine) as session: | ||
| update_policies(session) | ||
| logger.info("Casbin policy update finished") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| from typing import Optional | ||
| from sqlmodel import SQLModel, Field | ||
|
|
||
|
|
||
| class CasbinRule(SQLModel, table=True): | ||
| """ | ||
| Represents Casbin policy rules for RBAC. | ||
|
|
||
| Policy type (`ptype`): | ||
| - "p" -> policy rule (permissions) | ||
| - "g" -> subject-role assignment (org-level) | ||
| - "g2" -> subject-role assignment (project-level) | ||
|
|
||
| Field meanings based on `ptype`: | ||
|
|
||
| For ptype = "p" (permissions): | ||
| - v0: role | ||
| - v1: resource (e.g., "org_data", "project_data") | ||
| - v2: action (e.g., "read", "write", "delete") | ||
|
|
||
| For ptype = "g" (org-level role assignment): | ||
| - v0: user_id | ||
| - v1: role (e.g., "org_admin", "org_writer", etc.) | ||
| - v2: org_id | ||
|
|
||
| For ptype = "g2" (project-level role assignment): | ||
| - v0: user_id | ||
| - v1: role (e.g., "project_reader", etc.) | ||
| - v2: project_id | ||
| """ | ||
|
|
||
| __tablename__ = "casbin_rule" | ||
|
|
||
| id: Optional[int] = Field(default=None, primary_key=True) | ||
avirajsingh7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ptype: str = Field(index=True, max_length=255) | ||
avirajsingh7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| v0: Optional[str] = Field(default=None, max_length=255) | ||
| v1: Optional[str] = Field(default=None, max_length=255) | ||
| v2: Optional[str] = Field(default=None, max_length=255) | ||
| v3: Optional[str] = Field(default=None, max_length=255) | ||
| v4: Optional[str] = Field(default=None, max_length=255) | ||
| v5: Optional[str] = Field(default=None, max_length=255) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.