|
| 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