python-skills 1.0.0__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.
- python_skills/__init__.py +10 -0
- python_skills/__main__.py +6 -0
- python_skills/adapters/__init__.py +48 -0
- python_skills/adapters/agent_skills.py +415 -0
- python_skills/adapters/aider_adapter.py +226 -0
- python_skills/adapters/base.py +153 -0
- python_skills/adapters/claude.py +474 -0
- python_skills/adapters/cline.py +332 -0
- python_skills/adapters/codex.py +24 -0
- python_skills/adapters/continue_adapter.py +198 -0
- python_skills/adapters/cursor.py +327 -0
- python_skills/adapters/gemini.py +26 -0
- python_skills/adapters/goose.py +26 -0
- python_skills/adapters/junie.py +25 -0
- python_skills/adapters/kiro.py +382 -0
- python_skills/adapters/opencode.py +27 -0
- python_skills/adapters/roo.py +25 -0
- python_skills/adapters/universal.py +203 -0
- python_skills/adapters/vscode.py +27 -0
- python_skills/adapters/windsurf.py +26 -0
- python_skills/adapters/zed.py +27 -0
- python_skills/cli.py +326 -0
- python_skills/config.py +160 -0
- python_skills/detector.py +152 -0
- python_skills/installer.py +163 -0
- python_skills/markers.py +115 -0
- python_skills/skills/__init__.py +14 -0
- python_skills/skills/loader.py +171 -0
- python_skills/skills/metadata.py +152 -0
- python_skills/skills/registry.py +101 -0
- python_skills/state.py +204 -0
- python_skills-1.0.0.dist-info/METADATA +99 -0
- python_skills-1.0.0.dist-info/RECORD +105 -0
- python_skills-1.0.0.dist-info/WHEEL +4 -0
- python_skills-1.0.0.dist-info/entry_points.txt +2 -0
- python_skills-1.0.0.dist-info/licenses/LICENSE +21 -0
- skills/advanced_python.md +239 -0
- skills/anti_patterns/index.md +406 -0
- skills/comprehensions.md +167 -0
- skills/control_flow.md +175 -0
- skills/data_structures.md +243 -0
- skills/debugging/common_bugs.md +222 -0
- skills/debugging/inspection_techniques.md +249 -0
- skills/debugging/root_cause.md +203 -0
- skills/engineering/application_logging.md +195 -0
- skills/engineering/cli_apps.md +207 -0
- skills/engineering/configuration.md +218 -0
- skills/engineering/database.md +240 -0
- skills/engineering/dependency_management.md +205 -0
- skills/engineering/http_clients.md +267 -0
- skills/engineering/modules_packages.md +211 -0
- skills/engineering/packaging.md +197 -0
- skills/engineering/project_structure.md +155 -0
- skills/engineering/pyproject_toml.md +302 -0
- skills/engineering/virtual_environments.md +206 -0
- skills/functions.md +244 -0
- skills/generation/async_concurrency.md +291 -0
- skills/generation/error_handling.md +276 -0
- skills/generation/protocols_generics.md +243 -0
- skills/generation/type_hints.md +290 -0
- skills/generation/validation_pipeline.md +274 -0
- skills/generation/workflow.md +190 -0
- skills/oop.md +228 -0
- skills/quality/abstractions.md +154 -0
- skills/quality/comments.md +177 -0
- skills/quality/documentation.md +176 -0
- skills/quality/duplication.md +137 -0
- skills/quality/maintainability.md +142 -0
- skills/quality/naming.md +171 -0
- skills/quality/quality_functions.md +245 -0
- skills/quality/readability.md +239 -0
- skills/quality/type_annotations.md +192 -0
- skills/refactoring/behavior_preservation.md +157 -0
- skills/refactoring/incremental.md +187 -0
- skills/refactoring/interface_stability.md +199 -0
- skills/refactoring/safe_refactoring.md +206 -0
- skills/security/auth_boundaries.md +200 -0
- skills/security/command_injection.md +207 -0
- skills/security/dependency_risks.md +282 -0
- skills/security/file_handling.md +156 -0
- skills/security/input_validation.md +190 -0
- skills/security/path_traversal.md +172 -0
- skills/security/secrets.md +171 -0
- skills/security/sql_injection.md +188 -0
- skills/security/unsafe_deserialization.md +164 -0
- skills/stdlib/argparse.md +178 -0
- skills/stdlib/collections.md +212 -0
- skills/stdlib/datetime.md +187 -0
- skills/stdlib/functools.md +238 -0
- skills/stdlib/itertools.md +183 -0
- skills/stdlib/json.md +162 -0
- skills/stdlib/logging.md +185 -0
- skills/stdlib/os_sys.md +184 -0
- skills/stdlib/pathlib.md +218 -0
- skills/stdlib/re.md +171 -0
- skills/stdlib/statistics.md +112 -0
- skills/stdlib/subprocess.md +211 -0
- skills/testing/async_tests.md +249 -0
- skills/testing/coverage.md +168 -0
- skills/testing/edge_cases.md +197 -0
- skills/testing/fixtures_mocks.md +203 -0
- skills/testing/organization.md +205 -0
- skills/testing/parameterized.md +174 -0
- skills/testing/regression_tests.md +165 -0
- skills/variables_types.md +107 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Refactoring: Interface Stability
|
|
2
|
+
|
|
3
|
+
**Purpose**: Maintain stable public APIs during refactoring.
|
|
4
|
+
|
|
5
|
+
**When to use**: Any change to public functions, classes, or module APIs.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Public API Definition
|
|
12
|
+
- Functions/classes in `__all__`
|
|
13
|
+
- Exported in package `__init__.py`
|
|
14
|
+
- Documented in public docs
|
|
15
|
+
- Used by external consumers
|
|
16
|
+
|
|
17
|
+
### Stability Principles
|
|
18
|
+
1. **Add, don't remove** — Add new parameters with defaults
|
|
19
|
+
2. **Deprecate gracefully** — Warn before removing
|
|
20
|
+
3. **Version bump** — Breaking changes = major version
|
|
21
|
+
4. **Migration path** — Provide upgrade guide
|
|
22
|
+
|
|
23
|
+
### Safe API Evolution
|
|
24
|
+
|
|
25
|
+
#### Adding Parameters
|
|
26
|
+
```python
|
|
27
|
+
# Before
|
|
28
|
+
def fetch_users(client, limit=100):
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
# After — add with default (backward compatible)
|
|
32
|
+
def fetch_users(client, limit=100, *, filter=None, sort=None):
|
|
33
|
+
...
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Changing Return Type
|
|
37
|
+
```python
|
|
38
|
+
# BAD — breaks callers
|
|
39
|
+
def get_user(id) -> User:
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
def get_user(id) -> User | None: # Breaking!
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
# GOOD — add new function
|
|
46
|
+
def get_user(id) -> User:
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def get_user_or_none(id) -> User | None:
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
# Or use Result type
|
|
53
|
+
def get_user(id) -> Result[User, NotFoundError]:
|
|
54
|
+
...
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Deprecation Pattern
|
|
58
|
+
```python
|
|
59
|
+
import warnings
|
|
60
|
+
from functools import wraps
|
|
61
|
+
|
|
62
|
+
def deprecated(reason: str, version: str):
|
|
63
|
+
def decorator(func):
|
|
64
|
+
@wraps(func)
|
|
65
|
+
def wrapper(*args, **kwargs):
|
|
66
|
+
warnings.warn(
|
|
67
|
+
f"{func.__name__} is deprecated since {version}: {reason}",
|
|
68
|
+
DeprecationWarning,
|
|
69
|
+
stacklevel=2,
|
|
70
|
+
)
|
|
71
|
+
return func(*args, **kwargs)
|
|
72
|
+
return wrapper
|
|
73
|
+
return decorator
|
|
74
|
+
|
|
75
|
+
@deprecated("Use fetch_users_v2()", "2.0")
|
|
76
|
+
def fetch_users(client, limit=100):
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
# Type stub for deprecated
|
|
80
|
+
from typing import TYPE_CHECKING
|
|
81
|
+
if TYPE_CHECKING:
|
|
82
|
+
def fetch_users(...) -> list[User]: ...
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Removing Parameters
|
|
86
|
+
```python
|
|
87
|
+
# Step 1: Make optional with deprecation
|
|
88
|
+
def process(data, *, old_param=None, new_param=None):
|
|
89
|
+
if old_param is not None:
|
|
90
|
+
warnings.warn("old_param deprecated", DeprecationWarning)
|
|
91
|
+
new_param = old_param
|
|
92
|
+
# Use new_param
|
|
93
|
+
|
|
94
|
+
# Step 2: Remove after deprecation period (major version)
|
|
95
|
+
def process(data, *, new_param):
|
|
96
|
+
...
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Versioning
|
|
100
|
+
```toml
|
|
101
|
+
# pyproject.toml
|
|
102
|
+
[project]
|
|
103
|
+
version = "2.1.0" # Semantic: MAJOR.MINOR.PATCH
|
|
104
|
+
|
|
105
|
+
# MAJOR: Breaking API changes
|
|
106
|
+
# MINOR: New features, backward compatible
|
|
107
|
+
# PATCH: Bug fixes, backward compatible
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Interface Testing
|
|
111
|
+
```python
|
|
112
|
+
# Test public API surface
|
|
113
|
+
def test_public_api_surface():
|
|
114
|
+
"""Ensure public API hasn't accidentally changed"""
|
|
115
|
+
from mypackage import __all__
|
|
116
|
+
|
|
117
|
+
expected = {
|
|
118
|
+
"UserService",
|
|
119
|
+
"User",
|
|
120
|
+
"create_user",
|
|
121
|
+
"get_user",
|
|
122
|
+
"ValidationError",
|
|
123
|
+
}
|
|
124
|
+
assert set(__all__) == expected
|
|
125
|
+
|
|
126
|
+
# Test signatures
|
|
127
|
+
import inspect
|
|
128
|
+
|
|
129
|
+
def test_user_service_signatures():
|
|
130
|
+
sig = inspect.signature(UserService.create)
|
|
131
|
+
params = list(sig.parameters.keys())
|
|
132
|
+
assert params == ["self", "data", "validate"]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Decision Rules
|
|
138
|
+
|
|
139
|
+
| Change | Approach |
|
|
140
|
+
|--------|----------|
|
|
141
|
+
| New optional parameter | Add with default |
|
|
142
|
+
| New function | Add alongside old |
|
|
143
|
+
| Change return type | New function + deprecate old |
|
|
144
|
+
| Remove parameter | Deprecate → Major version remove |
|
|
145
|
+
| Change behavior | New function + deprecate old |
|
|
146
|
+
| Rename | Add alias + deprecate old |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Preferred Patterns
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
# Version-gated imports
|
|
154
|
+
# mypackage/__init__.py
|
|
155
|
+
__version__ = "2.1.0"
|
|
156
|
+
|
|
157
|
+
# V1 API (deprecated)
|
|
158
|
+
from .v1 import UserService as UserServiceV1
|
|
159
|
+
from .v1 import create_user as create_user_v1
|
|
160
|
+
|
|
161
|
+
# V2 API (current)
|
|
162
|
+
from .v2 import UserService
|
|
163
|
+
from .v2 import create_user
|
|
164
|
+
|
|
165
|
+
# Deprecated aliases
|
|
166
|
+
import warnings
|
|
167
|
+
warnings.warn(
|
|
168
|
+
"UserServiceV1 is deprecated, use UserService",
|
|
169
|
+
DeprecationWarning,
|
|
170
|
+
)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Avoid
|
|
176
|
+
|
|
177
|
+
- Silent breaking changes
|
|
178
|
+
- Removing public API in patch/minor
|
|
179
|
+
- Changing exception types
|
|
180
|
+
- Modifying default behavior
|
|
181
|
+
- Breaking `__all__` without major version
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Validation Considerations
|
|
186
|
+
|
|
187
|
+
- `pytest --collect-only` shows API surface
|
|
188
|
+
- `pip install -e . && python -c "import mypackage"` works
|
|
189
|
+
- Downstream consumers tested (if possible)
|
|
190
|
+
- Changelog documents all API changes
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Related Skills
|
|
195
|
+
|
|
196
|
+
- `refactoring/safe_refactoring.md`
|
|
197
|
+
- `refactoring/behavior_preservation.md`
|
|
198
|
+
- `engineering/pyproject_toml.md`
|
|
199
|
+
- `engineering/packaging.md`
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Refactoring: Safe Refactoring
|
|
2
|
+
|
|
3
|
+
**Purpose**: Refactoring rules that preserve behavior and minimize risk.
|
|
4
|
+
|
|
5
|
+
**When to use**: Any code restructuring, cleanup, or improvement.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Refactoring Principles
|
|
12
|
+
1. **Tests first** — No refactoring without tests
|
|
13
|
+
2. **Small steps** — One change at a time
|
|
14
|
+
3. **Run tests after each step** — Verify immediately
|
|
15
|
+
4. **Preserve behavior** — No functional changes
|
|
16
|
+
5. **Commit often** — Easy rollback
|
|
17
|
+
|
|
18
|
+
### Refactoring Workflow
|
|
19
|
+
```
|
|
20
|
+
1. RUN TESTS (baseline)
|
|
21
|
+
2. IDENTIFY refactoring goal
|
|
22
|
+
3. MAKE SMALLEST CHANGE
|
|
23
|
+
4. RUN TESTS
|
|
24
|
+
5. REPEAT 3-4 until goal achieved
|
|
25
|
+
6. RUN FULL TEST SUITE
|
|
26
|
+
7. REVIEW DIFF
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Safe Refactoring Patterns
|
|
30
|
+
|
|
31
|
+
#### Extract Function
|
|
32
|
+
```python
|
|
33
|
+
# Before
|
|
34
|
+
def process_order(order):
|
|
35
|
+
validate(order)
|
|
36
|
+
total = calculate_total(order.items)
|
|
37
|
+
tax = total * TAX_RATE
|
|
38
|
+
final = total + tax
|
|
39
|
+
save_order(order, final)
|
|
40
|
+
send_confirmation(order)
|
|
41
|
+
|
|
42
|
+
# After — extract calculation
|
|
43
|
+
def calculate_final_total(items):
|
|
44
|
+
total = calculate_total(items)
|
|
45
|
+
tax = total * TAX_RATE
|
|
46
|
+
return total + tax
|
|
47
|
+
|
|
48
|
+
def process_order(order):
|
|
49
|
+
validate(order)
|
|
50
|
+
final = calculate_final_total(order.items)
|
|
51
|
+
save_order(order, final)
|
|
52
|
+
send_confirmation(order)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### Extract Class
|
|
56
|
+
```python
|
|
57
|
+
# Before — mixed responsibilities
|
|
58
|
+
class OrderProcessor:
|
|
59
|
+
def process(self, order):
|
|
60
|
+
self.validate(order)
|
|
61
|
+
self.calculate_pricing(order)
|
|
62
|
+
self.save(order)
|
|
63
|
+
self.notify(order)
|
|
64
|
+
|
|
65
|
+
# After — separate concerns
|
|
66
|
+
class OrderValidator:
|
|
67
|
+
def validate(self, order): ...
|
|
68
|
+
|
|
69
|
+
class PricingCalculator:
|
|
70
|
+
def calculate(self, order): ...
|
|
71
|
+
|
|
72
|
+
class OrderRepository:
|
|
73
|
+
def save(self, order): ...
|
|
74
|
+
|
|
75
|
+
class NotificationService:
|
|
76
|
+
def notify(self, order): ...
|
|
77
|
+
|
|
78
|
+
class OrderProcessor:
|
|
79
|
+
def __init__(self, validator, calculator, repo, notifier):
|
|
80
|
+
self.validator = validator
|
|
81
|
+
self.calculator = calculator
|
|
82
|
+
self.repo = repo
|
|
83
|
+
self.notifier = notifier
|
|
84
|
+
|
|
85
|
+
def process(self, order):
|
|
86
|
+
self.validator.validate(order)
|
|
87
|
+
self.calculator.calculate(order)
|
|
88
|
+
self.repo.save(order)
|
|
89
|
+
self.notifier.notify(order)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Replace Conditional with Polymorphism
|
|
93
|
+
```python
|
|
94
|
+
# Before
|
|
95
|
+
def process_payment(payment):
|
|
96
|
+
if payment.type == "credit":
|
|
97
|
+
process_credit(payment)
|
|
98
|
+
elif payment.type == "debit":
|
|
99
|
+
process_debit(payment)
|
|
100
|
+
elif payment.type == "paypal":
|
|
101
|
+
process_paypal(payment)
|
|
102
|
+
|
|
103
|
+
# After
|
|
104
|
+
class PaymentProcessor(Protocol):
|
|
105
|
+
def process(self, payment): ...
|
|
106
|
+
|
|
107
|
+
class CreditProcessor:
|
|
108
|
+
def process(self, payment): ...
|
|
109
|
+
|
|
110
|
+
class DebitProcessor:
|
|
111
|
+
def process(self, payment): ...
|
|
112
|
+
|
|
113
|
+
class PayPalProcessor:
|
|
114
|
+
def process(self, payment): ...
|
|
115
|
+
|
|
116
|
+
PROCESSORS = {
|
|
117
|
+
"credit": CreditProcessor(),
|
|
118
|
+
"debit": DebitProcessor(),
|
|
119
|
+
"paypal": PayPalProcessor(),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
def process_payment(payment):
|
|
123
|
+
PROCESSORS[payment.type].process(payment)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Introduce Parameter Object
|
|
127
|
+
```python
|
|
128
|
+
# Before
|
|
129
|
+
def create_user(email, name, age, address, phone, preferences, referral):
|
|
130
|
+
...
|
|
131
|
+
|
|
132
|
+
# After
|
|
133
|
+
@dataclass
|
|
134
|
+
class UserData:
|
|
135
|
+
email: str
|
|
136
|
+
name: str
|
|
137
|
+
age: int
|
|
138
|
+
address: str
|
|
139
|
+
phone: str
|
|
140
|
+
preferences: dict
|
|
141
|
+
referral: str | None = None
|
|
142
|
+
|
|
143
|
+
def create_user(data: UserData):
|
|
144
|
+
...
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Decision Rules
|
|
150
|
+
|
|
151
|
+
| Refactoring | When Safe |
|
|
152
|
+
|-------------|-----------|
|
|
153
|
+
| Extract function | Pure logic, well-tested |
|
|
154
|
+
| Extract class | Clear responsibility boundary |
|
|
155
|
+
| Rename | IDE refactoring, all references updated |
|
|
156
|
+
| Move method | No behavior change |
|
|
157
|
+
| Replace conditional | Open/closed principle needed |
|
|
158
|
+
| Introduce parameter object | >4 related parameters |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Preferred Patterns
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# Strangler Fig for large refactors
|
|
166
|
+
# 1. Create new implementation alongside old
|
|
167
|
+
# 2. Route new calls to new implementation
|
|
168
|
+
# 3. Migrate callers one by one
|
|
169
|
+
# 4. Remove old implementation
|
|
170
|
+
|
|
171
|
+
# Feature flag for safe rollout
|
|
172
|
+
def process_order(order):
|
|
173
|
+
if settings.use_new_pricing:
|
|
174
|
+
return new_pricing.calculate(order)
|
|
175
|
+
return old_pricing.calculate(order)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Avoid
|
|
181
|
+
|
|
182
|
+
- Refactoring without tests
|
|
183
|
+
- Multiple changes in one step
|
|
184
|
+
- "While I'm here" changes
|
|
185
|
+
- Changing behavior during refactor
|
|
186
|
+
- Big bang refactoring
|
|
187
|
+
- Refactoring working code unnecessarily
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Validation Considerations
|
|
192
|
+
|
|
193
|
+
- Tests pass at every step
|
|
194
|
+
- `git diff` shows only refactoring
|
|
195
|
+
- No behavior changes in diff
|
|
196
|
+
- Performance regression check
|
|
197
|
+
- Code review focused on structure
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Related Skills
|
|
202
|
+
|
|
203
|
+
- `refactoring/incremental.md`
|
|
204
|
+
- `refactoring/behavior_preservation.md`
|
|
205
|
+
- `testing/regression_tests.md`
|
|
206
|
+
- `testing/organization.md`
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Security: Authentication/Authorization Boundaries
|
|
2
|
+
|
|
3
|
+
**Purpose**: Enforce auth boundaries correctly in code.
|
|
4
|
+
|
|
5
|
+
**When to use**: Any code with user identity, permissions, or access control.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Authentication vs Authorization
|
|
12
|
+
- **Authentication**: Who are you? (Identity)
|
|
13
|
+
- **Authorization**: What can you do? (Permissions)
|
|
14
|
+
|
|
15
|
+
### Never Trust Client-Side Identity
|
|
16
|
+
```python
|
|
17
|
+
# WRONG — trusting header
|
|
18
|
+
def get_user_profile(request, user_id: str):
|
|
19
|
+
return db.get_user(user_id) # User can access ANY profile!
|
|
20
|
+
|
|
21
|
+
# CORRECT — verified identity
|
|
22
|
+
def get_user_profile(request, current_user: User):
|
|
23
|
+
if current_user.id != request.path_params["user_id"]:
|
|
24
|
+
raise ForbiddenError()
|
|
25
|
+
return current_user
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Authorization at Boundary
|
|
29
|
+
```python
|
|
30
|
+
# Dependency injection (FastAPI example)
|
|
31
|
+
async def get_current_user(
|
|
32
|
+
token: str = Depends(oauth2_scheme),
|
|
33
|
+
user_service: UserService = Depends(),
|
|
34
|
+
) -> User:
|
|
35
|
+
user = await user_service.verify_token(token)
|
|
36
|
+
if not user:
|
|
37
|
+
raise UnauthorizedError()
|
|
38
|
+
return user
|
|
39
|
+
|
|
40
|
+
@app.get("/users/{user_id}")
|
|
41
|
+
async def get_user(
|
|
42
|
+
user_id: str,
|
|
43
|
+
current_user: User = Depends(get_current_user),
|
|
44
|
+
user_service: UserService = Depends(),
|
|
45
|
+
):
|
|
46
|
+
# Authorization check
|
|
47
|
+
if not current_user.can_access_user(user_id):
|
|
48
|
+
raise ForbiddenError("Cannot access this user")
|
|
49
|
+
return await user_service.get(user_id)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Permission Models
|
|
53
|
+
```python
|
|
54
|
+
# Role-based
|
|
55
|
+
class Role(str, Enum):
|
|
56
|
+
ADMIN = "admin"
|
|
57
|
+
USER = "user"
|
|
58
|
+
VIEWER = "viewer"
|
|
59
|
+
|
|
60
|
+
# Permission-based (more flexible)
|
|
61
|
+
class Permission(str, Enum):
|
|
62
|
+
USER_READ = "user:read"
|
|
63
|
+
USER_WRITE = "user:write"
|
|
64
|
+
ADMIN_PANEL = "admin:panel"
|
|
65
|
+
|
|
66
|
+
# User model
|
|
67
|
+
class User(BaseModel):
|
|
68
|
+
id: str
|
|
69
|
+
roles: list[Role] = []
|
|
70
|
+
permissions: list[Permission] = []
|
|
71
|
+
|
|
72
|
+
def has_permission(self, perm: Permission) -> bool:
|
|
73
|
+
return perm in self.permissions or Role.ADMIN in self.roles
|
|
74
|
+
|
|
75
|
+
def can_access_user(self, user_id: str) -> bool:
|
|
76
|
+
return self.id == user_id or self.has_permission(Permission.USER_READ)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Resource-Level Authorization
|
|
80
|
+
```python
|
|
81
|
+
# Policy-based
|
|
82
|
+
class AuthorizationPolicy:
|
|
83
|
+
def can_read(self, user: User, resource: Resource) -> bool:
|
|
84
|
+
if user.has_permission(Permission.ADMIN):
|
|
85
|
+
return True
|
|
86
|
+
return resource.owner_id == user.id or resource.is_public
|
|
87
|
+
|
|
88
|
+
def can_write(self, user: User, resource: Resource) -> bool:
|
|
89
|
+
if user.has_permission(Permission.ADMIN):
|
|
90
|
+
return True
|
|
91
|
+
return resource.owner_id == user.id
|
|
92
|
+
|
|
93
|
+
# Usage
|
|
94
|
+
policy = AuthorizationPolicy()
|
|
95
|
+
|
|
96
|
+
@app.put("/resources/{resource_id}")
|
|
97
|
+
async def update_resource(
|
|
98
|
+
resource_id: str,
|
|
99
|
+
data: ResourceUpdate,
|
|
100
|
+
current_user: User = Depends(get_current_user),
|
|
101
|
+
resource_service: ResourceService = Depends(),
|
|
102
|
+
):
|
|
103
|
+
resource = await resource_service.get(resource_id)
|
|
104
|
+
if not policy.can_write(current_user, resource):
|
|
105
|
+
raise ForbiddenError()
|
|
106
|
+
return await resource_service.update(resource_id, data)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Secure Session/Cookie Handling
|
|
110
|
+
```python
|
|
111
|
+
# HttpOnly, Secure, SameSite cookies
|
|
112
|
+
response.set_cookie(
|
|
113
|
+
"session",
|
|
114
|
+
session_token,
|
|
115
|
+
httponly=True,
|
|
116
|
+
secure=True, # HTTPS only
|
|
117
|
+
samesite="lax", # CSRF protection
|
|
118
|
+
max_age=3600,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# CSRF protection for state-changing operations
|
|
122
|
+
@app.post("/action")
|
|
123
|
+
async def action(
|
|
124
|
+
request: Request,
|
|
125
|
+
csrf_token: str = Form(),
|
|
126
|
+
session: Session = Depends(get_session),
|
|
127
|
+
):
|
|
128
|
+
if not verify_csrf(session, csrf_token):
|
|
129
|
+
raise ForbiddenError("Invalid CSRF token")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Decision Rules
|
|
135
|
+
|
|
136
|
+
| Boundary | Enforcement |
|
|
137
|
+
|----------|-------------|
|
|
138
|
+
| API endpoint | Dependency injection + policy check |
|
|
139
|
+
| Database query | Filter by user_id in query (not post-filter) |
|
|
140
|
+
| File access | Verify ownership before serve |
|
|
141
|
+
| Admin panel | Role check + audit log |
|
|
142
|
+
| Internal service | mTLS / service mesh / shared secret |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Preferred Patterns
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
# Centralized authorization
|
|
150
|
+
class AuthorizationService:
|
|
151
|
+
def __init__(self, policy: AuthorizationPolicy):
|
|
152
|
+
self.policy = policy
|
|
153
|
+
|
|
154
|
+
def authorize(self, user: User, action: str, resource: Resource) -> None:
|
|
155
|
+
if not self.policy.check(user, action, resource):
|
|
156
|
+
raise ForbiddenError(f"Cannot {action} {resource}")
|
|
157
|
+
|
|
158
|
+
# Decorator for endpoints
|
|
159
|
+
def require_permission(perm: Permission):
|
|
160
|
+
def decorator(func):
|
|
161
|
+
@wraps(func)
|
|
162
|
+
async def wrapper(*args, current_user: User = Depends(get_current_user), **kwargs):
|
|
163
|
+
if not current_user.has_permission(perm):
|
|
164
|
+
raise ForbiddenError(f"Requires {perm}")
|
|
165
|
+
return await func(*args, current_user=current_user, **kwargs)
|
|
166
|
+
return wrapper
|
|
167
|
+
return decorator
|
|
168
|
+
|
|
169
|
+
@require_permission(Permission.USER_WRITE)
|
|
170
|
+
async def create_user(...):
|
|
171
|
+
...
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Avoid
|
|
177
|
+
|
|
178
|
+
- Checking permissions after fetching data (leak via error messages)
|
|
179
|
+
- Client-controlled authorization (user_id in body/query)
|
|
180
|
+
- Missing authorization on any state-changing endpoint
|
|
181
|
+
- Hardcoded role checks (`if user.role == "admin"`) instead of permissions
|
|
182
|
+
- No audit logging for sensitive operations
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Validation Considerations
|
|
187
|
+
|
|
188
|
+
- Test with different user roles
|
|
189
|
+
- Test resource access boundaries
|
|
190
|
+
- Test privilege escalation attempts
|
|
191
|
+
- Audit log review
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Related Skills
|
|
196
|
+
|
|
197
|
+
- `security/input_validation.md`
|
|
198
|
+
- `security/secrets.md`
|
|
199
|
+
- `generation/error_handling.md`
|
|
200
|
+
- `engineering/http_clients.md`
|