Encryptors 2.58__tar.gz → 2.59__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.59}/PKG-INFO +1 -1
- {encryptors-2.58 → encryptors-2.59}/setup.py +1 -1
- {encryptors-2.58 → encryptors-2.59}/src/Encryptors.egg-info/PKG-INFO +1 -1
- {encryptors-2.58 → encryptors-2.59}/src/Encryptors.egg-info/SOURCES.txt +3 -17
- encryptors-2.59/src/Osdental/Context/__init__.py +5 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Decorators/SecureResolver.py +4 -3
- encryptors-2.59/src/Osdental/Enums/ResultType.py +5 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/AuditHelper.py +58 -10
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Http/APIClient.py +39 -12
- 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.59}/README.md +0 -0
- {encryptors-2.58 → encryptors-2.59}/setup.cfg +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Encryptors.egg-info/dependency_links.txt +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Encryptors.egg-info/entry_points.txt +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Encryptors.egg-info/requires.txt +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Encryptors.egg-info/top_level.txt +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Cache/Redis.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Cache/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Cli/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Constants/Constant.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Constants/Message.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Constants/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Database/BaseRepository.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Database/Connection.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Database/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Decorators/Grpc.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Decorators/Retry.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Decorators/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/Aes.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/Argon2.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/Bcrypt.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/Jwt.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/Rsa.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/Sha512.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/AuditType.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/ErrorSource.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/FileType.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/GrahpqlOperation.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/Profile.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/StatusCode.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Exception/ControlledException.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Exception/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/AzureClassifier.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/GrpcConnection.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/JwtTokenHelper.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/Resilience.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/ResponseDecryptor.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/_AuthTokenProcessor.py +0 -0
- {encryptors-2.58/src/Osdental/Graphql/Extensions → encryptors-2.59/src/Osdental/Helpers}/__init__.py +0 -0
- {encryptors-2.58/src/Osdental/Graphql/_Helpers → encryptors-2.59/src/Osdental/Http}/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Messaging/AzureServiceBus.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Messaging/Kafka.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Messaging/RabbitMQ.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Messaging/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Models/ApiResponse.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Models/AuditContext.py +0 -0
- /encryptors-2.58/src/Osdental/Graphql/Models/__init__.py → /encryptors-2.59/src/Osdental/Models/Graphql.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Models/Notification.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Models/Response.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Models/Token.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Models/TokenClaims.py +0 -0
- {encryptors-2.58/src/Osdental/Graphql → encryptors-2.59/src/Osdental/Models}/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Secrets/AzureKeyVaultProvider.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Secrets/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Services/JwtAuthTokenService.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Services/ServiceBusAuditEmitter.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Services/WebsocketClient.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Services/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Storage/S3Storage.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Storage/__init__.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/CaseConverter.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/CodeGenerator.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/DataNormalizer.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/DataUtils.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/DateUtils.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/FileMetaData.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/HashValidator.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/Mapper.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/PasswordGenerator.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/QueryGenerator.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/RsaUtils.py +0 -0
- {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/TextProcessor.py +0 -0
- {encryptors-2.58/src/Osdental/Helpers → encryptors-2.59/src/Osdental/Utils}/__init__.py +0 -0
- {encryptors-2.58/src/Osdental/Http → encryptors-2.59/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.59",
|
|
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,21 +33,11 @@ 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
|
|
51
42
|
src/Osdental/Helpers/AzureClassifier.py
|
|
52
43
|
src/Osdental/Helpers/GrpcConnection.py
|
|
@@ -56,7 +47,6 @@ src/Osdental/Helpers/ResponseDecryptor.py
|
|
|
56
47
|
src/Osdental/Helpers/_AuthTokenProcessor.py
|
|
57
48
|
src/Osdental/Helpers/__init__.py
|
|
58
49
|
src/Osdental/Http/APIClient.py
|
|
59
|
-
src/Osdental/Http/_Helpers.py
|
|
60
50
|
src/Osdental/Http/__init__.py
|
|
61
51
|
src/Osdental/Messaging/AzureServiceBus.py
|
|
62
52
|
src/Osdental/Messaging/Kafka.py
|
|
@@ -64,16 +54,12 @@ src/Osdental/Messaging/RabbitMQ.py
|
|
|
64
54
|
src/Osdental/Messaging/__init__.py
|
|
65
55
|
src/Osdental/Models/ApiResponse.py
|
|
66
56
|
src/Osdental/Models/AuditContext.py
|
|
57
|
+
src/Osdental/Models/Graphql.py
|
|
67
58
|
src/Osdental/Models/Notification.py
|
|
68
59
|
src/Osdental/Models/Response.py
|
|
69
60
|
src/Osdental/Models/Token.py
|
|
70
61
|
src/Osdental/Models/TokenClaims.py
|
|
71
62
|
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
63
|
src/Osdental/Secrets/AzureKeyVaultProvider.py
|
|
78
64
|
src/Osdental/Secrets/__init__.py
|
|
79
65
|
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
|
+
)
|
|
@@ -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: Optional[IAuditEmitter] = None,
|
|
19
24
|
timeout: Optional[httpx.Timeout] = None,
|
|
20
25
|
limits: Optional[httpx.Limits] = None
|
|
21
26
|
):
|
|
27
|
+
self._audit = audit
|
|
28
|
+
|
|
22
29
|
self._client = httpx.AsyncClient(
|
|
23
30
|
follow_redirects=True,
|
|
24
31
|
timeout=timeout or httpx.Timeout(10.0, read=20.0),
|
|
@@ -42,24 +49,37 @@ class APIClient:
|
|
|
42
49
|
**kwargs
|
|
43
50
|
)
|
|
44
51
|
|
|
52
|
+
audit_ctx = _ctx.get()
|
|
53
|
+
|
|
54
|
+
audit_ctx.headers = kwargs["headers"] or {}
|
|
55
|
+
|
|
45
56
|
response.raise_for_status()
|
|
46
57
|
|
|
47
|
-
|
|
58
|
+
api_res = ApiResponse(
|
|
59
|
+
status=response.status_code,
|
|
60
|
+
data=response.text
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
await self._audit_emit.emit(
|
|
64
|
+
_build_success_http_payload(audit_ctx, api_res)
|
|
65
|
+
)
|
|
48
66
|
|
|
49
67
|
return response
|
|
50
68
|
|
|
51
69
|
except httpx.HTTPStatusError as exc:
|
|
52
70
|
|
|
53
|
-
|
|
71
|
+
await self._audit_emit.emit(
|
|
72
|
+
_build_error_http_payload(audit_ctx, exc)
|
|
73
|
+
)
|
|
74
|
+
|
|
54
75
|
raise HttpClientException(
|
|
55
76
|
status_code=StatusCode.BAD_GATEWAY,
|
|
56
77
|
error=exc.response.text,
|
|
57
|
-
raw_status_code=exc.response.status_code,
|
|
58
78
|
) from exc
|
|
59
79
|
|
|
60
80
|
except httpx.RequestError as exc:
|
|
61
81
|
|
|
62
|
-
|
|
82
|
+
_build_error_http_request_payload(audit_ctx, exc)
|
|
63
83
|
raise HttpClientException(
|
|
64
84
|
status_code=StatusCode.GATEWAY_TIMEOUT,
|
|
65
85
|
error=str(exc),
|
|
@@ -67,7 +87,7 @@ class APIClient:
|
|
|
67
87
|
|
|
68
88
|
except Exception as exc:
|
|
69
89
|
|
|
70
|
-
|
|
90
|
+
_build_unexpected_http_payload(audit_ctx, exc)
|
|
71
91
|
|
|
72
92
|
raise
|
|
73
93
|
|
|
@@ -119,13 +139,20 @@ class APIClient:
|
|
|
119
139
|
|
|
120
140
|
data = response.json()
|
|
121
141
|
|
|
122
|
-
|
|
142
|
+
audit_ctx = _ctx.get()
|
|
143
|
+
|
|
144
|
+
audit_ctx.headers = headers or {}
|
|
123
145
|
|
|
146
|
+
_build_success_graphql_payload(audit_ctx, data)
|
|
147
|
+
|
|
148
|
+
if "errors" in data:
|
|
149
|
+
|
|
150
|
+
errors = data['errors']
|
|
124
151
|
logger.error(
|
|
125
|
-
f"GraphQL error: {
|
|
152
|
+
f"GraphQL error: {errors}"
|
|
126
153
|
)
|
|
127
154
|
|
|
128
|
-
|
|
155
|
+
_build_error_graphql_payload(audit_ctx, errors)
|
|
129
156
|
raise HttpClientException(
|
|
130
157
|
message="GraphQL execution failed",
|
|
131
158
|
error=str(data["errors"])
|
|
@@ -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)}")
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import Dict, Literal, Any, Optional
|
|
3
|
-
|
|
4
|
-
class AuditHelper:
|
|
5
|
-
|
|
6
|
-
@staticmethod
|
|
7
|
-
def build_request_payload(
|
|
8
|
-
request,
|
|
9
|
-
payload: Dict[str, Any],
|
|
10
|
-
audit_type: str
|
|
11
|
-
) -> Dict[str, Any]:
|
|
12
|
-
|
|
13
|
-
default_value = "*"
|
|
14
|
-
|
|
15
|
-
user_ip = request.headers.get("X-Forwarded-For")
|
|
16
|
-
if user_ip:
|
|
17
|
-
user_ip = user_ip.split(",")[0]
|
|
18
|
-
else:
|
|
19
|
-
user_ip = getattr(request.client, "host", "*")
|
|
20
|
-
|
|
21
|
-
SAFE_HEADERS = {
|
|
22
|
-
"user-agent",
|
|
23
|
-
"host",
|
|
24
|
-
"origin",
|
|
25
|
-
"referer",
|
|
26
|
-
"content-type"
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
headers = {k: v for k, v in request.headers.items() if k.lower() in SAFE_HEADERS}
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
"idMessageLog": request.headers.get("Idmessagelog"),
|
|
33
|
-
"environment": payload.get("env"),
|
|
34
|
-
"header": json.dumps(headers),
|
|
35
|
-
"microServiceUrl": str(request.url),
|
|
36
|
-
"microServiceName": payload.get("ms_name"),
|
|
37
|
-
"microServiceVersion": payload.get("ms_version"),
|
|
38
|
-
"serviceName": payload.get("operation_name"),
|
|
39
|
-
"machineNameUser": request.headers.get("Machinenameuser", default_value),
|
|
40
|
-
"ipUser": user_ip or default_value,
|
|
41
|
-
"userName": payload.get("user"),
|
|
42
|
-
"localitation": default_value,
|
|
43
|
-
"httpMethod": request.method,
|
|
44
|
-
"messageIn": json.dumps(payload) if payload else default_value,
|
|
45
|
-
"auditLog": audit_type,
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
@staticmethod
|
|
49
|
-
def build_final_payload(
|
|
50
|
-
_type: Literal["RESPONSE", "ERROR"],
|
|
51
|
-
status_code: int,
|
|
52
|
-
result: Optional[Any] = None,
|
|
53
|
-
error: Optional[Any] = None
|
|
54
|
-
) -> Dict[str, Any]:
|
|
55
|
-
|
|
56
|
-
result = "*" if result is None else result
|
|
57
|
-
error = "*" if error is None else error
|
|
58
|
-
|
|
59
|
-
if isinstance(result, (dict, list)):
|
|
60
|
-
result = json.dumps(result)
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
"type": _type,
|
|
64
|
-
"httpResponseCode": status_code,
|
|
65
|
-
"messageOut": result,
|
|
66
|
-
"errorProducer": error
|
|
67
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
from starlette.datastructures import Headers
|
|
2
|
-
|
|
3
|
-
class ExtractAuthToken:
|
|
4
|
-
|
|
5
|
-
@staticmethod
|
|
6
|
-
def get_auth_token(headers: Headers) -> str:
|
|
7
|
-
authorization = headers.get("authorization")
|
|
8
|
-
if not authorization or not authorization.startswith("Bearer "):
|
|
9
|
-
raise ValueError("Missing Bearer token")
|
|
10
|
-
|
|
11
|
-
return authorization.split(" ")[1]
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
from typing import Dict, Any
|
|
2
|
-
from uuid import UUID
|
|
3
|
-
from graphql import OperationType
|
|
4
|
-
from starlette.datastructures import Headers
|
|
5
|
-
from Osdental.Encryptor.Aes import AES
|
|
6
|
-
from Osdental.Models.Token import AuthToken
|
|
7
|
-
from Osdental.Enums.Profile import Profile
|
|
8
|
-
|
|
9
|
-
class TenantPolicy:
|
|
10
|
-
|
|
11
|
-
@staticmethod
|
|
12
|
-
def resolve(
|
|
13
|
-
token: AuthToken,
|
|
14
|
-
headers: Headers,
|
|
15
|
-
decrypted_payload: Dict[str, Any],
|
|
16
|
-
operation_type: str
|
|
17
|
-
) -> AuthToken:
|
|
18
|
-
|
|
19
|
-
final_id = None
|
|
20
|
-
dynamic_client_id = headers.get("dynamicClientId", None)
|
|
21
|
-
external_enterprise_req = (
|
|
22
|
-
decrypted_payload.get("idExternalEnterprise")
|
|
23
|
-
if decrypted_payload else None
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
if external_enterprise_req:
|
|
27
|
-
final_id = external_enterprise_req
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
elif token.abbreviation.startswith(
|
|
31
|
-
(Profile.MARKETING, Profile.SUPER_ADMIN, Profile.ADMIN_OSD)
|
|
32
|
-
) and dynamic_client_id:
|
|
33
|
-
decrypted_mk_id = AES.decrypt(token.aes_key_auth, dynamic_client_id)
|
|
34
|
-
final_id = decrypted_mk_id
|
|
35
|
-
token.mk_id_external_enterprise = decrypted_mk_id
|
|
36
|
-
|
|
37
|
-
elif (
|
|
38
|
-
token.abbreviation.startswith((Profile.SUPER_ADMIN, Profile.ADMIN_OSD))
|
|
39
|
-
and operation_type == OperationType.QUERY.value and not dynamic_client_id
|
|
40
|
-
):
|
|
41
|
-
final_id = str(UUID(int=0))
|
|
42
|
-
|
|
43
|
-
if final_id:
|
|
44
|
-
token.id_external_enterprise = final_id
|
|
45
|
-
|
|
46
|
-
return token
|