api-foundry-query-engine 0.8.74__tar.gz → 0.8.77__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.
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/PKG-INFO +1 -1
- api_foundry_query_engine-0.8.77/api_foundry_query_engine/__init__.py +1 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/connectors/connection_factory.py +20 -10
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/dao/sql_query_handler.py +18 -1
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/operation.py +38 -2
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/utils/token_decoder.py +20 -67
- api_foundry_query_engine-0.8.74/api_foundry_query_engine/__init__.py +0 -1
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/.gitignore +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/LICENSE +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/.pre-commit-config.yaml +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/adapters/adapter.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/adapters/gateway_adapter.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/connectors/connection.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/connectors/postgres_connection.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/dao/batch_operation_handler.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/dao/dao.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/dao/operation_dao.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/dao/sql_custom_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/dao/sql_delete_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/dao/sql_insert_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/dao/sql_restore_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/dao/sql_select_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/dao/sql_subselect_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/dao/sql_update_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/lambda_handler.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/services/service.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/services/transactional_service.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/utils/api_model.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/utils/app_exception.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/utils/claims_check.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/utils/dependency_resolver.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/utils/logger.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/api_foundry_query_engine/utils/reference_resolver.py +0 -0
- {api_foundry_query_engine-0.8.74 → api_foundry_query_engine-0.8.77}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: api-foundry-query-engine
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.77
|
|
4
4
|
Summary: The AWS lambda service handler use by the `api_foundry` project is a powerful tool designed to automate the deployment of REST APIs on AWS using Lambda services to access and interact with relational databases (RDBMS). This project leverages the OpenAPI specification to define and manage the APIs
|
|
5
5
|
Project-URL: Documentation, https://github.com/DanRepik/api-foundry
|
|
6
6
|
Project-URL: Source, https://github.com/DanRepik/api-foundry
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.77"
|
|
@@ -35,17 +35,27 @@ class ConnectionFactory:
|
|
|
35
35
|
log.info("database: %s", database)
|
|
36
36
|
db_config = self.db_config_map.get(database)
|
|
37
37
|
if not db_config:
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if
|
|
46
|
-
db_config = self.
|
|
38
|
+
# A <DATABASE>_DSN config value (e.g. CONTRACT_DB_DSN) skips
|
|
39
|
+
# Secrets Manager entirely. This matters for a Lambda that's
|
|
40
|
+
# VPC-attached for private database access but has no route
|
|
41
|
+
# to the Secrets Manager API (no NAT gateway, no interface
|
|
42
|
+
# endpoint) -- the SECRETS/get_secret_value path would just
|
|
43
|
+
# hang. PostgresConnection already accepts a raw "dsn" key.
|
|
44
|
+
dsn = self.config.get(f"{database.upper()}_DSN")
|
|
45
|
+
if dsn:
|
|
46
|
+
db_config = {"engine": self.config.get(f"{database.upper()}_ENGINE", "postgres"), "dsn": dsn}
|
|
47
47
|
else:
|
|
48
|
-
|
|
48
|
+
# Use config dict for secrets
|
|
49
|
+
secrets_map = self.config.get("SECRETS", {})
|
|
50
|
+
if isinstance(secrets_map, str):
|
|
51
|
+
secrets_map = json.loads(secrets_map)
|
|
52
|
+
secret_name = secrets_map.get(database)
|
|
53
|
+
log.debug("secret_name: %s", secret_name)
|
|
54
|
+
|
|
55
|
+
if secret_name:
|
|
56
|
+
db_config = self.__get_secret(secret_name)
|
|
57
|
+
else:
|
|
58
|
+
raise ValueError(f"Secret not found for database: {database}")
|
|
49
59
|
|
|
50
60
|
engine = db_config.get("engine")
|
|
51
61
|
if not engine:
|
|
@@ -208,7 +208,24 @@ class SQLQueryHandler:
|
|
|
208
208
|
action_permissions,
|
|
209
209
|
)
|
|
210
210
|
|
|
211
|
+
# A `public` rule means "world-readable regardless of who's
|
|
212
|
+
# calling" -- apply it unconditionally rather than only when the
|
|
213
|
+
# caller's own role list happens to literally contain the string
|
|
214
|
+
# "public" (which previously only happened by accident, via the
|
|
215
|
+
# anonymous-role fallback for token-less requests; an
|
|
216
|
+
# authenticated caller hitting a nominally-public resource would
|
|
217
|
+
# otherwise be filtered down to zero properties).
|
|
218
|
+
public_permissions = action_permissions.get("public", {})
|
|
219
|
+
if public_permissions:
|
|
220
|
+
regex_pattern = self._extract_permission_pattern(public_permissions)
|
|
221
|
+
if regex_pattern:
|
|
222
|
+
public_allowed = self._filter_properties_by_regex(properties, regex_pattern)
|
|
223
|
+
log.info("public rule, pattern: %s, matched: %s", regex_pattern, list(public_allowed.keys()))
|
|
224
|
+
allowed_properties.update(public_allowed)
|
|
225
|
+
|
|
211
226
|
for role in self.operation.roles:
|
|
227
|
+
if role == "public":
|
|
228
|
+
continue # already applied unconditionally above
|
|
212
229
|
role_permissions = action_permissions.get(role, {})
|
|
213
230
|
log.info("role: %s, role_permissions: %s", role, role_permissions)
|
|
214
231
|
|
|
@@ -359,7 +376,7 @@ class SQLQueryHandler:
|
|
|
359
376
|
]
|
|
360
377
|
sql = f"{column} {'NOT ' if operand == 'not-in' else ''}IN ({', '.join(assignments)})" # noqa E501
|
|
361
378
|
elif operand in ["like", "not-like"]:
|
|
362
|
-
sql = f"{column} {'NOT ' if operand == 'not-like' else ''}LIKE {self.placeholder(property, str(placeholder_name))}"
|
|
379
|
+
sql = f"{column} {'NOT ' if operand == 'not-like' else ''}LIKE {self.placeholder(property, str(placeholder_name))}" # noqa E501
|
|
363
380
|
else:
|
|
364
381
|
sql = f"{column} {operand} {self.placeholder(property, str(placeholder_name))}"
|
|
365
382
|
return sql
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from typing import Any, Dict, List, Optional
|
|
2
3
|
|
|
3
4
|
from api_foundry_query_engine.utils.logger import logger
|
|
@@ -95,8 +96,43 @@ class Operation:
|
|
|
95
96
|
|
|
96
97
|
@property
|
|
97
98
|
def roles(self) -> List[str]:
|
|
98
|
-
"""Get the roles from claims.
|
|
99
|
-
|
|
99
|
+
"""Get the roles from claims.
|
|
100
|
+
|
|
101
|
+
Falls back to a singular `role` claim plus `groups` when the
|
|
102
|
+
plural `roles` key itself isn't present -- some authorizers (e.g.
|
|
103
|
+
a custom Lambda TOKEN authorizer following AWS's own convention
|
|
104
|
+
of a `role` string and a `groups` list) never populate `roles`,
|
|
105
|
+
which previously made every such caller look role-less even with
|
|
106
|
+
a perfectly valid, correctly-scoped token. `groups` may arrive as
|
|
107
|
+
a real list or, when it passed through an API Gateway authorizer
|
|
108
|
+
context (whose values must be strings), a JSON-encoded string.
|
|
109
|
+
"""
|
|
110
|
+
if not self.claims:
|
|
111
|
+
return []
|
|
112
|
+
roles = self.claims.get("roles")
|
|
113
|
+
if roles:
|
|
114
|
+
return list(roles) if isinstance(roles, list) else [str(roles)]
|
|
115
|
+
|
|
116
|
+
fallback: List[str] = []
|
|
117
|
+
role = self.claims.get("role")
|
|
118
|
+
if role:
|
|
119
|
+
fallback.append(str(role))
|
|
120
|
+
|
|
121
|
+
groups_raw = self.claims.get("groups")
|
|
122
|
+
groups: List[str] = []
|
|
123
|
+
if isinstance(groups_raw, str) and groups_raw.strip():
|
|
124
|
+
try:
|
|
125
|
+
parsed = json.loads(groups_raw)
|
|
126
|
+
groups = [str(item) for item in parsed] if isinstance(parsed, list) else [groups_raw]
|
|
127
|
+
except (TypeError, ValueError):
|
|
128
|
+
groups = [groups_raw]
|
|
129
|
+
elif isinstance(groups_raw, list):
|
|
130
|
+
groups = [str(item) for item in groups_raw]
|
|
131
|
+
|
|
132
|
+
for group in groups:
|
|
133
|
+
if group not in fallback:
|
|
134
|
+
fallback.append(group)
|
|
135
|
+
return fallback
|
|
100
136
|
|
|
101
137
|
def subject(self) -> Optional[str]:
|
|
102
138
|
"""Get the subject from claims."""
|
|
@@ -33,9 +33,7 @@ def _log_jwt_configuration():
|
|
|
33
33
|
log.debug("=== JWT Configuration Debug ===")
|
|
34
34
|
log.debug("JWKS_HOST: %s", os.getenv("JWKS_HOST", "NOT_SET"))
|
|
35
35
|
log.debug("JWT_ISSUER: %s", os.getenv("JWT_ISSUER", "NOT_SET"))
|
|
36
|
-
log.debug(
|
|
37
|
-
"JWT_ALLOWED_AUDIENCES: %s", os.getenv("JWT_ALLOWED_AUDIENCES", "NOT_SET")
|
|
38
|
-
)
|
|
36
|
+
log.debug("JWT_ALLOWED_AUDIENCES: %s", os.getenv("JWT_ALLOWED_AUDIENCES", "NOT_SET"))
|
|
39
37
|
log.debug("ANONYMOUS_ROLE: %s", os.getenv("ANONYMOUS_ROLE", "NOT_SET"))
|
|
40
38
|
log.debug(
|
|
41
39
|
"TOKEN_VALIDATOR_LAMBDA_ARN: %s",
|
|
@@ -65,9 +63,7 @@ class LambdaTokenValidator:
|
|
|
65
63
|
log.error("boto3 not available for Lambda token validation")
|
|
66
64
|
raise ImportError("boto3 is required for Lambda token validation")
|
|
67
65
|
|
|
68
|
-
def validate(
|
|
69
|
-
self, token: str, method_arn: str = "arn:aws:execute-api:*:*:*"
|
|
70
|
-
) -> Dict[str, Any]:
|
|
66
|
+
def validate(self, token: str, method_arn: str = "arn:aws:execute-api:*:*:*") -> Dict[str, Any]:
|
|
71
67
|
"""
|
|
72
68
|
Invoke AWS Lambda TOKEN authorizer.
|
|
73
69
|
|
|
@@ -129,11 +125,7 @@ def _extract_event_entity_and_action(event: Dict[str, Any]) -> tuple[Optional[st
|
|
|
129
125
|
resource = event.get("resource") or event.get("path") or ""
|
|
130
126
|
parts = [p for p in str(resource).split("/") if p]
|
|
131
127
|
api_prefixes = {"api", "v1", "v2", "v3"}
|
|
132
|
-
literal_parts = [
|
|
133
|
-
p
|
|
134
|
-
for p in parts
|
|
135
|
-
if p not in api_prefixes and not (p.startswith("{") and p.endswith("}"))
|
|
136
|
-
]
|
|
128
|
+
literal_parts = [p for p in parts if p not in api_prefixes and not (p.startswith("{") and p.endswith("}"))]
|
|
137
129
|
entity = "_".join(literal_parts) if literal_parts else None
|
|
138
130
|
method = str(event.get("httpMethod", "")).upper()
|
|
139
131
|
action = METHOD_TO_ACTION.get(method, "read")
|
|
@@ -194,9 +186,7 @@ def _route_explicitly_allows_role(event: Dict[str, Any], role: Optional[str]) ->
|
|
|
194
186
|
|
|
195
187
|
_, action = _extract_event_entity_and_action(event)
|
|
196
188
|
permissions = _normalize_permissions(_get_route_permissions(event))
|
|
197
|
-
action_permissions = permissions.get("default", {}).get(
|
|
198
|
-
_normalize_permission_action(action), {}
|
|
199
|
-
)
|
|
189
|
+
action_permissions = permissions.get("default", {}).get(_normalize_permission_action(action), {})
|
|
200
190
|
return role in action_permissions
|
|
201
191
|
|
|
202
192
|
|
|
@@ -260,12 +250,7 @@ def token_decoder(
|
|
|
260
250
|
config_lambda_arn = lambda_validator_arn or os.getenv("TOKEN_VALIDATOR_LAMBDA_ARN")
|
|
261
251
|
|
|
262
252
|
# If no config provided, allow for runtime configuration
|
|
263
|
-
if (
|
|
264
|
-
not config_anonymous_role
|
|
265
|
-
and not jwks_url
|
|
266
|
-
and not os.getenv("JWKS_HOST")
|
|
267
|
-
and not config_lambda_arn
|
|
268
|
-
):
|
|
253
|
+
if not config_anonymous_role and not jwks_url and not os.getenv("JWKS_HOST") and not config_lambda_arn:
|
|
269
254
|
pass
|
|
270
255
|
|
|
271
256
|
def decorator(handler: Callable) -> Callable:
|
|
@@ -288,13 +273,9 @@ def token_decoder(
|
|
|
288
273
|
return handler(event, context)
|
|
289
274
|
|
|
290
275
|
# Determine configuration sources
|
|
291
|
-
config_lambda_arn = lambda_validator_arn or os.getenv(
|
|
292
|
-
"TOKEN_VALIDATOR_LAMBDA_ARN"
|
|
293
|
-
)
|
|
276
|
+
config_lambda_arn = lambda_validator_arn or os.getenv("TOKEN_VALIDATOR_LAMBDA_ARN")
|
|
294
277
|
config_jwks_url = (
|
|
295
|
-
jwks_url or f"{os.getenv('JWKS_HOST')}/.well-known/jwks.json"
|
|
296
|
-
if os.getenv("JWKS_HOST")
|
|
297
|
-
else None
|
|
278
|
+
jwks_url or f"{os.getenv('JWKS_HOST')}/.well-known/jwks.json" if os.getenv("JWKS_HOST") else None
|
|
298
279
|
)
|
|
299
280
|
config_issuer = issuer or os.getenv("JWT_ISSUER")
|
|
300
281
|
config_audience = (
|
|
@@ -321,14 +302,10 @@ def token_decoder(
|
|
|
321
302
|
and os.getenv("SKIP_CLAIMS_CHECK", "").lower() == "true"
|
|
322
303
|
and _route_explicitly_allows_role(event, config_anonymous_role)
|
|
323
304
|
):
|
|
324
|
-
log.debug(
|
|
325
|
-
"SKIP_CLAIMS_CHECK enabled; using anonymous role without inline token validation"
|
|
326
|
-
)
|
|
305
|
+
log.debug("SKIP_CLAIMS_CHECK enabled; using anonymous role without inline token validation")
|
|
327
306
|
if "requestContext" not in event:
|
|
328
307
|
event["requestContext"] = {}
|
|
329
|
-
event["requestContext"]["authorizer"] = {
|
|
330
|
-
"roles": [config_anonymous_role]
|
|
331
|
-
}
|
|
308
|
+
event["requestContext"]["authorizer"] = {"roles": [config_anonymous_role]}
|
|
332
309
|
return handler(event, context)
|
|
333
310
|
|
|
334
311
|
# Skip all validation if no method configured
|
|
@@ -338,13 +315,9 @@ def token_decoder(
|
|
|
338
315
|
log.debug("Using anonymous role: %s", config_anonymous_role)
|
|
339
316
|
if "requestContext" not in event:
|
|
340
317
|
event["requestContext"] = {}
|
|
341
|
-
event["requestContext"]["authorizer"] = {
|
|
342
|
-
"roles": [config_anonymous_role]
|
|
343
|
-
}
|
|
318
|
+
event["requestContext"]["authorizer"] = {"roles": [config_anonymous_role]}
|
|
344
319
|
elif config_anonymous_role:
|
|
345
|
-
log.error(
|
|
346
|
-
"Anonymous role configured but route does not explicitly allow it"
|
|
347
|
-
)
|
|
320
|
+
log.error("Anonymous role configured but route does not explicitly allow it")
|
|
348
321
|
return {
|
|
349
322
|
"statusCode": 401,
|
|
350
323
|
"headers": {"Content-Type": "application/json"},
|
|
@@ -365,9 +338,7 @@ def token_decoder(
|
|
|
365
338
|
"Configuring Lambda validator: %s",
|
|
366
339
|
config_lambda_arn,
|
|
367
340
|
)
|
|
368
|
-
wrapper._lambda_validator = LambdaTokenValidator(
|
|
369
|
-
config_lambda_arn
|
|
370
|
-
)
|
|
341
|
+
wrapper._lambda_validator = LambdaTokenValidator(config_lambda_arn)
|
|
371
342
|
else:
|
|
372
343
|
wrapper._lambda_validator = None
|
|
373
344
|
|
|
@@ -380,9 +351,7 @@ def token_decoder(
|
|
|
380
351
|
wrapper._jwt_decoder = JWTDecoder(
|
|
381
352
|
jwks_url=config_jwks_url,
|
|
382
353
|
issuer=config_issuer,
|
|
383
|
-
allowed_audiences=set(config_audience)
|
|
384
|
-
if config_audience
|
|
385
|
-
else None,
|
|
354
|
+
allowed_audiences=set(config_audience) if config_audience else None,
|
|
386
355
|
algorithms=config_algorithms,
|
|
387
356
|
anonymous_role=config_anonymous_role,
|
|
388
357
|
)
|
|
@@ -498,9 +467,7 @@ class JWTDecoder:
|
|
|
498
467
|
if jwks_url:
|
|
499
468
|
self.public_key = self.fetch_public_key_from_jwks(jwks_url)
|
|
500
469
|
if not self.public_key:
|
|
501
|
-
raise ValueError(
|
|
502
|
-
f"Failed to fetch public key from JWKS URL: {jwks_url}"
|
|
503
|
-
)
|
|
470
|
+
raise ValueError(f"Failed to fetch public key from JWKS URL: {jwks_url}")
|
|
504
471
|
else:
|
|
505
472
|
self.public_key = None
|
|
506
473
|
|
|
@@ -510,9 +477,7 @@ class JWTDecoder:
|
|
|
510
477
|
self.algorithms,
|
|
511
478
|
)
|
|
512
479
|
|
|
513
|
-
def fetch_public_key_from_jwks(
|
|
514
|
-
self, jwks_url: str, kid: Optional[str] = None
|
|
515
|
-
) -> Optional[str]:
|
|
480
|
+
def fetch_public_key_from_jwks(self, jwks_url: str, kid: Optional[str] = None) -> Optional[str]:
|
|
516
481
|
"""
|
|
517
482
|
Fetch the public key from a JWKS endpoint.
|
|
518
483
|
|
|
@@ -555,9 +520,7 @@ class JWTDecoder:
|
|
|
555
520
|
log.warning("Key ID %s not found, using first key", kid)
|
|
556
521
|
if not key:
|
|
557
522
|
key = keys[0]
|
|
558
|
-
log.debug(
|
|
559
|
-
"Using first available key with kid: %s", key.get("kid", "unknown")
|
|
560
|
-
)
|
|
523
|
+
log.debug("Using first available key with kid: %s", key.get("kid", "unknown"))
|
|
561
524
|
# Convert JWK to PEM (requires cryptography)
|
|
562
525
|
|
|
563
526
|
def b64url_decode(val):
|
|
@@ -583,15 +546,11 @@ class JWTDecoder:
|
|
|
583
546
|
if token:
|
|
584
547
|
return self.decode_token(token)
|
|
585
548
|
elif self.anonymous_role:
|
|
586
|
-
log.debug(
|
|
587
|
-
"No token found, using anonymous role: %s", self.anonymous_role
|
|
588
|
-
)
|
|
549
|
+
log.debug("No token found, using anonymous role: %s", self.anonymous_role)
|
|
589
550
|
return {"roles": [self.anonymous_role]}
|
|
590
551
|
else:
|
|
591
552
|
log.error("No token found and no anonymous role configured")
|
|
592
|
-
raise ValueError(
|
|
593
|
-
"Authentication required: no token provided and no anonymous access configured"
|
|
594
|
-
)
|
|
553
|
+
raise ValueError("Authentication required: no token provided and no anonymous access configured")
|
|
595
554
|
except ValueError as e:
|
|
596
555
|
if self.anonymous_role and "No authorization header" in str(e):
|
|
597
556
|
log.debug("Token parsing failed, using anonymous role: %s", str(e))
|
|
@@ -618,11 +577,7 @@ class JWTDecoder:
|
|
|
618
577
|
auth_token_parts = auth_header.split(" ")
|
|
619
578
|
log.debug("Authorization header parts: %d", len(auth_token_parts))
|
|
620
579
|
|
|
621
|
-
if (
|
|
622
|
-
len(auth_token_parts) != 2
|
|
623
|
-
or auth_token_parts[0].lower() != "bearer"
|
|
624
|
-
or not auth_token_parts[1]
|
|
625
|
-
):
|
|
580
|
+
if len(auth_token_parts) != 2 or auth_token_parts[0].lower() != "bearer" or not auth_token_parts[1]:
|
|
626
581
|
log.error("Invalid authorization header format")
|
|
627
582
|
raise ValueError("Invalid AuthorizationToken.")
|
|
628
583
|
|
|
@@ -682,9 +637,7 @@ class JWTDecoder:
|
|
|
682
637
|
log.debug("Token audience claim: %s", token_aud)
|
|
683
638
|
|
|
684
639
|
# Normalize token audience to a list for comparison
|
|
685
|
-
token_auds = (
|
|
686
|
-
[token_aud] if isinstance(token_aud, str) else list(token_aud or [])
|
|
687
|
-
)
|
|
640
|
+
token_auds = [token_aud] if isinstance(token_aud, str) else list(token_aud or [])
|
|
688
641
|
token_auds = [str(a).strip() for a in token_auds if str(a).strip()]
|
|
689
642
|
log.debug("Normalized token audiences: %s", token_auds)
|
|
690
643
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.8.74"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|