Skip to content

Commit a05ac79

Browse files
committed
Added documentation for authorization
1 parent 554f981 commit a05ac79

File tree

7 files changed

+779
-2
lines changed

7 files changed

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

0 commit comments

Comments
 (0)