roleflow 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
easy_rbac/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .models import Role
2
+ from .core import RBACEngine
3
+
4
+ __all__ = ["Role", "RBACEngine"]
easy_rbac/core.py ADDED
@@ -0,0 +1,65 @@
1
+ from typing import List, Optional, Dict, Callable
2
+ from easy_rbac.models import Role
3
+
4
+ class RBACEngine:
5
+ """
6
+ Core engine for Role-Based Access Control verification.
7
+ """
8
+ def __init__(self, roles: Optional[List[Role]] = None, role_loader: Optional[Callable[[str], Optional[Role]]] = None):
9
+ """
10
+ Initialize the RBAC engine.
11
+ :param roles: Optional list of Role models.
12
+ :param role_loader: Optional callback to dynamically fetch a role (e.g., from a database) if it's not found in memory.
13
+ """
14
+ self._roles: Dict[str, Role] = {}
15
+ self.role_loader = role_loader
16
+ if roles:
17
+ for role in roles:
18
+ self.add_role(role)
19
+
20
+ def add_role(self, role: Role) -> None:
21
+ """Register a new role in the engine."""
22
+ self._roles[role.name] = role
23
+
24
+ def get_role(self, role_name: str) -> Optional[Role]:
25
+ """Retrieve a registered role by its name. Falls back to role_loader if configured."""
26
+ if role_name in self._roles:
27
+ return self._roles[role_name]
28
+
29
+ # If not cached and we have a database/dynamic loader configured
30
+ if self.role_loader:
31
+ role = self.role_loader(role_name)
32
+ if role:
33
+ self.add_role(role) # Cache it for future queries
34
+ return role
35
+
36
+ return None
37
+
38
+ def is_granted(self, role_name: str, required_permission: str) -> bool:
39
+ """
40
+ Verify if a role has the required permission.
41
+
42
+ :param role_name: The name of the role trying to access the resource.
43
+ :param required_permission: The permission string required for access (e.g. 'table1.read').
44
+ :return: True if access is granted, False otherwise.
45
+ """
46
+ role = self.get_role(role_name)
47
+ if not role:
48
+ return False
49
+
50
+ for permit in role.permissions:
51
+ # 1. Root wildcard match
52
+ if permit == "*":
53
+ return True
54
+
55
+ # 2. Exact match
56
+ if permit == required_permission:
57
+ return True
58
+
59
+ # 3. Domain wildcard match (e.g., "table1.*" matches "table1.read")
60
+ if permit.endswith(".*"):
61
+ domain = permit[:-2] # remove the '.*'
62
+ if required_permission.startswith(f"{domain}."):
63
+ return True
64
+
65
+ return False
easy_rbac/fastapi.py ADDED
@@ -0,0 +1,39 @@
1
+ from typing import Callable, Optional, Any
2
+ from easy_rbac.core import RBACEngine
3
+
4
+ try:
5
+ from fastapi import HTTPException, status
6
+ from fastapi.requests import Request
7
+ HAS_FASTAPI = True
8
+ except ImportError:
9
+ HAS_FASTAPI = False
10
+
11
+ class RBACGuard:
12
+ """
13
+ A dependency injection factory for FastAPI to secure routes using Role-Based Access Control.
14
+ """
15
+ def __init__(self, engine: RBACEngine, role_provider: Callable[..., str]):
16
+ """
17
+ Initialize the RBACGuard.
18
+
19
+ :param engine: An instance of RBACEngine.
20
+ :param role_provider: A FastAPI dependency (callable) that resolves and returns the current user's role name as a string.
21
+ """
22
+ if not HAS_FASTAPI:
23
+ raise ImportError("FastAPI is not installed. Please install easy-rbac[fastapi] to use RBACGuard.")
24
+
25
+ self.engine = engine
26
+ self.role_provider = role_provider
27
+
28
+ def __call__(self, required_permission: str):
29
+ """
30
+ Returns a FastAPI dependency function to check for a specific permission.
31
+ """
32
+ def dependency(role_name: str = __import__("fastapi").Depends(self.role_provider)):
33
+ if not self.engine.is_granted(role_name, required_permission):
34
+ raise HTTPException(
35
+ status_code=status.HTTP_403_FORBIDDEN,
36
+ detail=f"Access denied: Requires permission '{required_permission}'"
37
+ )
38
+ return role_name
39
+ return dependency
easy_rbac/models.py ADDED
@@ -0,0 +1,17 @@
1
+ from typing import List, Union
2
+ from pydantic import BaseModel, Field
3
+
4
+ class Role(BaseModel):
5
+ """
6
+ Data model representing an RBAC Role.
7
+ """
8
+ id: Union[int, str] = Field(..., description="Unique identifier for the role.")
9
+ name: str = Field(..., description="Unique name of the role (e.g., 'ROLE_ADMIN').")
10
+ permissions: List[str] = Field(
11
+ default_factory=list,
12
+ description="List of permission strings. Supports exact match and wildcards (e.g. '*' or 'domain.*')."
13
+ )
14
+
15
+ model_config = {
16
+ "frozen": True
17
+ }
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: roleflow
3
+ Version: 0.1.1
4
+ Summary: A lightweight, hassle-free and production-ready RBAC (Role-Based Access Control) library.
5
+ Project-URL: Homepage, https://github.com/developer/easy-rbac
6
+ Author-email: sougata <sougatachongder8@gmail.com>
7
+ License: MIT
8
+ Requires-Python: >=3.8
9
+ Requires-Dist: pydantic>=2.0.0
10
+ Provides-Extra: fastapi
11
+ Requires-Dist: fastapi>=0.100.0; extra == 'fastapi'
12
+ Provides-Extra: test
13
+ Requires-Dist: fastapi; extra == 'test'
14
+ Requires-Dist: httpx; extra == 'test'
15
+ Requires-Dist: pytest; extra == 'test'
16
+ Requires-Dist: pytest-asyncio; extra == 'test'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # Easy RBAC
20
+
21
+ A lightweight, production-ready Role-Based Access Control (RBAC) package for Python, designed to be simple, fast, and framework-agnostic, while featuring seamless integration out-of-the-box for FastAPI.
22
+
23
+ ## Features
24
+ - **Generic RBAC Engine**: Easily verify permissions using wildcards (`*`, `table.*`) or exact matches.
25
+ - **Pydantic Validation**: Strong typing and validation for your Role and Permission schemas.
26
+ - **FastAPI Integration**: Native `RBACGuard` dependency injection for secure and hassle-free route protection.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install easy-rbac
32
+ ```
33
+
34
+ To install with FastAPI dependencies:
35
+ ```bash
36
+ pip install easy-rbac[fastapi]
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ### 1. Define your Roles
42
+ ```python
43
+ from easy_rbac import Role, RBACEngine
44
+
45
+ roles = [
46
+ Role(id=1, name="ROLE_ADMIN", permissions=["*"]),
47
+ Role(id=2, name="ROLE_STUDENT", permissions=["profile.read", "profile.edit", "course.read"]),
48
+ Role(id=3, name="ROLE_HOD", permissions=["course.*", "leave.approve"])
49
+ ]
50
+
51
+ engine = RBACEngine(roles=roles)
52
+ ```
53
+
54
+ ### 2. Fetch Roles from a Database (Dynamic Loading)
55
+ You don't have to provide all roles upfront. You can hook into your Database ORM by passing a `role_loader` callback function to the engine:
56
+
57
+ ```python
58
+ from easy_rbac import Role, RBACEngine
59
+
60
+ # Simulated database fetch function (e.g. using SQLAlchemy)
61
+ def db_role_loader(role_name: str) -> Role:
62
+ # 1. Query your database here using SQLAlchemy
63
+ # db_record = session.query(DbRole).filter(DbRole.name == role_name).first()
64
+ # 2. Convert database result into the generic easy_rbac.Role schema
65
+ # return Role(id=db_record.id, name=db_record.name, permissions=db_record.permissions)
66
+ pass
67
+
68
+ # Initialize engine without static roles
69
+ engine = RBACEngine(role_loader=db_role_loader)
70
+
71
+ # The engine will automatically call db_role_loader("ROLE_ADMIN") and cache it!
72
+ engine.is_granted("ROLE_ADMIN", "table1.read")
73
+ ```
74
+
75
+ ### 3. Check Permissions
76
+ ```python
77
+ # Returns True
78
+ engine.is_granted("ROLE_ADMIN", "anything.you.want")
79
+ engine.is_granted("ROLE_STUDENT", "profile.read")
80
+ engine.is_granted("ROLE_HOD", "course.create")
81
+
82
+ # Returns False
83
+ engine.is_granted("ROLE_STUDENT", "course.create")
84
+ ```
85
+
86
+ ### 4. FastAPI Integration
87
+ ```python
88
+ from fastapi import FastAPI, Depends
89
+ from easy_rbac.fastapi import RBACGuard
90
+
91
+ app = FastAPI()
92
+
93
+ # A mock function to get the current user's role
94
+ def get_current_user_role() -> str:
95
+ return "ROLE_STUDENT"
96
+
97
+ guard = RBACGuard(engine=engine, role_provider=get_current_user_role)
98
+
99
+ @app.get("/courses", dependencies=[Depends(guard("course.read"))])
100
+ def list_courses():
101
+ return {"message": "You can read courses!"}
102
+ ```
@@ -0,0 +1,7 @@
1
+ easy_rbac/__init__.py,sha256=F-sKKYOFvqniFS7J3_nsJ2eTQrVSxcyj3JR3zyO3Fbo,88
2
+ easy_rbac/core.py,sha256=FT8HaOU_EjqJVTHX_ITCLvf0PpMgp5U7YzZVPMYbhNE,2430
3
+ easy_rbac/fastapi.py,sha256=OGNgDKkMSrAloVaVcIlYE-AVSh2YrClTioeSYobtCsU,1500
4
+ easy_rbac/models.py,sha256=Xuq1WaOllF8BDVytPS4SK28ZsWRGns17R6sNblGMqgY,558
5
+ roleflow-0.1.1.dist-info/METADATA,sha256=XJnA24o9oJUsK3Su8PAnaaZ1Q8o5sE-4LcjiiX26yB4,3280
6
+ roleflow-0.1.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
7
+ roleflow-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any