auth-gate 0.2.0__tar.gz → 0.2.3__tar.gz
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.
- {auth_gate-0.2.0/src/auth_gate.egg-info → auth_gate-0.2.3}/PKG-INFO +39 -1
- {auth_gate-0.2.0 → auth_gate-0.2.3}/README.md +38 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/pyproject.toml +1 -1
- {auth_gate-0.2.0 → auth_gate-0.2.3}/setup.cfg +1 -1
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/auth_gate/__init__.py +37 -5
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/auth_gate/middleware.py +192 -5
- auth_gate-0.2.3/src/auth_gate/s2s_auth.py +352 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/auth_gate/user_auth.py +1 -1
- {auth_gate-0.2.0 → auth_gate-0.2.3/src/auth_gate.egg-info}/PKG-INFO +39 -1
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/auth_gate.egg-info/SOURCES.txt +2 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/tests/conftest.py +19 -3
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/tests/test_intergration.py +2 -2
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/tests/test_middleware.py +344 -1
- auth_gate-0.2.3/src/tests/test_s2s_auth.py +295 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/tests/test_user_auth.py +1 -1
- {auth_gate-0.2.0 → auth_gate-0.2.3}/LICENSE +0 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/auth_gate/config.py +0 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/auth_gate/fastapi_utils.py +0 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/auth_gate/schemas.py +0 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/auth_gate.egg-info/dependency_links.txt +0 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/auth_gate.egg-info/requires.txt +0 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/auth_gate.egg-info/top_level.txt +0 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/tests/__init__.py +0 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/tests/test_config.py +0 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/tests/test_fastapi_utils.py +0 -0
- {auth_gate-0.2.0 → auth_gate-0.2.3}/src/tests/test_schema.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: auth-gate
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Enterprise-grade authentication for microservices with Kong and Keycloak integration
|
|
5
5
|
Home-page: https://github.com/tradelink-org/auth-gate
|
|
6
6
|
Author: Brian Mburu
|
|
@@ -290,6 +290,44 @@ app.add_middleware(
|
|
|
290
290
|
)
|
|
291
291
|
```
|
|
292
292
|
|
|
293
|
+
### Parameterized Paths with UUID Matching
|
|
294
|
+
|
|
295
|
+
You can exclude or make paths optional using UUID v4 parameters:
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
app.add_middleware(
|
|
299
|
+
AuthMiddleware,
|
|
300
|
+
excluded_paths={
|
|
301
|
+
"/api/v1/categories/{category_id:uuid}": {"GET"}, # Public read
|
|
302
|
+
"/api/v1/products/{product_id:uuid}": {"GET"},
|
|
303
|
+
},
|
|
304
|
+
excluded_prefixes={
|
|
305
|
+
"/api/{version:uuid}": {"GET"}, # Version-specific docs
|
|
306
|
+
},
|
|
307
|
+
optional_auth_paths={
|
|
308
|
+
"/api/v1/recommendations/{user_id:uuid}": {"GET"}, # Personalized if authenticated
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Pattern Syntax:**
|
|
314
|
+
- `{param:uuid}` - Matches valid UUID v4 format (case-insensitive)
|
|
315
|
+
- Works with exact paths, prefixes, and optional auth paths
|
|
316
|
+
- Supports method-specific exclusions
|
|
317
|
+
- Exact matches take precedence over patterns
|
|
318
|
+
|
|
319
|
+
**Example Behavior:**
|
|
320
|
+
```python
|
|
321
|
+
# Matches: /api/v1/categories/7b5bcc8f-2c99-43c0-9c7d-e27c10881bd2
|
|
322
|
+
# Does not match: /api/v1/categories/invalid-id
|
|
323
|
+
# Does not match: /api/v1/categories/all
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**UUID v4 Validation:**
|
|
327
|
+
- Must have version digit "4" in the correct position
|
|
328
|
+
- Must have variant bits (8, 9, a, or b) in the correct position
|
|
329
|
+
- Accepts uppercase, lowercase, or mixed case
|
|
330
|
+
|
|
293
331
|
### Direct Validator Usage
|
|
294
332
|
|
|
295
333
|
```python
|
|
@@ -249,6 +249,44 @@ app.add_middleware(
|
|
|
249
249
|
)
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
+
### Parameterized Paths with UUID Matching
|
|
253
|
+
|
|
254
|
+
You can exclude or make paths optional using UUID v4 parameters:
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
app.add_middleware(
|
|
258
|
+
AuthMiddleware,
|
|
259
|
+
excluded_paths={
|
|
260
|
+
"/api/v1/categories/{category_id:uuid}": {"GET"}, # Public read
|
|
261
|
+
"/api/v1/products/{product_id:uuid}": {"GET"},
|
|
262
|
+
},
|
|
263
|
+
excluded_prefixes={
|
|
264
|
+
"/api/{version:uuid}": {"GET"}, # Version-specific docs
|
|
265
|
+
},
|
|
266
|
+
optional_auth_paths={
|
|
267
|
+
"/api/v1/recommendations/{user_id:uuid}": {"GET"}, # Personalized if authenticated
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Pattern Syntax:**
|
|
273
|
+
- `{param:uuid}` - Matches valid UUID v4 format (case-insensitive)
|
|
274
|
+
- Works with exact paths, prefixes, and optional auth paths
|
|
275
|
+
- Supports method-specific exclusions
|
|
276
|
+
- Exact matches take precedence over patterns
|
|
277
|
+
|
|
278
|
+
**Example Behavior:**
|
|
279
|
+
```python
|
|
280
|
+
# Matches: /api/v1/categories/7b5bcc8f-2c99-43c0-9c7d-e27c10881bd2
|
|
281
|
+
# Does not match: /api/v1/categories/invalid-id
|
|
282
|
+
# Does not match: /api/v1/categories/all
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**UUID v4 Validation:**
|
|
286
|
+
- Must have version digit "4" in the correct position
|
|
287
|
+
- Must have variant bits (8, 9, a, or b) in the correct position
|
|
288
|
+
- Accepts uppercase, lowercase, or mixed case
|
|
289
|
+
|
|
252
290
|
### Direct Validator Usage
|
|
253
291
|
|
|
254
292
|
```python
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "auth-gate"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "Enterprise-grade authentication for microservices with Kong and Keycloak integration"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -4,41 +4,67 @@ Tradelink Authentication Client
|
|
|
4
4
|
Enterprise authentication client for microservices with Kong/Keycloak integration.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from .config import AuthMode, AuthSettings
|
|
7
|
+
from .config import AuthMode, AuthSettings, get_settings, reset_settings
|
|
8
8
|
from .fastapi_utils import (
|
|
9
9
|
get_current_auth,
|
|
10
10
|
get_current_service,
|
|
11
11
|
get_current_user,
|
|
12
12
|
get_optional_auth,
|
|
13
13
|
get_optional_user,
|
|
14
|
+
is_bypass_mode,
|
|
15
|
+
is_using_keycloak,
|
|
16
|
+
is_using_kong,
|
|
14
17
|
require_admin,
|
|
15
18
|
require_customer,
|
|
16
19
|
require_moderator,
|
|
17
20
|
require_roles,
|
|
18
21
|
require_scopes,
|
|
22
|
+
require_service_roles,
|
|
19
23
|
require_supplier,
|
|
20
24
|
require_supplier_or_admin,
|
|
21
25
|
require_user_admin,
|
|
22
26
|
require_user_customer,
|
|
23
27
|
require_user_moderator,
|
|
28
|
+
require_user_roles,
|
|
24
29
|
require_user_supplier,
|
|
25
30
|
verify_hmac_signature,
|
|
26
31
|
)
|
|
27
32
|
from .middleware import AuthMiddleware
|
|
28
|
-
from .
|
|
29
|
-
|
|
33
|
+
from .s2s_auth import (
|
|
34
|
+
CircuitBreaker,
|
|
35
|
+
CircuitBreakerOpenError,
|
|
36
|
+
CircuitState,
|
|
37
|
+
ServiceAuthClient,
|
|
38
|
+
ServiceToken,
|
|
39
|
+
get_service_auth_client,
|
|
40
|
+
)
|
|
41
|
+
from .schemas import AuthContext, BaseAuthContext, ServiceContext, UserContext
|
|
42
|
+
from .user_auth import HMACVerifier, UserValidator, get_user_validator
|
|
30
43
|
|
|
31
|
-
__version__ = "0.2.
|
|
44
|
+
__version__ = "0.2.3"
|
|
32
45
|
|
|
33
46
|
__all__ = [
|
|
34
47
|
# Configuration
|
|
35
48
|
"AuthSettings",
|
|
36
49
|
"AuthMode",
|
|
37
|
-
|
|
50
|
+
"get_settings",
|
|
51
|
+
"reset_settings",
|
|
52
|
+
# Schemas & Type Aliases
|
|
38
53
|
"UserContext",
|
|
39
54
|
"ServiceContext",
|
|
55
|
+
"AuthContext",
|
|
56
|
+
"BaseAuthContext",
|
|
40
57
|
# User Authentication
|
|
41
58
|
"UserValidator",
|
|
59
|
+
"HMACVerifier",
|
|
60
|
+
"get_user_validator",
|
|
61
|
+
# Service-to-Service
|
|
62
|
+
"ServiceAuthClient",
|
|
63
|
+
"CircuitBreaker",
|
|
64
|
+
"CircuitBreakerOpenError",
|
|
65
|
+
"CircuitState",
|
|
66
|
+
"ServiceToken",
|
|
67
|
+
"get_service_auth_client",
|
|
42
68
|
# Middleware
|
|
43
69
|
"AuthMiddleware",
|
|
44
70
|
# FastAPI Dependencies
|
|
@@ -48,6 +74,8 @@ __all__ = [
|
|
|
48
74
|
"get_optional_auth",
|
|
49
75
|
"get_optional_user",
|
|
50
76
|
"require_roles",
|
|
77
|
+
"require_user_roles",
|
|
78
|
+
"require_service_roles",
|
|
51
79
|
"require_scopes",
|
|
52
80
|
"require_admin",
|
|
53
81
|
"require_supplier",
|
|
@@ -59,4 +87,8 @@ __all__ = [
|
|
|
59
87
|
"require_user_customer",
|
|
60
88
|
"require_user_supplier",
|
|
61
89
|
"require_user_moderator",
|
|
90
|
+
# Mode Utilities
|
|
91
|
+
"is_using_kong",
|
|
92
|
+
"is_using_keycloak",
|
|
93
|
+
"is_bypass_mode",
|
|
62
94
|
]
|
|
@@ -3,8 +3,9 @@ FastAPI middleware for authentication (with service-to-service support)
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
+
import re
|
|
6
7
|
import time
|
|
7
|
-
from typing import Dict, Optional, Set, Union
|
|
8
|
+
from typing import Dict, List, Optional, Pattern, Set, Tuple, Union
|
|
8
9
|
|
|
9
10
|
from fastapi import HTTPException, Request, status
|
|
10
11
|
from fastapi.responses import JSONResponse
|
|
@@ -16,6 +17,12 @@ from .user_auth import get_user_validator
|
|
|
16
17
|
|
|
17
18
|
logger = logging.getLogger(__name__)
|
|
18
19
|
|
|
20
|
+
# UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
|
21
|
+
# where y is 8, 9, a, or b (variant bits)
|
|
22
|
+
TYPE_PATTERNS = {
|
|
23
|
+
"uuid": r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}",
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
|
|
20
27
|
class AuthMiddleware(BaseHTTPMiddleware):
|
|
21
28
|
"""
|
|
@@ -73,6 +80,16 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|
|
73
80
|
# Paths where authentication is optional
|
|
74
81
|
self.optional_auth_dict = self._normalize_paths(optional_auth_paths or set())
|
|
75
82
|
|
|
83
|
+
# Compile parameterized patterns
|
|
84
|
+
self._excluded_patterns = self._compile_patterns(self.excluded_paths_dict)
|
|
85
|
+
self._excluded_prefix_patterns = self._compile_patterns(
|
|
86
|
+
self.excluded_prefixes_dict, is_prefix=True
|
|
87
|
+
)
|
|
88
|
+
self._optional_patterns = self._compile_patterns(self.optional_auth_dict)
|
|
89
|
+
|
|
90
|
+
# Remove parameterized paths from literal dicts (they're now in pattern lists)
|
|
91
|
+
self._remove_parameterized_from_dicts()
|
|
92
|
+
|
|
76
93
|
self.validator = get_user_validator()
|
|
77
94
|
self.settings = get_settings()
|
|
78
95
|
|
|
@@ -94,25 +111,195 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|
|
94
111
|
return normalized
|
|
95
112
|
raise ValueError("Invalid type for paths: must be set or dict")
|
|
96
113
|
|
|
114
|
+
@staticmethod
|
|
115
|
+
def _has_parameters(path: str) -> bool:
|
|
116
|
+
"""Check if path contains parameter placeholders like {param:type}"""
|
|
117
|
+
return "{" in path and "}" in path
|
|
118
|
+
|
|
119
|
+
def _compile_path_pattern(self, path: str, is_prefix: bool = False) -> Pattern:
|
|
120
|
+
"""
|
|
121
|
+
Compile a parameterized path pattern to regex.
|
|
122
|
+
|
|
123
|
+
Supports syntax: /path/{param:type}/more
|
|
124
|
+
|
|
125
|
+
Types:
|
|
126
|
+
- uuid: UUID v4 format (case-insensitive)
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
path: Path pattern with {param:type} placeholders
|
|
130
|
+
is_prefix: If True, don't anchor end of pattern (for prefix matching)
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Compiled regex pattern
|
|
134
|
+
|
|
135
|
+
Raises:
|
|
136
|
+
ValueError: If syntax is invalid or type is unsupported
|
|
137
|
+
|
|
138
|
+
Examples:
|
|
139
|
+
>>> _compile_path_pattern('/api/{id:uuid}')
|
|
140
|
+
re.compile(r'^/api/[0-9a-f]{8}-...$', re.IGNORECASE)
|
|
141
|
+
|
|
142
|
+
>>> _compile_path_pattern('/api/{version:uuid}', is_prefix=True)
|
|
143
|
+
re.compile(r'^/api/[0-9a-f]{8}-...', re.IGNORECASE)
|
|
144
|
+
"""
|
|
145
|
+
# Pattern to find parameter placeholders: {param:type}
|
|
146
|
+
param_pattern = r"\{([^}:]+)(?::([^}]+))?\}"
|
|
147
|
+
|
|
148
|
+
def replace_param(match: re.Match) -> str:
|
|
149
|
+
param_name = match.group(1).strip()
|
|
150
|
+
param_type = match.group(2).strip() if match.group(2) else None
|
|
151
|
+
|
|
152
|
+
if param_type is None:
|
|
153
|
+
raise ValueError(
|
|
154
|
+
f"Parameter '{{{param_name}}}' missing type annotation. "
|
|
155
|
+
f"Use {{param:type}} syntax (e.g., {{id:uuid}})"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if param_type not in TYPE_PATTERNS:
|
|
159
|
+
supported = ", ".join(TYPE_PATTERNS.keys())
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"Unsupported type '{param_type}' for parameter '{{{param_name}}}'. "
|
|
162
|
+
f"Supported types: {supported}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return f"({TYPE_PATTERNS[param_type]})"
|
|
166
|
+
|
|
167
|
+
# Escape special regex characters in path
|
|
168
|
+
escaped_path = re.escape(path)
|
|
169
|
+
|
|
170
|
+
# Unescape { and } for parameter replacement
|
|
171
|
+
escaped_path = escaped_path.replace(r"\{", "{").replace(r"\}", "}")
|
|
172
|
+
|
|
173
|
+
# Replace parameters with regex patterns
|
|
174
|
+
regex_pattern = re.sub(param_pattern, replace_param, escaped_path)
|
|
175
|
+
|
|
176
|
+
# Anchor pattern
|
|
177
|
+
if is_prefix:
|
|
178
|
+
regex_pattern = f"^{regex_pattern}"
|
|
179
|
+
else:
|
|
180
|
+
regex_pattern = f"^{regex_pattern}$"
|
|
181
|
+
|
|
182
|
+
# Compile with case-insensitive flag for UUIDs
|
|
183
|
+
try:
|
|
184
|
+
return re.compile(regex_pattern, re.IGNORECASE)
|
|
185
|
+
except re.error as e:
|
|
186
|
+
raise ValueError(f"Failed to compile pattern for path '{path}': {e}")
|
|
187
|
+
|
|
188
|
+
def _compile_patterns(
|
|
189
|
+
self, paths_dict: Dict[str, Optional[Set[str]]], is_prefix: bool = False
|
|
190
|
+
) -> List[Tuple[Pattern, Optional[Set[str]]]]:
|
|
191
|
+
"""
|
|
192
|
+
Extract parameterized paths and compile them to regex patterns.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
paths_dict: Dictionary of paths with optional method restrictions
|
|
196
|
+
is_prefix: If True, compile as prefix patterns (don't anchor end)
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List of (compiled_pattern, methods) tuples
|
|
200
|
+
|
|
201
|
+
Raises:
|
|
202
|
+
ValueError: If pattern syntax is invalid or type is unsupported
|
|
203
|
+
"""
|
|
204
|
+
patterns = []
|
|
205
|
+
|
|
206
|
+
for path, methods in paths_dict.items():
|
|
207
|
+
if self._has_parameters(path):
|
|
208
|
+
try:
|
|
209
|
+
compiled_pattern = self._compile_path_pattern(path, is_prefix)
|
|
210
|
+
patterns.append((compiled_pattern, methods))
|
|
211
|
+
except ValueError as e:
|
|
212
|
+
raise ValueError(f"Invalid pattern in path '{path}': {e}")
|
|
213
|
+
|
|
214
|
+
return patterns
|
|
215
|
+
|
|
216
|
+
def _remove_parameterized_from_dicts(self):
|
|
217
|
+
"""
|
|
218
|
+
Remove parameterized paths from literal path dictionaries.
|
|
219
|
+
They are now stored in pattern lists.
|
|
220
|
+
"""
|
|
221
|
+
for path_dict in [
|
|
222
|
+
self.excluded_paths_dict,
|
|
223
|
+
self.excluded_prefixes_dict,
|
|
224
|
+
self.optional_auth_dict,
|
|
225
|
+
]:
|
|
226
|
+
parameterized_keys = [key for key in path_dict.keys() if self._has_parameters(key)]
|
|
227
|
+
for key in parameterized_keys:
|
|
228
|
+
del path_dict[key]
|
|
229
|
+
|
|
97
230
|
def is_excluded(self, path: str, method: str) -> bool:
|
|
98
|
-
"""
|
|
99
|
-
|
|
231
|
+
"""
|
|
232
|
+
Check if path is excluded from authentication for the given method.
|
|
233
|
+
|
|
234
|
+
Matching order (highest to lowest priority):
|
|
235
|
+
1. Exact literal match in excluded_paths
|
|
236
|
+
2. Parameterized pattern match in excluded_paths
|
|
237
|
+
3. Prefix literal match in excluded_prefixes
|
|
238
|
+
4. Prefix pattern match in excluded_prefixes
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
path: Request path (e.g., '/api/v1/categories/7b5bcc8f-...')
|
|
242
|
+
method: HTTP method (e.g., 'GET', 'POST')
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
True if path is excluded from authentication for this method
|
|
246
|
+
"""
|
|
247
|
+
# 1. Exact literal match (O(1) dict lookup)
|
|
100
248
|
for p, methods in self.excluded_paths_dict.items():
|
|
101
249
|
if path == p and (methods is None or method in methods):
|
|
250
|
+
logger.debug(f"Exact match excluded: {path} [{method}]")
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
# 2. Parameterized pattern match (O(n) pattern checks)
|
|
254
|
+
for pattern, methods in self._excluded_patterns:
|
|
255
|
+
if pattern.match(path) and (methods is None or method in methods):
|
|
256
|
+
logger.debug(f"Pattern match excluded: {path} [{method}] via {pattern.pattern}")
|
|
102
257
|
return True
|
|
103
258
|
|
|
104
|
-
# Prefix match
|
|
259
|
+
# 3. Prefix literal match (O(n) prefix checks)
|
|
105
260
|
for p, methods in self.excluded_prefixes_dict.items():
|
|
106
261
|
if path.startswith(p) and (methods is None or method in methods):
|
|
262
|
+
logger.debug(f"Prefix match excluded: {path} [{method}]")
|
|
263
|
+
return True
|
|
264
|
+
|
|
265
|
+
# 4. Prefix pattern match (O(n) pattern checks)
|
|
266
|
+
for pattern, methods in self._excluded_prefix_patterns:
|
|
267
|
+
# For prefix patterns, check if path starts with pattern match
|
|
268
|
+
match = pattern.match(path)
|
|
269
|
+
if match and (methods is None or method in methods):
|
|
270
|
+
logger.debug(
|
|
271
|
+
f"Prefix pattern match excluded: {path} [{method}] via {pattern.pattern}"
|
|
272
|
+
)
|
|
107
273
|
return True
|
|
108
274
|
|
|
109
275
|
return False
|
|
110
276
|
|
|
111
277
|
def is_optional_auth(self, path: str, method: str) -> bool:
|
|
112
|
-
"""
|
|
278
|
+
"""
|
|
279
|
+
Check if path has optional authentication for the given method.
|
|
280
|
+
|
|
281
|
+
Checks both literal paths and parameterized patterns.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
path: Request path
|
|
285
|
+
method: HTTP method
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
True if authentication is optional for this path and method
|
|
289
|
+
"""
|
|
290
|
+
# Exact literal match
|
|
113
291
|
for p, methods in self.optional_auth_dict.items():
|
|
114
292
|
if (path == p) and (methods is None or method in methods):
|
|
115
293
|
return True
|
|
294
|
+
|
|
295
|
+
# Parameterized pattern match
|
|
296
|
+
for pattern, methods in self._optional_patterns:
|
|
297
|
+
if pattern.match(path) and (methods is None or method in methods):
|
|
298
|
+
logger.debug(
|
|
299
|
+
f"Pattern match optional auth: {path} [{method}] via {pattern.pattern}"
|
|
300
|
+
)
|
|
301
|
+
return True
|
|
302
|
+
|
|
116
303
|
return False
|
|
117
304
|
|
|
118
305
|
async def dispatch(self, request: Request, call_next):
|