api-foundry-query-engine 0.8.56__tar.gz → 0.8.58__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.56 → api_foundry_query_engine-0.8.58}/PKG-INFO +1 -1
- api_foundry_query_engine-0.8.58/api_foundry_query_engine/__init__.py +1 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/adapters/gateway_adapter.py +13 -20
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/lambda_handler.py +1 -1
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/claims_check.py +13 -30
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/pyproject.toml +7 -0
- api_foundry_query_engine-0.8.56/api_foundry_query_engine/__init__.py +0 -1
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/.gitignore +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/LICENSE +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/.pre-commit-config.yaml +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/adapters/adapter.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/adapters/case_change_adapter.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/adapters/security_adapter.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/connectors/connection.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/connectors/connection_factory.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/connectors/oracle_connector.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/connectors/postgres_connection.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/batch_operation_handler.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/dao.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/operation_dao.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_custom_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_delete_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_insert_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_restore_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_select_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_subselect_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_update_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/operation.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/services/service.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/services/transactional_service.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/api_model.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/app_exception.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/dependency_resolver.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/logger.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/reference_resolver.py +0 -0
- {api_foundry_query_engine-0.8.56 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/token_decoder.py +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.58
|
|
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.58"
|
|
@@ -4,7 +4,6 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
|
4
4
|
|
|
5
5
|
from api_foundry_query_engine.adapters.adapter import Adapter
|
|
6
6
|
from api_foundry_query_engine.operation import Operation
|
|
7
|
-
from api_foundry_query_engine.utils.app_exception import ApplicationException
|
|
8
7
|
|
|
9
8
|
log = logging.getLogger(__name__)
|
|
10
9
|
|
|
@@ -57,14 +56,15 @@ class GatewayAdapter(Adapter):
|
|
|
57
56
|
|
|
58
57
|
# Handle different authorizer types
|
|
59
58
|
if isinstance(claims, dict):
|
|
60
|
-
# Parse JSON-stringified fields (roles, permissions)
|
|
61
|
-
|
|
59
|
+
# Parse JSON-stringified fields (roles, permissions)
|
|
60
|
+
# from token validator
|
|
61
|
+
for key in ["roles", "permissions"]:
|
|
62
62
|
if key in claims and isinstance(claims[key], str):
|
|
63
63
|
try:
|
|
64
64
|
claims[key] = json.loads(claims[key])
|
|
65
65
|
except (json.JSONDecodeError, ValueError):
|
|
66
66
|
pass # Keep as string if not valid JSON
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
# TOKEN authorizer puts claims directly in authorizer object
|
|
69
69
|
if "sub" in claims or "iss" in claims or "roles" in claims:
|
|
70
70
|
# Already have JWT claims at top level
|
|
@@ -103,19 +103,16 @@ class GatewayAdapter(Adapter):
|
|
|
103
103
|
if path_parameters is not None:
|
|
104
104
|
event_params.update(path_parameters)
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if queryStringParameters is not None:
|
|
110
|
-
event_params.update(queryStringParameters)
|
|
111
|
-
|
|
106
|
+
query_string_parameters = self._convert_parameters(event.get("queryStringParameters"))
|
|
107
|
+
if query_string_parameters is not None:
|
|
108
|
+
event_params.update(query_string_parameters)
|
|
112
109
|
query_params, metadata_params = self.split_params(event_params)
|
|
113
110
|
|
|
114
111
|
store_params = {}
|
|
115
112
|
body = event.get("body")
|
|
116
113
|
if body is not None and len(body) > 0:
|
|
117
114
|
store_params = json.loads(body)
|
|
118
|
-
|
|
115
|
+
|
|
119
116
|
return Operation(
|
|
120
117
|
entity=entity,
|
|
121
118
|
action=action,
|
|
@@ -125,9 +122,7 @@ class GatewayAdapter(Adapter):
|
|
|
125
122
|
claims=claims,
|
|
126
123
|
)
|
|
127
124
|
|
|
128
|
-
def _convert_parameters(
|
|
129
|
-
self, parameters: Optional[Dict[str, Any]]
|
|
130
|
-
) -> Optional[Dict[str, Any]]:
|
|
125
|
+
def _convert_parameters(self, parameters: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
131
126
|
"""
|
|
132
127
|
Convert parameters to appropriate types.
|
|
133
128
|
|
|
@@ -151,9 +146,7 @@ class GatewayAdapter(Adapter):
|
|
|
151
146
|
result[parameter] = value
|
|
152
147
|
return result
|
|
153
148
|
|
|
154
|
-
def split_params(
|
|
155
|
-
self, parameters: Dict[str, Any]
|
|
156
|
-
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
149
|
+
def split_params(self, parameters: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
157
150
|
"""
|
|
158
151
|
Split a dictionary into two dictionaries based on keys.
|
|
159
152
|
|
|
@@ -174,8 +167,8 @@ class GatewayAdapter(Adapter):
|
|
|
174
167
|
else:
|
|
175
168
|
query_params[key] = value
|
|
176
169
|
|
|
177
|
-
log.info(
|
|
178
|
-
log.info(
|
|
179
|
-
log.info(
|
|
170
|
+
log.info("split_params input: %s", parameters)
|
|
171
|
+
log.info("query_params: %s", query_params)
|
|
172
|
+
log.info("metadata_params: %s", metadata_params)
|
|
180
173
|
|
|
181
174
|
return query_params, metadata_params
|
|
@@ -48,7 +48,7 @@ class QueryEngine:
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
@token_decoder()
|
|
51
|
-
@claims_check(validate_path_scope=False)
|
|
51
|
+
@claims_check(validate_path_scope=False, validate_scope_format=False)
|
|
52
52
|
def handler(event, _):
|
|
53
53
|
if not hasattr(handler, "engine_config"):
|
|
54
54
|
log.info("Loading engine config from environment variables")
|
|
@@ -83,17 +83,13 @@ def claims_check(
|
|
|
83
83
|
config_require_authentication = require_authentication or os.getenv(
|
|
84
84
|
"REQUIRE_AUTHENTICATION", ""
|
|
85
85
|
).lower() in ("true", "1", "yes")
|
|
86
|
-
log.debug(
|
|
87
|
-
"config_require_authentication: %s", config_require_authentication
|
|
88
|
-
)
|
|
86
|
+
log.debug("config_require_authentication: %s", config_require_authentication)
|
|
89
87
|
|
|
90
88
|
# Extract claims from the event (set by token_decoder)
|
|
91
89
|
claims = _extract_claims(event)
|
|
92
90
|
if not claims:
|
|
93
91
|
if config_require_authentication:
|
|
94
|
-
raise ApplicationException(
|
|
95
|
-
status_code=401, message="No authentication claims found"
|
|
96
|
-
)
|
|
92
|
+
raise ApplicationException(status_code=401, message="No authentication claims found")
|
|
97
93
|
else:
|
|
98
94
|
log.debug("Found claims: %s", list(claims.keys()))
|
|
99
95
|
|
|
@@ -111,9 +107,7 @@ def claims_check(
|
|
|
111
107
|
|
|
112
108
|
# Check specific scopes if required
|
|
113
109
|
if required_scopes:
|
|
114
|
-
_validate_required_scopes(
|
|
115
|
-
claims, required_scopes, event, operation_type, entity_name
|
|
116
|
-
)
|
|
110
|
+
_validate_required_scopes(claims, required_scopes, event, operation_type, entity_name)
|
|
117
111
|
|
|
118
112
|
# Check specific permissions if required
|
|
119
113
|
if required_permissions:
|
|
@@ -157,9 +151,7 @@ def _extract_claims(event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
157
151
|
|
|
158
152
|
# Check direct authorizer context (most common for TOKEN authorizer)
|
|
159
153
|
claim_keys = ["sub", "scope", "permissions", "roles"]
|
|
160
|
-
if isinstance(authorizer, dict) and any(
|
|
161
|
-
key in authorizer for key in claim_keys
|
|
162
|
-
):
|
|
154
|
+
if isinstance(authorizer, dict) and any(key in authorizer for key in claim_keys):
|
|
163
155
|
log.debug("Found claims in requestContext.authorizer")
|
|
164
156
|
return authorizer
|
|
165
157
|
|
|
@@ -202,12 +194,12 @@ def _validate_scope_format(claims: Dict[str, Any]) -> None:
|
|
|
202
194
|
elif not isinstance(scopes, list):
|
|
203
195
|
scopes = []
|
|
204
196
|
|
|
205
|
-
#
|
|
197
|
+
# Scopes and permissions are now optional - they may be present but are not required
|
|
198
|
+
# The x-af-permissions in the API spec handles role-based access control
|
|
206
199
|
permissions = claims.get("permissions", [])
|
|
207
200
|
if not scopes and not permissions:
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
)
|
|
201
|
+
log.debug("No scopes or permissions found in token - relying on role-based permissions")
|
|
202
|
+
return
|
|
211
203
|
|
|
212
204
|
# Validate scope format: should be operation:resource or operation:*
|
|
213
205
|
for scope in scopes:
|
|
@@ -238,9 +230,7 @@ def _validate_min_scope_level(claims: Dict[str, Any], min_level: str) -> None:
|
|
|
238
230
|
user_max_level = max(user_max_level, scope_value)
|
|
239
231
|
|
|
240
232
|
if user_max_level < min_level_value:
|
|
241
|
-
raise ApplicationException(
|
|
242
|
-
status_code=403, message=f"Insufficient scope level. Required: {min_level}"
|
|
243
|
-
)
|
|
233
|
+
raise ApplicationException(status_code=403, message=f"Insufficient scope level. Required: {min_level}")
|
|
244
234
|
|
|
245
235
|
|
|
246
236
|
def _validate_path_scope(
|
|
@@ -293,8 +283,7 @@ def _validate_path_scope(
|
|
|
293
283
|
if not has_scope and not has_permission:
|
|
294
284
|
raise ApplicationException(
|
|
295
285
|
status_code=403,
|
|
296
|
-
message=f"Access denied. Required scope: {required_scope} "
|
|
297
|
-
f"or permission: {required_permission}",
|
|
286
|
+
message=f"Access denied. Required scope: {required_scope} " f"or permission: {required_permission}",
|
|
298
287
|
)
|
|
299
288
|
|
|
300
289
|
log.debug("Path scope validation passed for %s", required_scope)
|
|
@@ -321,14 +310,10 @@ def _validate_required_scopes(
|
|
|
321
310
|
# Check each required scope
|
|
322
311
|
for required_scope in required_scopes:
|
|
323
312
|
if not _scope_matches(user_scopes, required_scope, operation, entity):
|
|
324
|
-
raise ApplicationException(
|
|
325
|
-
status_code=403, message=f"Required scope not found: {required_scope}"
|
|
326
|
-
)
|
|
313
|
+
raise ApplicationException(status_code=403, message=f"Required scope not found: {required_scope}")
|
|
327
314
|
|
|
328
315
|
|
|
329
|
-
def _validate_required_permissions(
|
|
330
|
-
claims: Dict[str, Any], required_permissions: list
|
|
331
|
-
) -> None:
|
|
316
|
+
def _validate_required_permissions(claims: Dict[str, Any], required_permissions: list) -> None:
|
|
332
317
|
"""Validate that user has required permissions."""
|
|
333
318
|
user_permissions = claims.get("permissions", [])
|
|
334
319
|
if isinstance(user_permissions, str):
|
|
@@ -431,9 +416,7 @@ def _extract_entity_from_path(event: Dict[str, Any]) -> Optional[str]:
|
|
|
431
416
|
return None
|
|
432
417
|
|
|
433
418
|
|
|
434
|
-
def _scope_matches(
|
|
435
|
-
user_scopes: list, required_scope: str, operation: str, entity: str
|
|
436
|
-
) -> bool:
|
|
419
|
+
def _scope_matches(user_scopes: list, required_scope: str, operation: str, entity: str) -> bool:
|
|
437
420
|
"""Check if user scopes match the required scope."""
|
|
438
421
|
_ = entity # Silence unused warning
|
|
439
422
|
# Check for exact match
|
|
@@ -90,3 +90,10 @@ serve = "mkdocs serve --dev-addr localhost:8000"
|
|
|
90
90
|
|
|
91
91
|
[tool.hatch.publish.index]
|
|
92
92
|
disable = true # Disable publishing by default for safety
|
|
93
|
+
|
|
94
|
+
# Linting / formatting configuration
|
|
95
|
+
[tool.ruff]
|
|
96
|
+
line-length = 120
|
|
97
|
+
|
|
98
|
+
[tool.black]
|
|
99
|
+
line-length = 120
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.8.56"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|