Skip to content

Commit b361a7f

Browse files
authored
Merge pull request #261 from python-ellar/authorization_documentation
fix: Authorization Documentation
2 parents 7510ff8 + 39300bd commit b361a7f

File tree

9 files changed

+861
-19
lines changed

9 files changed

+861
-19
lines changed

docs/security/authorization.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,42 @@
1-
# Coming Soon
1+
# Authorization
2+
3+
Authorization is a crucial security feature in Ellar that determines what resources authenticated users can access. Ellar provides a flexible and powerful authorization system through policies, roles, and claims.
4+
5+
## Table of Contents
6+
7+
1. [Basic Authorization](#basic-authorization)
8+
2. [Policies](./authorization/policies.md)
9+
3. [Role-Based Authorization](./authorization/role-based.md)
10+
4. [Claims-Based Authorization](./authorization/claims-based.md)
11+
5. [Custom Policies with Requirements](./authorization/custom-policies.md)
12+
6. [Combining Policies](./authorization/combining-policies.md)
13+
14+
## Basic Authorization
15+
16+
To use authorization in your Ellar application, you need to:
17+
18+
1. Decorate your controllers or routes with `@Authorize()`
19+
2. Apply specific policies using `@CheckPolicies()`
20+
3. Ensure users are authenticated using `@AuthenticationRequired()`
21+
22+
Here's a basic example:
23+
24+
```python
25+
from ellar.auth import AuthenticationRequired, Authorize, CheckPolicies
26+
from ellar.common import Controller, get
27+
28+
@Controller("/articles")
29+
@Authorize() # Enable authorization for all routes
30+
@AuthenticationRequired() # Require authentication
31+
class ArticleController:
32+
@get("/admin")
33+
@CheckPolicies(RolePolicy("admin")) # Only allow admins
34+
async def admin_dashboard(self):
35+
return "Admin Dashboard"
36+
37+
@get("/public")
38+
async def public_articles(self): # Accessible to any authenticated user
39+
return "Public Articles"
40+
```
41+
42+
For detailed information about specific authorization features, please refer to the respective sections in the documentation.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Claims-Based Authorization
2+
3+
Claims-based authorization provides a more flexible and granular approach to authorization compared to role-based authorization. Claims are key-value pairs that represent attributes of the user and their access rights.
4+
5+
## Using ClaimsPolicy
6+
7+
Ellar provides the `ClaimsPolicy` class for implementing claims-based authorization:
8+
9+
```python
10+
from ellar.auth import AuthenticationRequired, Authorize, CheckPolicies
11+
from ellar.auth.policy import ClaimsPolicy
12+
from ellar.common import Controller, get
13+
14+
@Controller("/articles")
15+
@Authorize()
16+
class ArticleController:
17+
@get("/create")
18+
@CheckPolicies(ClaimsPolicy("article", "create"))
19+
async def create_article(self):
20+
return "Create Article"
21+
22+
@get("/publish")
23+
@CheckPolicies(ClaimsPolicy("article", "create", "publish"))
24+
async def publish_article(self):
25+
return "Publish Article"
26+
```
27+
28+
## How ClaimsPolicy Works
29+
30+
The `ClaimsPolicy` checks if the user has specific claim values for a given claim type. Claims are typically stored in the user's identity:
31+
32+
```python
33+
# Example user data structure with claims
34+
user_data = {
35+
"id": "123",
36+
"username": "john_doe",
37+
"article": ["create", "read", "publish"], # Claim type: "article" with multiple values
38+
"subscription": "premium" # Claim type: "subscription" with single value
39+
}
40+
```
41+
42+
## Single vs Multiple Claim Values
43+
44+
Claims can have single or multiple values:
45+
46+
```python
47+
@Controller("/content")
48+
@Authorize()
49+
class ContentController:
50+
@get("/premium")
51+
@CheckPolicies(ClaimsPolicy("subscription", "premium")) # Single claim value
52+
async def premium_content(self):
53+
return "Premium Content"
54+
55+
@get("/manage")
56+
@CheckPolicies(ClaimsPolicy("permissions", "create", "edit", "delete")) # Multiple claim values
57+
async def manage_content(self):
58+
return "Content Management"
59+
```
60+
61+
## Combining Claims Policies
62+
63+
You can combine multiple claims policies using logical operators:
64+
65+
```python
66+
@Controller("/advanced")
67+
@Authorize()
68+
class AdvancedController:
69+
@get("/editor")
70+
@CheckPolicies(
71+
ClaimsPolicy("article", "edit") &
72+
ClaimsPolicy("status", "active")
73+
)
74+
async def editor_dashboard(self):
75+
return "Editor Dashboard"
76+
77+
@get("/moderator")
78+
@CheckPolicies(
79+
ClaimsPolicy("content", "moderate") |
80+
ClaimsPolicy("role", "admin")
81+
)
82+
async def moderator_dashboard(self):
83+
return "Moderator Dashboard"
84+
```
85+
86+
## Best Practices
87+
88+
1. Use descriptive claim types and values
89+
2. Keep claim values simple and atomic
90+
3. Use claims for fine-grained permissions
91+
4. Consider using claims instead of roles for more flexible authorization
92+
5. Document your claim types and their possible values
93+
94+
## Example: E-commerce Authorization
95+
96+
Here's a comprehensive example showing claims-based authorization in an e-commerce application:
97+
98+
```python
99+
@Controller("/store")
100+
@Authorize()
101+
class StoreController:
102+
@get("/products")
103+
@CheckPolicies(ClaimsPolicy("store", "view_products"))
104+
async def view_products(self):
105+
return "Product List"
106+
107+
@get("/products/manage")
108+
@CheckPolicies(
109+
ClaimsPolicy("store", "manage_products") &
110+
ClaimsPolicy("account_status", "verified")
111+
)
112+
async def manage_products(self):
113+
return "Product Management"
114+
115+
@get("/orders")
116+
@CheckPolicies(
117+
ClaimsPolicy("store", "view_orders") |
118+
ClaimsPolicy("role", "customer_service")
119+
)
120+
async def view_orders(self):
121+
return "Order List"
122+
123+
@get("/reports")
124+
@CheckPolicies(
125+
ClaimsPolicy("store", "view_reports") &
126+
(ClaimsPolicy("role", "manager") | ClaimsPolicy("permissions", "analytics"))
127+
)
128+
async def view_reports(self):
129+
return "Store Reports"
130+
```
131+
132+
## Claims vs Roles
133+
134+
While roles are a form of claims, dedicated claims offer several advantages:
135+
136+
1. **Granularity**: Claims can represent specific permissions rather than broad role categories
137+
2. **Flexibility**: Claims can be easily added or modified without changing role structures
138+
3. **Clarity**: Claims directly express what a user can do rather than implying it through roles
139+
4. **Scalability**: Claims can grow with your application's needs without role explosion
140+
141+
For complex authorization scenarios, consider combining claims with roles and custom policies. See [Combining Policies](./combining-policies.md) for more information.
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# Combining Policies
2+
3+
Ellar provides powerful operators to combine different types of policies for complex authorization scenarios. This guide shows you how to use these combinations effectively.
4+
5+
## Basic Policy Operators
6+
7+
Ellar supports three logical operators for combining policies:
8+
9+
- `&` (AND): Both policies must return `True`
10+
- `|` (OR): At least one policy must return `True`
11+
- `~` (NOT): Inverts the policy result
12+
13+
## Simple Combinations
14+
15+
Here are basic examples of combining policies:
16+
17+
```python
18+
from ellar.auth import AuthenticationRequired, Authorize, CheckPolicies
19+
from ellar.auth.policy import RolePolicy, ClaimsPolicy
20+
from ellar.common import Controller, get
21+
22+
@Controller("/examples")
23+
@Authorize()
24+
@AuthenticationRequired()
25+
class ExampleController:
26+
@get("/and-example")
27+
@CheckPolicies(RolePolicy("admin") & RolePolicy("editor"))
28+
async def requires_both_roles(self):
29+
return "Must be both admin and editor"
30+
31+
@get("/or-example")
32+
@CheckPolicies(RolePolicy("admin") | RolePolicy("moderator"))
33+
async def requires_either_role(self):
34+
return "Must be either admin or moderator"
35+
36+
@get("/not-example")
37+
@CheckPolicies(~RolePolicy("banned"))
38+
async def not_banned(self):
39+
return "Access allowed if not banned"
40+
```
41+
42+
## Complex Combinations
43+
44+
You can create more complex authorization rules by combining multiple policies:
45+
46+
```python
47+
@Controller("/advanced")
48+
@Authorize()
49+
@AuthenticationRequired()
50+
class AdvancedController:
51+
@get("/complex")
52+
@CheckPolicies(
53+
(RolePolicy("editor") & ClaimsPolicy("department", "content")) |
54+
RolePolicy("admin")
55+
)
56+
async def complex_access(self):
57+
return "Complex access rules"
58+
59+
@get("/nested")
60+
@CheckPolicies(
61+
RolePolicy("user") &
62+
(ClaimsPolicy("subscription", "premium") | RolePolicy("staff")) &
63+
~RolePolicy("restricted")
64+
)
65+
async def nested_rules(self):
66+
return "Nested policy rules"
67+
```
68+
69+
## Combining Different Policy Types
70+
71+
You can mix and match different types of policies:
72+
73+
```python
74+
from ellar.auth import RolePolicy, ClaimsPolicy
75+
from ellar.common import Controller, get
76+
from .custom_policies import AgeRequirementPolicy, TeamMemberPolicy
77+
78+
@Controller("/mixed")
79+
@Authorize()
80+
class MixedPolicyController:
81+
@get("/content")
82+
@CheckPolicies(
83+
(AgeRequirementPolicy[18] & ClaimsPolicy("region", "US", "CA")) |
84+
RolePolicy("global_admin")
85+
)
86+
async def age_and_region(self):
87+
return "Age and region restricted content"
88+
89+
@get("/team-access")
90+
@CheckPolicies(
91+
TeamMemberPolicy["engineering"] &
92+
(RolePolicy("developer") | RolePolicy("team_lead")) &
93+
ClaimsPolicy("security_clearance", "level2")
94+
)
95+
async def team_access(self):
96+
return "Team-specific access"
97+
```
98+
99+
## Real-World Examples
100+
101+
Here are some practical examples of policy combinations:
102+
103+
### Content Management System
104+
105+
```python
106+
@Controller("/cms")
107+
@Authorize()
108+
class CMSController:
109+
@get("/articles/{id}/edit")
110+
@CheckPolicies(
111+
(RolePolicy("editor") & ClaimsPolicy("article", "edit")) |
112+
RolePolicy("admin") |
113+
(TeamMemberPolicy["content"] & ClaimsPolicy("article", "edit"))
114+
)
115+
async def edit_article(self):
116+
return "Edit Article"
117+
118+
@get("/articles/{id}/publish")
119+
@CheckPolicies(
120+
(RolePolicy("editor") & ClaimsPolicy("article", "publish") & ~RolePolicy("junior")) |
121+
RolePolicy("senior_editor") |
122+
RolePolicy("admin")
123+
)
124+
async def publish_article(self):
125+
return "Publish Article"
126+
```
127+
128+
### E-commerce Platform
129+
130+
```python
131+
@Controller("/store")
132+
@Authorize()
133+
class StoreController:
134+
@get("/products/{id}/manage")
135+
@CheckPolicies(
136+
(RolePolicy("vendor") & ClaimsPolicy("store", "manage_products")) |
137+
RolePolicy("store_admin")
138+
)
139+
async def manage_product(self):
140+
return "Manage Product"
141+
142+
@get("/orders/{id}/refund")
143+
@CheckPolicies(
144+
(RolePolicy("support") & ClaimsPolicy("order", "refund") & AgeRequirementPolicy[21]) |
145+
RolePolicy("finance_admin")
146+
)
147+
async def process_refund(self):
148+
return "Process Refund"
149+
```
150+
151+
## Best Practices
152+
153+
1. **Readability**
154+
- Use parentheses to make complex combinations clear
155+
- Break long policy combinations into multiple lines
156+
- Consider creating custom policies for very complex rules
157+
158+
2. **Performance**
159+
- Order OR conditions with the most likely to succeed first
160+
- Order AND conditions with the least expensive to evaluate first
161+
- Consider caching policy results for expensive evaluations
162+
163+
3. **Maintenance**
164+
- Document complex policy combinations
165+
- Create reusable policy combinations for common patterns
166+
- Keep policy logic modular and testable
167+
168+
4. **Security**
169+
- Always start with the principle of least privilege
170+
- Use OR combinations carefully as they broaden access
171+
- Regularly audit policy combinations for security implications
172+
173+
## Common Patterns
174+
175+
Here are some common patterns for combining policies:
176+
177+
```python
178+
# Role hierarchy
179+
base_access = RolePolicy("user")
180+
elevated_access = base_access & RolePolicy("premium")
181+
admin_access = elevated_access & RolePolicy("admin")
182+
183+
# Feature access with fallback
184+
feature_access = (
185+
ClaimsPolicy("feature", "beta") & RolePolicy("beta_tester")
186+
) | RolePolicy("admin")
187+
188+
# Geographic restrictions with age verification
189+
regional_access = (
190+
AgeRequirementPolicy[21] &
191+
ClaimsPolicy("region", "US", "CA")
192+
) | RolePolicy("global_access")
193+
194+
# Team-based access with role requirements
195+
team_access = (
196+
TeamMemberPolicy["project-x"] &
197+
(RolePolicy("developer") | RolePolicy("designer"))
198+
) & ~RolePolicy("restricted")
199+
```
200+
201+
For more specific examples of each policy type, refer to:
202+
- [Role-Based Authorization](./role-based.md)
203+
- [Claims-Based Authorization](./claims-based.md)
204+
- [Custom Policies with Requirements](./custom-policies.md)

0 commit comments

Comments
 (0)