Encryptors 2.58__tar.gz → 2.60__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.
- {encryptors-2.58 → encryptors-2.60}/PKG-INFO +1 -1
- {encryptors-2.58 → encryptors-2.60}/setup.py +1 -1
- {encryptors-2.58 → encryptors-2.60}/src/Encryptors.egg-info/PKG-INFO +1 -1
- {encryptors-2.58 → encryptors-2.60}/src/Encryptors.egg-info/SOURCES.txt +4 -17
- encryptors-2.60/src/Osdental/Context/__init__.py +5 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Decorators/SecureResolver.py +4 -3
- encryptors-2.60/src/Osdental/Enums/ResultType.py +5 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Helpers/AuditHelper.py +58 -10
- encryptors-2.60/src/Osdental/Helpers/AuditQueue.py +45 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Http/APIClient.py +37 -12
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Services/ServiceBusAuditEmitter.py +6 -2
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Services/__init__.py +1 -1
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Storage/__init__.py +2 -2
- encryptors-2.58/src/Osdental/Graphql/Extensions/AuditExtension.py +0 -196
- encryptors-2.58/src/Osdental/Graphql/_Exceptions/__init__.py +0 -8
- encryptors-2.58/src/Osdental/Graphql/_Helpers/_AuditHelper.py +0 -67
- encryptors-2.58/src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py +0 -11
- encryptors-2.58/src/Osdental/Graphql/_Helpers/_TenantPolicy.py +0 -46
- encryptors-2.58/src/Osdental/Graphql/_Helpers/_TokenService.py +0 -44
- encryptors-2.58/src/Osdental/Helpers/AuditDispatcher.py +0 -169
- encryptors-2.58/src/Osdental/Http/_Helpers.py +0 -125
- encryptors-2.58/src/Osdental/Models/__init__.py +0 -0
- encryptors-2.58/src/Osdental/Rest/Context/RequestContext.py +0 -19
- encryptors-2.58/src/Osdental/Rest/Context/__init__.py +0 -0
- encryptors-2.58/src/Osdental/Rest/Middlewares/RequestContextMiddleware.py +0 -22
- encryptors-2.58/src/Osdental/Rest/Middlewares/__init__.py +0 -0
- encryptors-2.58/src/Osdental/Rest/__init__.py +0 -0
- encryptors-2.58/src/Osdental/Utils/__init__.py +0 -0
- encryptors-2.58/src/Osdental/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/README.md +0 -0
- {encryptors-2.58 → encryptors-2.60}/setup.cfg +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Encryptors.egg-info/dependency_links.txt +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Encryptors.egg-info/entry_points.txt +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Encryptors.egg-info/requires.txt +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Encryptors.egg-info/top_level.txt +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Cache/Redis.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Cache/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Cli/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Constants/Constant.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Constants/Message.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Constants/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Database/BaseRepository.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Database/Connection.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Database/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Decorators/Grpc.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Decorators/Retry.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Decorators/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Encryptor/Aes.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Encryptor/Argon2.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Encryptor/Bcrypt.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Encryptor/Jwt.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Encryptor/Rsa.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Encryptor/Sha512.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Encryptor/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Enums/AuditType.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Enums/ErrorSource.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Enums/FileType.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Enums/GrahpqlOperation.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Enums/Profile.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Enums/StatusCode.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Enums/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Exception/ControlledException.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Exception/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Helpers/AzureClassifier.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Helpers/GrpcConnection.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Helpers/JwtTokenHelper.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Helpers/Resilience.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Helpers/ResponseDecryptor.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Helpers/_AuthTokenProcessor.py +0 -0
- {encryptors-2.58/src/Osdental/Graphql/Extensions → encryptors-2.60/src/Osdental/Helpers}/__init__.py +0 -0
- {encryptors-2.58/src/Osdental/Graphql/_Helpers → encryptors-2.60/src/Osdental/Http}/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Messaging/AzureServiceBus.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Messaging/Kafka.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Messaging/RabbitMQ.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Messaging/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Models/ApiResponse.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Models/AuditContext.py +0 -0
- /encryptors-2.58/src/Osdental/Graphql/Models/__init__.py → /encryptors-2.60/src/Osdental/Models/Graphql.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Models/Notification.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Models/Response.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Models/Token.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Models/TokenClaims.py +0 -0
- {encryptors-2.58/src/Osdental/Graphql → encryptors-2.60/src/Osdental/Models}/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Secrets/AzureKeyVaultProvider.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Secrets/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Services/JwtAuthTokenService.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Services/WebsocketClient.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Storage/S3Storage.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Utils/CaseConverter.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Utils/CodeGenerator.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Utils/DataNormalizer.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Utils/DataUtils.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Utils/DateUtils.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Utils/FileMetaData.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Utils/HashValidator.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Utils/Mapper.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Utils/PasswordGenerator.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Utils/QueryGenerator.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Utils/RsaUtils.py +0 -0
- {encryptors-2.58 → encryptors-2.60}/src/Osdental/Utils/TextProcessor.py +0 -0
- {encryptors-2.58/src/Osdental/Helpers → encryptors-2.60/src/Osdental/Utils}/__init__.py +0 -0
- {encryptors-2.58/src/Osdental/Http → encryptors-2.60/src/Osdental}/__init__.py +0 -0
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
# ANDERSON REVISAR EL CACHE LOCAL DEL KEYVAULT PARA VALIDAR SI FUNCIONA
|
|
3
3
|
setup(
|
|
4
4
|
name="Encryptors",
|
|
5
|
-
version="2.
|
|
5
|
+
version="2.60",
|
|
6
6
|
author="OSDental LLC",
|
|
7
7
|
author_email="support@osdental.ai",
|
|
8
8
|
description="End-to-end algorithm library",
|
|
@@ -13,6 +13,7 @@ src/Osdental/Cli/__init__.py
|
|
|
13
13
|
src/Osdental/Constants/Constant.py
|
|
14
14
|
src/Osdental/Constants/Message.py
|
|
15
15
|
src/Osdental/Constants/__init__.py
|
|
16
|
+
src/Osdental/Context/__init__.py
|
|
16
17
|
src/Osdental/Database/BaseRepository.py
|
|
17
18
|
src/Osdental/Database/Connection.py
|
|
18
19
|
src/Osdental/Database/__init__.py
|
|
@@ -32,22 +33,13 @@ src/Osdental/Enums/ErrorSource.py
|
|
|
32
33
|
src/Osdental/Enums/FileType.py
|
|
33
34
|
src/Osdental/Enums/GrahpqlOperation.py
|
|
34
35
|
src/Osdental/Enums/Profile.py
|
|
36
|
+
src/Osdental/Enums/ResultType.py
|
|
35
37
|
src/Osdental/Enums/StatusCode.py
|
|
36
38
|
src/Osdental/Enums/__init__.py
|
|
37
39
|
src/Osdental/Exception/ControlledException.py
|
|
38
40
|
src/Osdental/Exception/__init__.py
|
|
39
|
-
src/Osdental/Graphql/__init__.py
|
|
40
|
-
src/Osdental/Graphql/Extensions/AuditExtension.py
|
|
41
|
-
src/Osdental/Graphql/Extensions/__init__.py
|
|
42
|
-
src/Osdental/Graphql/Models/__init__.py
|
|
43
|
-
src/Osdental/Graphql/_Exceptions/__init__.py
|
|
44
|
-
src/Osdental/Graphql/_Helpers/_AuditHelper.py
|
|
45
|
-
src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py
|
|
46
|
-
src/Osdental/Graphql/_Helpers/_TenantPolicy.py
|
|
47
|
-
src/Osdental/Graphql/_Helpers/_TokenService.py
|
|
48
|
-
src/Osdental/Graphql/_Helpers/__init__.py
|
|
49
|
-
src/Osdental/Helpers/AuditDispatcher.py
|
|
50
41
|
src/Osdental/Helpers/AuditHelper.py
|
|
42
|
+
src/Osdental/Helpers/AuditQueue.py
|
|
51
43
|
src/Osdental/Helpers/AzureClassifier.py
|
|
52
44
|
src/Osdental/Helpers/GrpcConnection.py
|
|
53
45
|
src/Osdental/Helpers/JwtTokenHelper.py
|
|
@@ -56,7 +48,6 @@ src/Osdental/Helpers/ResponseDecryptor.py
|
|
|
56
48
|
src/Osdental/Helpers/_AuthTokenProcessor.py
|
|
57
49
|
src/Osdental/Helpers/__init__.py
|
|
58
50
|
src/Osdental/Http/APIClient.py
|
|
59
|
-
src/Osdental/Http/_Helpers.py
|
|
60
51
|
src/Osdental/Http/__init__.py
|
|
61
52
|
src/Osdental/Messaging/AzureServiceBus.py
|
|
62
53
|
src/Osdental/Messaging/Kafka.py
|
|
@@ -64,16 +55,12 @@ src/Osdental/Messaging/RabbitMQ.py
|
|
|
64
55
|
src/Osdental/Messaging/__init__.py
|
|
65
56
|
src/Osdental/Models/ApiResponse.py
|
|
66
57
|
src/Osdental/Models/AuditContext.py
|
|
58
|
+
src/Osdental/Models/Graphql.py
|
|
67
59
|
src/Osdental/Models/Notification.py
|
|
68
60
|
src/Osdental/Models/Response.py
|
|
69
61
|
src/Osdental/Models/Token.py
|
|
70
62
|
src/Osdental/Models/TokenClaims.py
|
|
71
63
|
src/Osdental/Models/__init__.py
|
|
72
|
-
src/Osdental/Rest/__init__.py
|
|
73
|
-
src/Osdental/Rest/Context/RequestContext.py
|
|
74
|
-
src/Osdental/Rest/Context/__init__.py
|
|
75
|
-
src/Osdental/Rest/Middlewares/RequestContextMiddleware.py
|
|
76
|
-
src/Osdental/Rest/Middlewares/__init__.py
|
|
77
64
|
src/Osdental/Secrets/AzureKeyVaultProvider.py
|
|
78
65
|
src/Osdental/Secrets/__init__.py
|
|
79
66
|
src/Osdental/Services/JwtAuthTokenService.py
|
|
@@ -3,7 +3,7 @@ from functools import wraps
|
|
|
3
3
|
from typing import Callable
|
|
4
4
|
from graphql import GraphQLResolveInfo
|
|
5
5
|
from Osdental.Models.Response import Response
|
|
6
|
-
from Osdental.Graphql
|
|
6
|
+
from Osdental.Models.Graphql import BaseGraphQLContext
|
|
7
7
|
from Osdental.Models.TokenClaims import UserTokenClaims
|
|
8
8
|
from Osdental.Models.Token import AuthToken
|
|
9
9
|
from Osdental.Enums.Profile import Profile
|
|
@@ -19,12 +19,11 @@ from Osdental.Models.AuditContext import AuditContext
|
|
|
19
19
|
from Osdental.Helpers.AuditHelper import (
|
|
20
20
|
_build_success_payload, _build_error_payload, _build_unexpected_payload
|
|
21
21
|
)
|
|
22
|
-
|
|
22
|
+
from Osdental.Context import _ctx
|
|
23
23
|
|
|
24
24
|
logger = logging.getLogger(__name__)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
|
|
28
27
|
def resolver(public: bool = False, action=None):
|
|
29
28
|
|
|
30
29
|
def decorator(func: Callable):
|
|
@@ -95,6 +94,8 @@ def resolver(public: bool = False, action=None):
|
|
|
95
94
|
decrypted_payload=decrypted_payload,
|
|
96
95
|
)
|
|
97
96
|
|
|
97
|
+
_ctx.set(audit_ctx)
|
|
98
|
+
|
|
98
99
|
# ── 6. EJECUTAR RESOLVER ──────────────────
|
|
99
100
|
result = await func(obj, info, **kwargs)
|
|
100
101
|
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from typing import Optional, List, Dict, Any
|
|
2
3
|
from fastapi import Request
|
|
3
|
-
from
|
|
4
|
+
from httpx import HTTPStatusError, RequestError
|
|
4
5
|
from Osdental.Models.AuditContext import AuditContext
|
|
5
6
|
from Osdental.Models.Response import Response
|
|
7
|
+
from Osdental.Models.ApiResponse import ApiResponse
|
|
6
8
|
from Osdental.Exception.ControlledException import OSDException
|
|
7
9
|
from Osdental.Enums.StatusCode import StatusCode
|
|
8
10
|
from Osdental.Enums.AuditType import AuditType
|
|
11
|
+
from Osdental.Enums.ResultType import ResultType
|
|
9
12
|
|
|
10
13
|
def build_request_payload(
|
|
11
14
|
request: Request,
|
|
@@ -53,8 +56,8 @@ def build_request_payload(
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
def _build_final_payload(
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
status_code: int | str,
|
|
60
|
+
type_: ResultType = ResultType.ERROR,
|
|
58
61
|
result: Optional[Any] = None,
|
|
59
62
|
error: Optional[Any] = None
|
|
60
63
|
) -> Dict[str, Any]:
|
|
@@ -73,25 +76,23 @@ def _build_final_payload(
|
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
def _build_success_payload(ctx: Optional[AuditContext], result: Response) -> Dict[str, Any]:
|
|
76
|
-
base = _base_payload(ctx
|
|
79
|
+
base = _base_payload(ctx)
|
|
77
80
|
return base | _build_final_payload(
|
|
78
|
-
type_=
|
|
81
|
+
type_=ResultType.RESPONSE,
|
|
79
82
|
status_code=result.status,
|
|
80
83
|
result=result.data
|
|
81
84
|
)
|
|
82
85
|
|
|
83
86
|
def _build_error_payload(ctx: Optional[AuditContext], exc: OSDException) -> Dict[str, Any]:
|
|
84
|
-
base = _base_payload(ctx
|
|
87
|
+
base = _base_payload(ctx)
|
|
85
88
|
return base | _build_final_payload(
|
|
86
|
-
type_="ERROR",
|
|
87
89
|
status_code=exc.audit_status_code,
|
|
88
90
|
error=exc.error or exc.message
|
|
89
91
|
)
|
|
90
92
|
|
|
91
93
|
def _build_unexpected_payload(ctx: Optional[AuditContext], exc: Exception) -> Dict[str, Any]:
|
|
92
|
-
base = _base_payload(ctx
|
|
94
|
+
base = _base_payload(ctx)
|
|
93
95
|
return base | _build_final_payload(
|
|
94
|
-
type_="ERROR",
|
|
95
96
|
status_code=StatusCode.INTERNAL_SERVER_ERROR,
|
|
96
97
|
error=str(exc)
|
|
97
98
|
)
|
|
@@ -115,4 +116,51 @@ def _base_payload(ctx: Optional[AuditContext], audit_type: AuditType = AuditType
|
|
|
115
116
|
"httpMethod": ctx.http_method,
|
|
116
117
|
"messageIn": json.dumps(ctx.variables) if ctx.variables else "*",
|
|
117
118
|
"auditLog": audit_type,
|
|
118
|
-
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ------------ FOR EXTERNAL APIS --------------------
|
|
123
|
+
def _build_success_http_payload(ctx: Optional[AuditContext], result: ApiResponse) -> Dict[str, Any]:
|
|
124
|
+
base = _base_payload(ctx, AuditType.EXTERNAL)
|
|
125
|
+
return base | _build_final_payload(
|
|
126
|
+
type_=ResultType.RESPONSE,
|
|
127
|
+
status_code=result.status,
|
|
128
|
+
result=result.data
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def _build_error_http_payload(ctx: Optional[AuditContext], exc: HTTPStatusError) -> Dict[str, Any]:
|
|
132
|
+
base = _base_payload(ctx, AuditType.EXTERNAL)
|
|
133
|
+
return base | _build_final_payload(
|
|
134
|
+
status_code=exc.response.status_code,
|
|
135
|
+
error=exc.response.text
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def _build_error_http_request_payload(ctx: Optional[AuditContext], exc: RequestError) -> Dict[str, Any]:
|
|
139
|
+
base = _base_payload(ctx, AuditType.EXTERNAL)
|
|
140
|
+
return base | _build_final_payload(
|
|
141
|
+
status_code=503,
|
|
142
|
+
error=str(exc)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def _build_unexpected_http_payload(ctx: Optional[AuditContext], exc: Exception) -> Dict[str, Any]:
|
|
146
|
+
base = _base_payload(ctx, AuditType.EXTERNAL)
|
|
147
|
+
return base | _build_final_payload(
|
|
148
|
+
status_code=StatusCode.INTERNAL_SERVER_ERROR,
|
|
149
|
+
error=str(exc)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def _build_success_graphql_payload(ctx: Optional[AuditContext], result: Dict[str, Any]) -> Dict[str, Any]:
|
|
153
|
+
base = _base_payload(ctx, AuditType.EXTERNAL)
|
|
154
|
+
return base | _build_final_payload(
|
|
155
|
+
type_=ResultType.RESPONSE,
|
|
156
|
+
status_code=200,
|
|
157
|
+
result=result
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _build_error_graphql_payload(ctx: Optional[AuditContext], errors: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
162
|
+
base = _base_payload(ctx, AuditType.EXTERNAL)
|
|
163
|
+
return base | _build_final_payload(
|
|
164
|
+
status_code=StatusCode.INTERNAL_SERVER_ERROR,
|
|
165
|
+
error=json.dumps(errors)
|
|
166
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AuditQueue:
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
worker,
|
|
12
|
+
maxsize: int = 1000,
|
|
13
|
+
workers: int = 5
|
|
14
|
+
):
|
|
15
|
+
self._worker = worker
|
|
16
|
+
self._queue = asyncio.Queue(maxsize=maxsize)
|
|
17
|
+
self._workers = workers
|
|
18
|
+
self._tasks: list[asyncio.Task] = []
|
|
19
|
+
|
|
20
|
+
async def emit(self, payload):
|
|
21
|
+
await self._queue.put(payload)
|
|
22
|
+
|
|
23
|
+
def start(self):
|
|
24
|
+
for _ in range(self._workers):
|
|
25
|
+
task = asyncio.create_task(self._consume())
|
|
26
|
+
self._tasks.append(task)
|
|
27
|
+
|
|
28
|
+
async def _consume(self):
|
|
29
|
+
while True:
|
|
30
|
+
payload = await self._queue.get()
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
await self._worker(payload)
|
|
34
|
+
|
|
35
|
+
except Exception:
|
|
36
|
+
logger.exception("Error processing audit")
|
|
37
|
+
|
|
38
|
+
finally:
|
|
39
|
+
self._queue.task_done()
|
|
40
|
+
|
|
41
|
+
async def stop(self):
|
|
42
|
+
await self._queue.join()
|
|
43
|
+
|
|
44
|
+
for task in self._tasks:
|
|
45
|
+
task.cancel()
|
|
@@ -3,12 +3,16 @@ from typing import Optional, Dict, Any
|
|
|
3
3
|
import httpx
|
|
4
4
|
from http import HTTPMethod
|
|
5
5
|
from Osdental.Decorators.Retry import rest_retry
|
|
6
|
-
from Osdental.
|
|
7
|
-
|
|
8
|
-
audit_unknown_error, audit_graphql_error
|
|
9
|
-
)
|
|
6
|
+
from Osdental.Models.ApiResponse import ApiResponse
|
|
7
|
+
from Osdental.Services import IAuditEmitter
|
|
10
8
|
from Osdental.Exception.ControlledException import HttpClientException
|
|
11
9
|
from Osdental.Enums.StatusCode import StatusCode
|
|
10
|
+
from Osdental.Context import _ctx
|
|
11
|
+
from Osdental.Helpers.AuditHelper import (
|
|
12
|
+
_build_success_http_payload, _build_error_http_payload,
|
|
13
|
+
_build_error_http_request_payload, _build_unexpected_http_payload,
|
|
14
|
+
_build_success_graphql_payload, _build_error_graphql_payload
|
|
15
|
+
)
|
|
12
16
|
|
|
13
17
|
logger = logging.getLogger(__name__)
|
|
14
18
|
|
|
@@ -16,9 +20,12 @@ class APIClient:
|
|
|
16
20
|
|
|
17
21
|
def __init__(
|
|
18
22
|
self,
|
|
23
|
+
audit_emit: Optional[IAuditEmitter] = None,
|
|
19
24
|
timeout: Optional[httpx.Timeout] = None,
|
|
20
25
|
limits: Optional[httpx.Limits] = None
|
|
21
26
|
):
|
|
27
|
+
self._audit_emit = audit_emit
|
|
28
|
+
|
|
22
29
|
self._client = httpx.AsyncClient(
|
|
23
30
|
follow_redirects=True,
|
|
24
31
|
timeout=timeout or httpx.Timeout(10.0, read=20.0),
|
|
@@ -34,6 +41,9 @@ class APIClient:
|
|
|
34
41
|
@rest_retry
|
|
35
42
|
async def _request(self, method: HTTPMethod, url: str, **kwargs) -> httpx.Response:
|
|
36
43
|
|
|
44
|
+
audit_ctx = _ctx.get()
|
|
45
|
+
audit_ctx.headers = kwargs.get("headers") or {}
|
|
46
|
+
|
|
37
47
|
try:
|
|
38
48
|
|
|
39
49
|
response = await self._client.request(
|
|
@@ -44,22 +54,31 @@ class APIClient:
|
|
|
44
54
|
|
|
45
55
|
response.raise_for_status()
|
|
46
56
|
|
|
47
|
-
|
|
57
|
+
api_res = ApiResponse(
|
|
58
|
+
status=response.status_code,
|
|
59
|
+
data=response.text
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
await self._audit_emit.emit(
|
|
63
|
+
_build_success_http_payload(audit_ctx, api_res)
|
|
64
|
+
)
|
|
48
65
|
|
|
49
66
|
return response
|
|
50
67
|
|
|
51
68
|
except httpx.HTTPStatusError as exc:
|
|
52
69
|
|
|
53
|
-
|
|
70
|
+
await self._audit_emit.emit(
|
|
71
|
+
_build_error_http_payload(audit_ctx, exc)
|
|
72
|
+
)
|
|
73
|
+
|
|
54
74
|
raise HttpClientException(
|
|
55
75
|
status_code=StatusCode.BAD_GATEWAY,
|
|
56
76
|
error=exc.response.text,
|
|
57
|
-
raw_status_code=exc.response.status_code,
|
|
58
77
|
) from exc
|
|
59
78
|
|
|
60
79
|
except httpx.RequestError as exc:
|
|
61
80
|
|
|
62
|
-
|
|
81
|
+
_build_error_http_request_payload(audit_ctx, exc)
|
|
63
82
|
raise HttpClientException(
|
|
64
83
|
status_code=StatusCode.GATEWAY_TIMEOUT,
|
|
65
84
|
error=str(exc),
|
|
@@ -67,7 +86,7 @@ class APIClient:
|
|
|
67
86
|
|
|
68
87
|
except Exception as exc:
|
|
69
88
|
|
|
70
|
-
|
|
89
|
+
_build_unexpected_http_payload(audit_ctx, exc)
|
|
71
90
|
|
|
72
91
|
raise
|
|
73
92
|
|
|
@@ -105,6 +124,9 @@ class APIClient:
|
|
|
105
124
|
headers: Optional[Dict[str, str]] = None
|
|
106
125
|
) -> Dict[str, Any]:
|
|
107
126
|
|
|
127
|
+
audit_ctx = _ctx.get()
|
|
128
|
+
audit_ctx.headers = headers or {}
|
|
129
|
+
|
|
108
130
|
payload = {
|
|
109
131
|
"query": query,
|
|
110
132
|
"variables": variables
|
|
@@ -119,13 +141,16 @@ class APIClient:
|
|
|
119
141
|
|
|
120
142
|
data = response.json()
|
|
121
143
|
|
|
122
|
-
|
|
144
|
+
_build_success_graphql_payload(audit_ctx, data)
|
|
123
145
|
|
|
146
|
+
if "errors" in data:
|
|
147
|
+
|
|
148
|
+
errors = data['errors']
|
|
124
149
|
logger.error(
|
|
125
|
-
f"GraphQL error: {
|
|
150
|
+
f"GraphQL error: {errors}"
|
|
126
151
|
)
|
|
127
152
|
|
|
128
|
-
|
|
153
|
+
_build_error_graphql_payload(audit_ctx, errors)
|
|
129
154
|
raise HttpClientException(
|
|
130
155
|
message="GraphQL execution failed",
|
|
131
156
|
error=str(data["errors"])
|
|
@@ -8,10 +8,14 @@ class ServiceBusAuditEmitter:
|
|
|
8
8
|
def __init__(self, messaging: IMessageQueue, storage: IStorageService):
|
|
9
9
|
self._messaging = messaging
|
|
10
10
|
self._storage = storage
|
|
11
|
+
|
|
11
12
|
|
|
12
|
-
async def
|
|
13
|
+
async def process(self, payload: Dict[str, Any]) -> None:
|
|
13
14
|
message_log_id = payload.get("idMessageLog")
|
|
14
15
|
data = json.dumps(payload)
|
|
15
16
|
blob_name = f"audits/{message_log_id}.text"
|
|
16
|
-
await self._storage.upload(blob_name, data)
|
|
17
|
+
url = await self._storage.upload(blob_name, data)
|
|
18
|
+
if not url:
|
|
19
|
+
return
|
|
20
|
+
|
|
17
21
|
await self._messaging.send_message(blob_name)
|
|
@@ -6,13 +6,13 @@ from azure.storage.blob import BlobSasPermissions
|
|
|
6
6
|
class IStorageService(ABC):
|
|
7
7
|
|
|
8
8
|
@abstractmethod
|
|
9
|
-
async def upload(self, blob_name: str, data: bytes | str) -> str
|
|
9
|
+
async def upload(self, blob_name: str, data: bytes | str) -> str:
|
|
10
10
|
""" Upload a file to blob storage """
|
|
11
11
|
pass
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@abstractmethod
|
|
15
|
-
async def download(self, blob_name: str) -> bytes
|
|
15
|
+
async def download(self, blob_name: str) -> bytes:
|
|
16
16
|
""" Download a file from blob storage """
|
|
17
17
|
pass
|
|
18
18
|
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
import logging
|
|
3
|
-
import json
|
|
4
|
-
from graphql.pyutils import is_awaitable
|
|
5
|
-
from ariadne.types import Extension
|
|
6
|
-
from Osdental.Encryptor.Aes import AES
|
|
7
|
-
from Osdental.Graphql._Helpers._TenantPolicy import TenantPolicy
|
|
8
|
-
from Osdental.Rest.Context.RequestContext import (
|
|
9
|
-
current_context,
|
|
10
|
-
RequestContext
|
|
11
|
-
)
|
|
12
|
-
from Osdental.Models.Response import Response
|
|
13
|
-
from Osdental.Graphql.Models import BaseGraphQLContext
|
|
14
|
-
from Osdental.Constants.Constant import Constant
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger(__name__)
|
|
17
|
-
|
|
18
|
-
class AuditExtension(Extension):
|
|
19
|
-
|
|
20
|
-
def request_started(self, context):
|
|
21
|
-
request = context.request
|
|
22
|
-
self._ctx_token = current_context.set(
|
|
23
|
-
RequestContext(
|
|
24
|
-
request=request,
|
|
25
|
-
request_id=request.headers.get("x-request-id"),
|
|
26
|
-
user=None
|
|
27
|
-
)
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
self.errors = None
|
|
31
|
-
self.request_payload = None
|
|
32
|
-
self.result = None
|
|
33
|
-
|
|
34
|
-
def _should_skip(self, body) -> bool:
|
|
35
|
-
query = body.get("query", "") or ""
|
|
36
|
-
return "__schema" in query or "__type(" in query
|
|
37
|
-
|
|
38
|
-
async def resolve(self, next_, root, info, **kwargs):
|
|
39
|
-
|
|
40
|
-
context: BaseGraphQLContext = info.context
|
|
41
|
-
request = context.request
|
|
42
|
-
|
|
43
|
-
# cache body
|
|
44
|
-
context._cached_body = (
|
|
45
|
-
getattr(context, "_cached_body", None) or await request.json()
|
|
46
|
-
)
|
|
47
|
-
body = context._cached_body
|
|
48
|
-
|
|
49
|
-
# skip introspection
|
|
50
|
-
if self._should_skip(body):
|
|
51
|
-
result = next_(root, info, **kwargs)
|
|
52
|
-
if is_awaitable(result):
|
|
53
|
-
result = await result
|
|
54
|
-
return result
|
|
55
|
-
|
|
56
|
-
is_root = root is None
|
|
57
|
-
|
|
58
|
-
# SOLO ROOT
|
|
59
|
-
if is_root:
|
|
60
|
-
|
|
61
|
-
# identificar resolver público
|
|
62
|
-
resolver_fn = inspect.unwrap(next_)
|
|
63
|
-
is_public = getattr(resolver_fn, "_is_public", False)
|
|
64
|
-
|
|
65
|
-
# inicializar auth UNA SOLA VEZ
|
|
66
|
-
if not hasattr(context, "_auth_initialized"):
|
|
67
|
-
context._auth_initialized = True
|
|
68
|
-
|
|
69
|
-
headers = request.headers
|
|
70
|
-
container = context.container
|
|
71
|
-
|
|
72
|
-
original_token = None
|
|
73
|
-
context.aes_auth = None
|
|
74
|
-
if not is_public and headers.get("authorization"):
|
|
75
|
-
token_service = container.token_service
|
|
76
|
-
aes_auth = request.app.state.aes_auth
|
|
77
|
-
aes_user = request.app.state.aes_user
|
|
78
|
-
|
|
79
|
-
try:
|
|
80
|
-
original_token = await token_service.authenticate(headers, aes_user)
|
|
81
|
-
except Exception:
|
|
82
|
-
original_token = None
|
|
83
|
-
|
|
84
|
-
context.aes_auth = aes_auth
|
|
85
|
-
|
|
86
|
-
# VALIDACIÓN SOLO ROOT
|
|
87
|
-
if not is_public and not original_token:
|
|
88
|
-
raise ValueError("Authorization required")
|
|
89
|
-
|
|
90
|
-
# payload
|
|
91
|
-
variables = body.get("variables") or {}
|
|
92
|
-
|
|
93
|
-
encrypted_payload = (
|
|
94
|
-
kwargs.get("data")
|
|
95
|
-
or variables.get("data")
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
decrypted_payload = None
|
|
99
|
-
|
|
100
|
-
if not is_public and encrypted_payload is not None:
|
|
101
|
-
|
|
102
|
-
if not isinstance(encrypted_payload, str):
|
|
103
|
-
raise ValueError("Encrypted payload must be a string")
|
|
104
|
-
|
|
105
|
-
if not context.aes_auth:
|
|
106
|
-
raise ValueError("Missing AES configuration")
|
|
107
|
-
|
|
108
|
-
try:
|
|
109
|
-
decrypted = AES.decrypt(context.aes_auth, encrypted_payload)
|
|
110
|
-
except Exception:
|
|
111
|
-
raise ValueError("Invalid encrypted payload")
|
|
112
|
-
|
|
113
|
-
if isinstance(decrypted, dict):
|
|
114
|
-
decrypted_payload = decrypted
|
|
115
|
-
|
|
116
|
-
elif isinstance(decrypted, str):
|
|
117
|
-
try:
|
|
118
|
-
decrypted_payload = json.loads(decrypted)
|
|
119
|
-
except Exception:
|
|
120
|
-
raise ValueError("Decrypted payload is not valid JSON")
|
|
121
|
-
|
|
122
|
-
else:
|
|
123
|
-
raise ValueError("Unsupported decrypted payload type")
|
|
124
|
-
|
|
125
|
-
if decrypted_payload is not None:
|
|
126
|
-
kwargs["data"] = decrypted_payload
|
|
127
|
-
|
|
128
|
-
if not is_public:
|
|
129
|
-
token = TenantPolicy.resolve(
|
|
130
|
-
token=original_token,
|
|
131
|
-
headers=request.headers,
|
|
132
|
-
decrypted_payload=decrypted_payload,
|
|
133
|
-
operation_type=info.operation.operation.value
|
|
134
|
-
)
|
|
135
|
-
context.token = token
|
|
136
|
-
|
|
137
|
-
# auditoría request payload
|
|
138
|
-
if not self.request_payload:
|
|
139
|
-
token = context.token
|
|
140
|
-
|
|
141
|
-
self.request = request
|
|
142
|
-
self.request_payload = {
|
|
143
|
-
"operation_type": info.operation.operation.value,
|
|
144
|
-
"operation_name": body.get("operationName", "UnknownOperation"),
|
|
145
|
-
"query": body.get("query"),
|
|
146
|
-
"variables": decrypted_payload,
|
|
147
|
-
"user": token.user_full_name if token else "Public"
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
# SIEMPRE ejecutar resolver (root + fields)
|
|
151
|
-
result = next_(root, info, **kwargs)
|
|
152
|
-
|
|
153
|
-
if is_awaitable(result):
|
|
154
|
-
result = await result
|
|
155
|
-
|
|
156
|
-
# SOLO ROOT captura resultado final
|
|
157
|
-
if is_root and self.result is None:
|
|
158
|
-
self.result = result
|
|
159
|
-
|
|
160
|
-
return result
|
|
161
|
-
|
|
162
|
-
def has_errors(self, errors, context):
|
|
163
|
-
self.errors = errors
|
|
164
|
-
|
|
165
|
-
def request_finished(self, context):
|
|
166
|
-
|
|
167
|
-
if not self.request_payload:
|
|
168
|
-
return
|
|
169
|
-
|
|
170
|
-
query = self.request_payload.get("query", "")
|
|
171
|
-
|
|
172
|
-
if self._should_skip({"query": query}):
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
if self.result is None:
|
|
176
|
-
return
|
|
177
|
-
|
|
178
|
-
decrypted_key = context.aes_auth
|
|
179
|
-
|
|
180
|
-
if isinstance(self.result, Response):
|
|
181
|
-
decrypted_key = self.result.key or decrypted_key
|
|
182
|
-
|
|
183
|
-
dispatcher = context.request.app.state.audit_dispatcher
|
|
184
|
-
|
|
185
|
-
try:
|
|
186
|
-
dispatcher.dispatch(
|
|
187
|
-
request=self.request,
|
|
188
|
-
request_payload=self.request_payload,
|
|
189
|
-
result=self.result,
|
|
190
|
-
metadata={
|
|
191
|
-
"decrypted_key": decrypted_key
|
|
192
|
-
},
|
|
193
|
-
audit_type=Constant.MESSAGE_LOG_INTERNAL
|
|
194
|
-
)
|
|
195
|
-
except Exception as e:
|
|
196
|
-
logger.warning(f"[AUDIT ERROR]: {str(e)}")
|