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 +4 -0
- easy_rbac/core.py +65 -0
- easy_rbac/fastapi.py +39 -0
- easy_rbac/models.py +17 -0
- roleflow-0.1.1.dist-info/METADATA +102 -0
- roleflow-0.1.1.dist-info/RECORD +7 -0
- roleflow-0.1.1.dist-info/WHEEL +4 -0
easy_rbac/__init__.py
ADDED
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,,
|