api-foundry-query-engine 0.8.30__tar.gz → 0.8.32__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.30 → api_foundry_query_engine-0.8.32}/PKG-INFO +2 -1
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/adapters/adapter.py +1 -1
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/adapters/case_change_adapter.py +1 -1
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/adapters/gateway_adapter.py +23 -35
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/adapters/security_adapter.py +2 -2
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/connectors/connection_factory.py +6 -6
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/connectors/oracle_connector.py +1 -1
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/connectors/postgres_connection.py +1 -1
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_custom_query_handler.py +1 -1
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_delete_query_handler.py +21 -5
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_insert_query_handler.py +1 -1
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_query_handler.py +68 -20
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/lambda_handler.py +7 -6
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/operation.py +31 -14
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/services/service.py +12 -6
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/services/transactional_service.py +2 -2
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/utils/api_model.py +2 -2
- api_foundry_query_engine-0.8.32/api_foundry_query_engine/utils/claims_check.py +471 -0
- api_foundry_query_engine-0.8.32/api_foundry_query_engine/utils/gateway_operation.py +279 -0
- api_foundry_query_engine-0.8.32/api_foundry_query_engine/utils/token_decoder.py +415 -0
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/pyproject.toml +38 -2
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/.gitignore +0 -0
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/LICENSE +0 -0
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/.pre-commit-config.yaml +0 -0
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/__init__.py +0 -0
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/connectors/connection.py +0 -0
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/dao.py +0 -0
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/operation_dao.py +0 -0
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_select_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_subselect_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_update_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/utils/app_exception.py +0 -0
- {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/utils/logger.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.32
|
|
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
|
|
@@ -18,3 +18,4 @@ Requires-Dist: fixture-foundry>=0.1.0; extra == 'dev'
|
|
|
18
18
|
Requires-Dist: isort>=5.12.0; extra == 'dev'
|
|
19
19
|
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
20
20
|
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: simple-oauth-server; extra == 'dev'
|
|
@@ -30,7 +30,7 @@ class CaseChangeAdapter(Adapter):
|
|
|
30
30
|
or self.__check_camel_case(operation.store_params)
|
|
31
31
|
or self.__check_camel_case(operation.query_params)
|
|
32
32
|
)
|
|
33
|
-
log.info(
|
|
33
|
+
log.info("camel_case: %s", self.camel_case)
|
|
34
34
|
|
|
35
35
|
if self.camel_case:
|
|
36
36
|
return Operation(
|
|
@@ -65,38 +65,30 @@ class GatewayAdapter(Adapter):
|
|
|
65
65
|
if body is not None and len(body) > 0:
|
|
66
66
|
store_params = json.loads(body)
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
claims =
|
|
70
|
-
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
permissions_raw = claims.get("permissions", [])
|
|
91
|
-
if isinstance(permissions_raw, str):
|
|
92
|
-
try:
|
|
93
|
-
permissions = json.loads(permissions_raw)
|
|
94
|
-
except (json.JSONDecodeError, TypeError):
|
|
95
|
-
permissions = []
|
|
68
|
+
# Extract JWT claims from API Gateway event
|
|
69
|
+
claims = event.get("requestContext", {}).get("authorizer", {})
|
|
70
|
+
|
|
71
|
+
# Handle different authorizer types
|
|
72
|
+
if isinstance(claims, dict):
|
|
73
|
+
# TOKEN authorizer puts claims directly in authorizer object
|
|
74
|
+
if "sub" in claims or "iss" in claims:
|
|
75
|
+
# Already have JWT claims at top level
|
|
76
|
+
pass
|
|
77
|
+
elif "claims" in claims:
|
|
78
|
+
# Some configurations nest claims
|
|
79
|
+
claims = claims["claims"]
|
|
80
|
+
elif "iam" in claims:
|
|
81
|
+
# IAM authorizer fallback
|
|
82
|
+
claims = claims["iam"]
|
|
83
|
+
elif "lambda" in claims:
|
|
84
|
+
# Lambda authorizer fallback
|
|
85
|
+
claims = claims["lambda"]
|
|
86
|
+
else:
|
|
87
|
+
# Empty or unknown format
|
|
88
|
+
claims = {}
|
|
96
89
|
else:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
subject = claims.get("sub")
|
|
90
|
+
# Non-dict authorizer context
|
|
91
|
+
claims = {}
|
|
100
92
|
scope_str = claims.get("scope")
|
|
101
93
|
|
|
102
94
|
# Enforce OAuth scopes (simulating API Gateway authorizer behavior)
|
|
@@ -132,10 +124,6 @@ class GatewayAdapter(Adapter):
|
|
|
132
124
|
store_params=store_params,
|
|
133
125
|
query_params=query_params,
|
|
134
126
|
metadata_params=metadata_params,
|
|
135
|
-
roles=roles,
|
|
136
|
-
groups=groups,
|
|
137
|
-
subject=subject,
|
|
138
|
-
permissions=permissions,
|
|
139
127
|
claims=claims,
|
|
140
128
|
)
|
|
141
129
|
|
|
@@ -99,8 +99,8 @@ class SecurityAdapter(Adapter):
|
|
|
99
99
|
try:
|
|
100
100
|
operation = self.unmarshal(event)
|
|
101
101
|
result = self.service.execute(operation)
|
|
102
|
-
log.debug(
|
|
102
|
+
log.debug("adapter result: %s", result)
|
|
103
103
|
return self.marshal(result)
|
|
104
104
|
except PermissionError as e:
|
|
105
|
-
log.error(
|
|
105
|
+
log.error("Permission error: %s", e)
|
|
106
106
|
return {"error": str(e), "status": "permission_denied"}
|
|
@@ -32,7 +32,7 @@ class ConnectionFactory:
|
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
34
|
# Get the secret name based on the engine and database from the secrets map
|
|
35
|
-
log.info(
|
|
35
|
+
log.info("database: %s", database)
|
|
36
36
|
db_config = self.db_config_map.get(database)
|
|
37
37
|
if not db_config:
|
|
38
38
|
# Use config dict for secrets
|
|
@@ -40,7 +40,7 @@ class ConnectionFactory:
|
|
|
40
40
|
if isinstance(secrets_map, str):
|
|
41
41
|
secrets_map = json.loads(secrets_map)
|
|
42
42
|
secret_name = secrets_map.get(database)
|
|
43
|
-
log.debug(
|
|
43
|
+
log.debug("secret_name: %s", secret_name)
|
|
44
44
|
|
|
45
45
|
if secret_name:
|
|
46
46
|
db_config = self.__get_secret(secret_name)
|
|
@@ -79,7 +79,7 @@ class ConnectionFactory:
|
|
|
79
79
|
sts_client = boto3.client("sts", endpoint_url=endpoint_url)
|
|
80
80
|
|
|
81
81
|
secret_account_id = self.config.get("SECRET_ACCOUNT_ID", None)
|
|
82
|
-
log.debug(
|
|
82
|
+
log.debug("secret_account_id: %s", secret_account_id)
|
|
83
83
|
|
|
84
84
|
if secret_account_id:
|
|
85
85
|
# If a secret account ID is provided, assume a role in that account
|
|
@@ -100,16 +100,16 @@ class ConnectionFactory:
|
|
|
100
100
|
)
|
|
101
101
|
else:
|
|
102
102
|
# If no secret account ID is provided, use the default account
|
|
103
|
-
log.info(
|
|
103
|
+
log.info("endpoint_url: %s", endpoint_url)
|
|
104
104
|
secretsmanager = boto3.client(
|
|
105
105
|
"secretsmanager",
|
|
106
106
|
endpoint_url=endpoint_url,
|
|
107
107
|
)
|
|
108
108
|
|
|
109
109
|
# Get the secret value from AWS Secrets Manager
|
|
110
|
-
log.info(
|
|
110
|
+
log.info("db_secret_name: %s", db_secret_name)
|
|
111
111
|
db_secret = secretsmanager.get_secret_value(SecretId=db_secret_name)
|
|
112
|
-
log.debug(
|
|
112
|
+
log.debug("loading secret name: %s", db_secret)
|
|
113
113
|
|
|
114
114
|
# Return the parsed JSON secret string
|
|
115
115
|
return json.loads(db_secret.get("SecretString"))
|
|
@@ -15,7 +15,7 @@ class OracleConnnector(Connector):
|
|
|
15
15
|
def execute(self, cursor, sql: str, parameters: dict):
|
|
16
16
|
from oracledb import Error, IntegrityError, ProgrammingError
|
|
17
17
|
|
|
18
|
-
log.debug(
|
|
18
|
+
log.debug("sql: %s, parameters: %s", sql, parameters)
|
|
19
19
|
try:
|
|
20
20
|
cursor.execute(sql, parameters)
|
|
21
21
|
except IntegrityError as err:
|
|
@@ -39,7 +39,7 @@ class SQLCustomQueryHandler(SQLQueryHandler):
|
|
|
39
39
|
self._selection_results = self.check_permissions(
|
|
40
40
|
"read", self.path_operation.permissions, self.path_operation.outputs
|
|
41
41
|
)
|
|
42
|
-
log.debug(
|
|
42
|
+
log.debug("selection_results: %s", self._selection_results)
|
|
43
43
|
return self._selection_results
|
|
44
44
|
|
|
45
45
|
def _compile(self):
|
|
@@ -29,12 +29,28 @@ class SQLDeleteSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
29
29
|
if not self.schema_object.permissions:
|
|
30
30
|
return True
|
|
31
31
|
|
|
32
|
+
# Use the proper provider-action-role structure
|
|
33
|
+
provider_permissions = self.schema_object.permissions.get("default", {})
|
|
34
|
+
delete_permissions = provider_permissions.get("delete", {})
|
|
35
|
+
|
|
32
36
|
for role in self.operation.roles:
|
|
33
|
-
role_permissions =
|
|
34
|
-
log.info(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
role_permissions = delete_permissions.get(role)
|
|
38
|
+
log.info("role: %s, role_permissions: %s", role, role_permissions)
|
|
39
|
+
|
|
40
|
+
# If no permissions found for role, check wildcard "*"
|
|
41
|
+
if role_permissions is None:
|
|
42
|
+
role_permissions = delete_permissions.get("*")
|
|
43
|
+
log.info("Fallback to wildcard role '*': %s", role_permissions)
|
|
44
|
+
if role_permissions is None:
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
# Handle both boolean and object permission formats
|
|
48
|
+
if isinstance(role_permissions, bool):
|
|
49
|
+
allowed = role_permissions
|
|
50
|
+
else:
|
|
51
|
+
# For complex permission objects, existence means allowed
|
|
52
|
+
allowed = True
|
|
53
|
+
|
|
38
54
|
if allowed:
|
|
39
55
|
return True
|
|
40
56
|
|
|
@@ -78,7 +78,7 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
78
78
|
for k, v in self.schema_object.properties.items()
|
|
79
79
|
if k in allowed_property_names
|
|
80
80
|
}
|
|
81
|
-
log.info(
|
|
81
|
+
log.info("allowed properties: %s", allowed_properties)
|
|
82
82
|
|
|
83
83
|
import json
|
|
84
84
|
|
|
@@ -187,29 +187,77 @@ class SQLQueryHandler:
|
|
|
187
187
|
return properties
|
|
188
188
|
|
|
189
189
|
allowed_properties = {}
|
|
190
|
+
log.info(
|
|
191
|
+
"Input properties keys: %s",
|
|
192
|
+
list(properties.keys()) if properties else "None",
|
|
193
|
+
)
|
|
194
|
+
log.info("Operation roles: %s", self.operation.roles)
|
|
195
|
+
|
|
196
|
+
# Permissions structure: {provider: {action: {role: rule}}}
|
|
197
|
+
# Use 'default' as the standard provider
|
|
198
|
+
provider_permissions = permissions.get("default", {})
|
|
199
|
+
action_permissions = provider_permissions.get(permission_type, {})
|
|
200
|
+
|
|
201
|
+
log.info(
|
|
202
|
+
"Extracted action permissions for %s: %s",
|
|
203
|
+
permission_type,
|
|
204
|
+
action_permissions,
|
|
205
|
+
)
|
|
190
206
|
|
|
191
207
|
for role in self.operation.roles:
|
|
192
|
-
role_permissions =
|
|
193
|
-
log.info(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
role_permissions = action_permissions.get(role, {})
|
|
209
|
+
log.info("role: %s, role_permissions: %s", role, role_permissions)
|
|
210
|
+
|
|
211
|
+
# If no permissions found for specific role, check wildcard "*"
|
|
212
|
+
if not role_permissions:
|
|
213
|
+
role_permissions = action_permissions.get("*", {})
|
|
214
|
+
log.info("Fallback to wildcard role '*': %s", role_permissions)
|
|
215
|
+
if not role_permissions:
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
# Extract permission pattern from the rule
|
|
219
|
+
regex_pattern = self._extract_permission_pattern(role_permissions)
|
|
220
|
+
|
|
221
|
+
if regex_pattern:
|
|
222
|
+
# Use efficient regex dictionary filtering
|
|
223
|
+
role_allowed = self._filter_properties_by_regex(
|
|
224
|
+
properties, regex_pattern
|
|
225
|
+
)
|
|
226
|
+
log.info(
|
|
227
|
+
"role: %s, pattern: %s, matched: %s",
|
|
228
|
+
role,
|
|
229
|
+
regex_pattern,
|
|
230
|
+
list(role_allowed.keys()),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Merge with existing allowed properties
|
|
234
|
+
allowed_properties.update(role_allowed)
|
|
235
|
+
|
|
236
|
+
log.info("allowed_properties: %s", allowed_properties)
|
|
211
237
|
return allowed_properties
|
|
212
238
|
|
|
239
|
+
def _filter_properties_by_regex(
|
|
240
|
+
self, properties: Dict[str, SchemaObjectProperty], regex_pattern: str
|
|
241
|
+
) -> Dict[str, SchemaObjectProperty]:
|
|
242
|
+
"""Filter dictionary properties using a regex pattern.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
properties: Dictionary of property name -> SchemaObjectProperty
|
|
246
|
+
regex_pattern: Regex pattern to match property names
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Dict[str, SchemaObjectProperty]: Filtered properties dict
|
|
250
|
+
"""
|
|
251
|
+
if not regex_pattern:
|
|
252
|
+
return {}
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
compiled_regex = re.compile(regex_pattern)
|
|
256
|
+
return {k: v for k, v in properties.items() if compiled_regex.match(k)}
|
|
257
|
+
except re.error as e:
|
|
258
|
+
log.warning("Invalid regex pattern '%s': %s", regex_pattern, e)
|
|
259
|
+
return {}
|
|
260
|
+
|
|
213
261
|
def _extract_permission_pattern(self, permission_rule) -> str:
|
|
214
262
|
"""Extract the regex pattern from a permission rule.
|
|
215
263
|
|
|
@@ -381,7 +429,7 @@ class SQLSchemaQueryHandler(SQLQueryHandler):
|
|
|
381
429
|
"""
|
|
382
430
|
log.info("selection_result")
|
|
383
431
|
if not hasattr(self, "__selection_results"):
|
|
384
|
-
log.info(
|
|
432
|
+
log.info("prefix_map: %s", self.prefix_map)
|
|
385
433
|
filters = self.operation.metadata_params.get("_properties", ".*").split()
|
|
386
434
|
allowed_properties = self.check_permissions(
|
|
387
435
|
"read", self.schema_object.permissions, self.schema_object.properties
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Mapping, Any
|
|
5
5
|
|
|
6
6
|
from api_foundry_query_engine.utils.api_model import set_api_model
|
|
7
7
|
from api_foundry_query_engine.utils.app_exception import ApplicationException
|
|
8
8
|
from api_foundry_query_engine.adapters.gateway_adapter import GatewayAdapter
|
|
9
|
+
from api_foundry_query_engine.utils.token_decoder import token_decoder
|
|
10
|
+
from api_foundry_query_engine.utils.claims_check import claims_check
|
|
9
11
|
|
|
12
|
+
logging.basicConfig(level=os.getenv("LOG_LEVEL", "INFO"))
|
|
10
13
|
log = logging.getLogger(__name__)
|
|
11
14
|
|
|
12
15
|
|
|
@@ -44,15 +47,13 @@ class QueryEngine:
|
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
@token_decoder()
|
|
51
|
+
@claims_check()
|
|
51
52
|
def handler(event, _):
|
|
52
53
|
if not hasattr(handler, "engine_config"):
|
|
53
54
|
log.info("Loading engine config from environment variables")
|
|
54
55
|
handler.engine_config = os.environ
|
|
55
|
-
log.info(
|
|
56
|
+
log.info("engine_config: %s", handler.engine_config)
|
|
56
57
|
|
|
57
58
|
if not hasattr(handler, "query_engine"):
|
|
58
59
|
set_api_model(handler.engine_config)
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
from typing import Any, Dict, List, Optional
|
|
2
2
|
|
|
3
|
+
from api_foundry_query_engine.utils.logger import logger
|
|
4
|
+
|
|
5
|
+
logger = logger(__name__)
|
|
6
|
+
|
|
3
7
|
|
|
4
8
|
class Operation:
|
|
5
9
|
"""
|
|
@@ -35,11 +39,6 @@ class Operation:
|
|
|
35
39
|
query_params: Optional[Dict[str, Any]] = None,
|
|
36
40
|
store_params: Optional[Dict[str, Any]] = None,
|
|
37
41
|
metadata_params: Optional[Dict[str, Any]] = None,
|
|
38
|
-
roles: Optional[List[str]] = None,
|
|
39
|
-
scope: Optional[Dict[str, Any]] = None,
|
|
40
|
-
groups: Optional[List[str]] = None,
|
|
41
|
-
permissions: Optional[List[str]] = None,
|
|
42
|
-
subject: Optional[str] = None,
|
|
43
42
|
claims: Optional[Dict[str, Any]] = None,
|
|
44
43
|
):
|
|
45
44
|
"""
|
|
@@ -76,12 +75,30 @@ class Operation:
|
|
|
76
75
|
self.metadata_params = metadata_params or {}
|
|
77
76
|
|
|
78
77
|
# Roles defining the context in which the operation is allowed.
|
|
79
|
-
self.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
self.claims = claims or {}
|
|
79
|
+
|
|
80
|
+
# Log the operation for debugging and audit purposes
|
|
81
|
+
logger.info(
|
|
82
|
+
"Operation created: entity=%s, action=%s, "
|
|
83
|
+
"query_params=%s, store_params=%s, "
|
|
84
|
+
"metadata_params=%s, claims=%s",
|
|
85
|
+
self.entity,
|
|
86
|
+
self.action,
|
|
87
|
+
self.query_params,
|
|
88
|
+
self.store_params,
|
|
89
|
+
self.metadata_params,
|
|
90
|
+
self.claims,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def roles(self) -> List[str]:
|
|
95
|
+
"""Get the roles from claims."""
|
|
96
|
+
return self.claims.get("roles", []) if self.claims else []
|
|
97
|
+
|
|
98
|
+
def subject(self) -> Optional[str]:
|
|
99
|
+
"""Get the subject from claims."""
|
|
100
|
+
return self.claims.get("sub") if self.claims else None
|
|
101
|
+
|
|
102
|
+
def groups(self) -> List[str]:
|
|
103
|
+
"""Get the groups from claims."""
|
|
104
|
+
return self.claims.get("groups", []) if self.claims else []
|
|
@@ -8,7 +8,9 @@ log = logger(__name__)
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Service:
|
|
11
|
-
def __init__(self, config: dict =
|
|
11
|
+
def __init__(self, config: dict = None):
|
|
12
|
+
if not config:
|
|
13
|
+
config = {}
|
|
12
14
|
self.config = config
|
|
13
15
|
|
|
14
16
|
def execute(self, operation: Operation) -> list[dict]:
|
|
@@ -16,7 +18,9 @@ class Service:
|
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
class ServiceAdapter(Service):
|
|
19
|
-
def __init__(self, config: dict =
|
|
21
|
+
def __init__(self, config: dict = None):
|
|
22
|
+
if not config:
|
|
23
|
+
config = {}
|
|
20
24
|
super().__init__(config)
|
|
21
25
|
|
|
22
26
|
def execute(self, operation: Operation) -> list[dict]:
|
|
@@ -24,7 +28,9 @@ class ServiceAdapter(Service):
|
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
class MutationPublisher(ServiceAdapter):
|
|
27
|
-
def __init__(self, config: dict =
|
|
31
|
+
def __init__(self, config: dict = None):
|
|
32
|
+
if not config:
|
|
33
|
+
config = {}
|
|
28
34
|
super().__init__(config)
|
|
29
35
|
|
|
30
36
|
def execute(self, operation):
|
|
@@ -34,7 +40,7 @@ class MutationPublisher(ServiceAdapter):
|
|
|
34
40
|
|
|
35
41
|
def publish_notification(self, operation):
|
|
36
42
|
topic_arn = self.config.get("BROADCAST_TOPIC", None)
|
|
37
|
-
log.debug(
|
|
43
|
+
log.debug("Topic ARN: %s", topic_arn)
|
|
38
44
|
|
|
39
45
|
if topic_arn is not None:
|
|
40
46
|
log.debug("Sending message")
|
|
@@ -46,7 +52,7 @@ class MutationPublisher(ServiceAdapter):
|
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
message_str = json.dumps({"default": json.dumps(message)})
|
|
49
|
-
log.debug(
|
|
55
|
+
log.debug("message_str: %s", message_str)
|
|
50
56
|
hash_object = hashlib.sha256(message_str.encode("utf-8"))
|
|
51
57
|
hex_dig = hash_object.hexdigest()
|
|
52
58
|
|
|
@@ -57,7 +63,7 @@ class MutationPublisher(ServiceAdapter):
|
|
|
57
63
|
MessageGroupId=operation.api_name,
|
|
58
64
|
Message=message_str,
|
|
59
65
|
)
|
|
60
|
-
log.info(
|
|
66
|
+
log.info("publish msg id %s", msg_id)
|
|
61
67
|
|
|
62
68
|
def __client(self, client_type):
|
|
63
69
|
import boto3
|
|
@@ -45,8 +45,8 @@ class TransactionalService(ServiceAdapter):
|
|
|
45
45
|
return [result]
|
|
46
46
|
return result
|
|
47
47
|
except Exception as error:
|
|
48
|
-
log.error(
|
|
49
|
-
log.error(
|
|
48
|
+
log.error("transaction exception: %s", error)
|
|
49
|
+
log.error("traceback: %s", traceback.format_exc())
|
|
50
50
|
raise error
|
|
51
51
|
finally:
|
|
52
52
|
connection.close()
|
|
@@ -289,7 +289,7 @@ class APIModel:
|
|
|
289
289
|
"""Class to load and expose the API configuration as objects."""
|
|
290
290
|
|
|
291
291
|
def __init__(self, config: Dict[str, Any]):
|
|
292
|
-
|
|
292
|
+
log.info("building api_model")
|
|
293
293
|
self.schema_objects = {
|
|
294
294
|
name: SchemaObject(schema_data)
|
|
295
295
|
for name, schema_data in config.get("schema_objects", {}).items()
|
|
@@ -323,4 +323,4 @@ def set_api_model(engine_config: Mapping[str, str]):
|
|
|
323
323
|
os.environ.get("API_SPEC", "/var/task/api_spec.yaml"), "r"
|
|
324
324
|
) as file:
|
|
325
325
|
api_model = APIModel(yaml.safe_load(file))
|
|
326
|
-
log.info(
|
|
326
|
+
log.info("Loaded API model: %s", api_model)
|