Encryptors 2.81.0__tar.gz → 2.81.2__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.81.0 → encryptors-2.81.2}/PKG-INFO +1 -1
- {encryptors-2.81.0 → encryptors-2.81.2}/setup.py +1 -1
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Encryptors.egg-info/PKG-INFO +1 -1
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Encryptors.egg-info/SOURCES.txt +11 -26
- encryptors-2.81.2/src/Osdental/Decorators/SecureResolver.py +119 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Exception/ControlledException.py +4 -10
- encryptors-2.81.0/src/Osdental/Graphql/_Helpers/_TokenService.py → encryptors-2.81.2/src/Osdental/Helpers/token_validation_service.py +13 -8
- encryptors-2.81.2/src/Osdental/Http/Constants.py +5 -0
- encryptors-2.81.2/src/Osdental/Http/Exceptions.py +25 -0
- encryptors-2.81.0/src/Osdental/Http/APIClient.py → encryptors-2.81.2/src/Osdental/Http/HttpxClient.py +39 -40
- encryptors-2.81.2/src/Osdental/Http/Messages.py +1 -0
- encryptors-2.81.2/src/Osdental/Http/Types.py +3 -0
- encryptors-2.81.2/src/Osdental/Http/__init__.py +30 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Messaging/AzureServiceBus.py +6 -0
- encryptors-2.81.0/src/Osdental/Messaging/__init__.py → encryptors-2.81.2/src/Osdental/Messaging/Kafka.py +6 -7
- encryptors-2.81.2/src/Osdental/Messaging/RabbitMQ.py +15 -0
- encryptors-2.81.2/src/Osdental/Messaging/__init__.py +37 -0
- encryptors-2.81.0/src/Osdental/Models/ApiResponse.py → encryptors-2.81.2/src/Osdental/Models/Response.py +5 -9
- encryptors-2.81.0/src/Osdental/Graphql/Models/__init__.py → encryptors-2.81.2/src/Osdental/Models/base_graphql_context.py +1 -4
- {encryptors-2.81.0/src/Osdental/Helpers → encryptors-2.81.2/src/Osdental/Secrets}/KeyVaultService.py +36 -11
- encryptors-2.81.2/src/Osdental/Secrets/__init__.py +83 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Utils/Mapper.py +18 -16
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Storage/AzureBlobStorage.py +9 -9
- encryptors-2.81.2/src/Osdental/Storage/__init__.py +118 -0
- encryptors-2.81.0/src/Osdental/Decorators/SecureResolver.py +0 -59
- encryptors-2.81.0/src/Osdental/Decorators/SqlDataNormalizer.py +0 -60
- encryptors-2.81.0/src/Osdental/Graphql/Extensions/AuditExtension.py +0 -194
- encryptors-2.81.0/src/Osdental/Graphql/_Exceptions/__init__.py +0 -8
- encryptors-2.81.0/src/Osdental/Graphql/_Helpers/_AuditHelper.py +0 -67
- encryptors-2.81.0/src/Osdental/Helpers/AuditDispatcher.py +0 -172
- encryptors-2.81.0/src/Osdental/Helpers/WebsocketClient.py +0 -44
- encryptors-2.81.0/src/Osdental/Helpers/_Ports.py +0 -11
- encryptors-2.81.0/src/Osdental/Http/_Helpers.py +0 -122
- encryptors-2.81.0/src/Osdental/Messaging/Kafka.py +0 -11
- encryptors-2.81.0/src/Osdental/Messaging/RabbitMQ.py +0 -11
- encryptors-2.81.0/src/Osdental/Models/AuditConfig.py +0 -9
- encryptors-2.81.0/src/Osdental/Models/Notification.py +0 -10
- encryptors-2.81.0/src/Osdental/Models/Response.py +0 -27
- encryptors-2.81.0/src/Osdental/Models/_Audit.py +0 -14
- encryptors-2.81.0/src/Osdental/Rest/Context/RequestContext.py +0 -19
- encryptors-2.81.0/src/Osdental/Rest/Context/__init__.py +0 -0
- encryptors-2.81.0/src/Osdental/Rest/Middlewares/RequestContextMiddleware.py +0 -22
- encryptors-2.81.0/src/Osdental/Rest/Middlewares/__init__.py +0 -0
- encryptors-2.81.0/src/Osdental/Rest/__init__.py +0 -0
- encryptors-2.81.0/src/Osdental/Shared/Enums/__init__.py +0 -0
- encryptors-2.81.0/src/Osdental/Shared/Utils/__init__.py +0 -0
- encryptors-2.81.0/src/Osdental/Shared/__init__.py +0 -0
- encryptors-2.81.0/src/Osdental/Storage/__init__.py +0 -36
- encryptors-2.81.0/src/Osdental/__init__.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/README.md +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/setup.cfg +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Encryptors.egg-info/dependency_links.txt +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Encryptors.egg-info/entry_points.txt +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Encryptors.egg-info/requires.txt +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Encryptors.egg-info/top_level.txt +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Cache/Redis.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Cache/__init__.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Cli/__init__.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Database/BaseRepository.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Database/Connection.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Database/SqlExceptionTranslator.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Database/SqlTransientDetector.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Database/StoredProcedureResultValidator.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Database/__init__.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Decorators/Grpc.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Decorators/PublicResolver.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Decorators/Retry.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Decorators/__init__.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Encryptor/Aes.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Encryptor/Argon2.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Encryptor/Bcrypt.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Encryptor/Jwt.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Encryptor/Rsa.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Encryptor/Sha512.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Encryptor/__init__.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Exception/__init__.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Helpers/AzureClassifier.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Helpers/GrpcConnection.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Helpers/Resilience.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Helpers/ResponseDecryptor.py +0 -0
- {encryptors-2.81.0/src/Osdental/Graphql/Extensions → encryptors-2.81.2/src/Osdental/Helpers}/__init__.py +0 -0
- /encryptors-2.81.0/src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py → /encryptors-2.81.2/src/Osdental/Helpers/extract_auth_token.py +0 -0
- /encryptors-2.81.0/src/Osdental/Graphql/_Helpers/_TenantPolicy.py → /encryptors-2.81.2/src/Osdental/Helpers/tenant_policy.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Models/Token.py +0 -0
- {encryptors-2.81.0/src/Osdental/Graphql/_Helpers → encryptors-2.81.2/src/Osdental/Models}/__init__.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Enums/Code.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Enums/Constant.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Enums/FileType.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Enums/GrahpqlOperation.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Enums/Message.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Enums/Profile.py +0 -0
- {encryptors-2.81.0/src/Osdental/Graphql → encryptors-2.81.2/src/Osdental/Shared/Enums}/__init__.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Logger.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Utils/CaseConverter.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Utils/CodeGenerator.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Utils/DataNormalizer.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Utils/DataUtils.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Utils/DateUtils.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Utils/FileMetaData.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Utils/HashValidator.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Utils/PasswordGenerator.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Utils/QueryGenerator.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Utils/RsaUtils.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Shared/Utils/TextProcessor.py +0 -0
- {encryptors-2.81.0/src/Osdental/Helpers → encryptors-2.81.2/src/Osdental/Shared/Utils}/__init__.py +0 -0
- {encryptors-2.81.0/src/Osdental/Http → encryptors-2.81.2/src/Osdental/Shared}/__init__.py +0 -0
- {encryptors-2.81.0 → encryptors-2.81.2}/src/Osdental/Storage/S3Storage.py +0 -0
- {encryptors-2.81.0/src/Osdental/Models → encryptors-2.81.2/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.81.
|
|
5
|
+
version="2.81.2",
|
|
6
6
|
author="OSDental LLC",
|
|
7
7
|
author_email="support@osdental.ai",
|
|
8
8
|
description="End-to-end algorithm library",
|
|
@@ -20,7 +20,6 @@ src/Osdental/Decorators/Grpc.py
|
|
|
20
20
|
src/Osdental/Decorators/PublicResolver.py
|
|
21
21
|
src/Osdental/Decorators/Retry.py
|
|
22
22
|
src/Osdental/Decorators/SecureResolver.py
|
|
23
|
-
src/Osdental/Decorators/SqlDataNormalizer.py
|
|
24
23
|
src/Osdental/Decorators/__init__.py
|
|
25
24
|
src/Osdental/Encryptor/Aes.py
|
|
26
25
|
src/Osdental/Encryptor/Argon2.py
|
|
@@ -31,44 +30,30 @@ src/Osdental/Encryptor/Sha512.py
|
|
|
31
30
|
src/Osdental/Encryptor/__init__.py
|
|
32
31
|
src/Osdental/Exception/ControlledException.py
|
|
33
32
|
src/Osdental/Exception/__init__.py
|
|
34
|
-
src/Osdental/Graphql/__init__.py
|
|
35
|
-
src/Osdental/Graphql/Extensions/AuditExtension.py
|
|
36
|
-
src/Osdental/Graphql/Extensions/__init__.py
|
|
37
|
-
src/Osdental/Graphql/Models/__init__.py
|
|
38
|
-
src/Osdental/Graphql/_Exceptions/__init__.py
|
|
39
|
-
src/Osdental/Graphql/_Helpers/_AuditHelper.py
|
|
40
|
-
src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py
|
|
41
|
-
src/Osdental/Graphql/_Helpers/_TenantPolicy.py
|
|
42
|
-
src/Osdental/Graphql/_Helpers/_TokenService.py
|
|
43
|
-
src/Osdental/Graphql/_Helpers/__init__.py
|
|
44
|
-
src/Osdental/Helpers/AuditDispatcher.py
|
|
45
33
|
src/Osdental/Helpers/AzureClassifier.py
|
|
46
34
|
src/Osdental/Helpers/GrpcConnection.py
|
|
47
|
-
src/Osdental/Helpers/KeyVaultService.py
|
|
48
35
|
src/Osdental/Helpers/Resilience.py
|
|
49
36
|
src/Osdental/Helpers/ResponseDecryptor.py
|
|
50
|
-
src/Osdental/Helpers/WebsocketClient.py
|
|
51
|
-
src/Osdental/Helpers/_Ports.py
|
|
52
37
|
src/Osdental/Helpers/__init__.py
|
|
53
|
-
src/Osdental/
|
|
54
|
-
src/Osdental/
|
|
38
|
+
src/Osdental/Helpers/extract_auth_token.py
|
|
39
|
+
src/Osdental/Helpers/tenant_policy.py
|
|
40
|
+
src/Osdental/Helpers/token_validation_service.py
|
|
41
|
+
src/Osdental/Http/Constants.py
|
|
42
|
+
src/Osdental/Http/Exceptions.py
|
|
43
|
+
src/Osdental/Http/HttpxClient.py
|
|
44
|
+
src/Osdental/Http/Messages.py
|
|
45
|
+
src/Osdental/Http/Types.py
|
|
55
46
|
src/Osdental/Http/__init__.py
|
|
56
47
|
src/Osdental/Messaging/AzureServiceBus.py
|
|
57
48
|
src/Osdental/Messaging/Kafka.py
|
|
58
49
|
src/Osdental/Messaging/RabbitMQ.py
|
|
59
50
|
src/Osdental/Messaging/__init__.py
|
|
60
|
-
src/Osdental/Models/ApiResponse.py
|
|
61
|
-
src/Osdental/Models/AuditConfig.py
|
|
62
|
-
src/Osdental/Models/Notification.py
|
|
63
51
|
src/Osdental/Models/Response.py
|
|
64
52
|
src/Osdental/Models/Token.py
|
|
65
|
-
src/Osdental/Models/_Audit.py
|
|
66
53
|
src/Osdental/Models/__init__.py
|
|
67
|
-
src/Osdental/
|
|
68
|
-
src/Osdental/
|
|
69
|
-
src/Osdental/
|
|
70
|
-
src/Osdental/Rest/Middlewares/RequestContextMiddleware.py
|
|
71
|
-
src/Osdental/Rest/Middlewares/__init__.py
|
|
54
|
+
src/Osdental/Models/base_graphql_context.py
|
|
55
|
+
src/Osdental/Secrets/KeyVaultService.py
|
|
56
|
+
src/Osdental/Secrets/__init__.py
|
|
72
57
|
src/Osdental/Shared/Logger.py
|
|
73
58
|
src/Osdental/Shared/__init__.py
|
|
74
59
|
src/Osdental/Shared/Enums/Code.py
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from typing import Callable
|
|
4
|
+
from Osdental.Models.Response import Response
|
|
5
|
+
from Osdental.Shared.Enums.Profile import Profile
|
|
6
|
+
from Osdental.Exception.ControlledException import (
|
|
7
|
+
OSDException, AccessDeniedException, UnauthorizedException
|
|
8
|
+
)
|
|
9
|
+
from Osdental.Encryptor.Aes import AES
|
|
10
|
+
from Osdental.Models.base_graphql_context import BaseGraphQLContext
|
|
11
|
+
from Osdental.Helpers.tenant_policy import TenantPolicy
|
|
12
|
+
from Osdental.Secrets import ISecretProvider
|
|
13
|
+
from Osdental.Shared.Logger import logger
|
|
14
|
+
|
|
15
|
+
def resolver(action=None):
|
|
16
|
+
|
|
17
|
+
def decorator(func: Callable):
|
|
18
|
+
|
|
19
|
+
@wraps(func)
|
|
20
|
+
async def wrapper(obj, info, **kwargs):
|
|
21
|
+
try:
|
|
22
|
+
context: BaseGraphQLContext = info.context
|
|
23
|
+
request = context.request
|
|
24
|
+
token_service = context.container.token_service
|
|
25
|
+
|
|
26
|
+
if action:
|
|
27
|
+
if Profile(token.abbreviation) not in action.allowed_roles:
|
|
28
|
+
raise AccessDeniedException(
|
|
29
|
+
error="User not allowed to perform this action"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Get secrets
|
|
33
|
+
secret_service: ISecretProvider = context.container.secret_service
|
|
34
|
+
keys = await secret_service.get_many(
|
|
35
|
+
["AES-KEY-AUTH", "AES-KEY-USER", "AUTH-JWT-USER-KEY"]
|
|
36
|
+
)
|
|
37
|
+
#
|
|
38
|
+
|
|
39
|
+
encrypted_payload = kwargs.get("data")
|
|
40
|
+
|
|
41
|
+
decrypted_payload = None
|
|
42
|
+
|
|
43
|
+
if encrypted_payload is not None:
|
|
44
|
+
|
|
45
|
+
if not isinstance(encrypted_payload, str):
|
|
46
|
+
raise ValueError("Encrypted payload must be a string")
|
|
47
|
+
|
|
48
|
+
if not context.aes_auth:
|
|
49
|
+
raise ValueError("Missing AES configuration")
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
decrypted = AES.decrypt(context.aes_auth, encrypted_payload)
|
|
53
|
+
except Exception:
|
|
54
|
+
raise ValueError("Invalid encrypted payload")
|
|
55
|
+
|
|
56
|
+
if isinstance(decrypted, dict):
|
|
57
|
+
decrypted_payload = decrypted
|
|
58
|
+
|
|
59
|
+
elif isinstance(decrypted, str):
|
|
60
|
+
try:
|
|
61
|
+
decrypted_payload = json.loads(decrypted)
|
|
62
|
+
except Exception:
|
|
63
|
+
raise ValueError("Decrypted payload is not valid JSON")
|
|
64
|
+
|
|
65
|
+
else:
|
|
66
|
+
raise ValueError("Unsupported decrypted payload type")
|
|
67
|
+
|
|
68
|
+
if decrypted_payload is not None:
|
|
69
|
+
kwargs["data"] = decrypted_payload
|
|
70
|
+
|
|
71
|
+
# token
|
|
72
|
+
try:
|
|
73
|
+
original_token = await token_service.authenticate(
|
|
74
|
+
request.headers,
|
|
75
|
+
keys["AUTH-JWT-USER-KEY"],
|
|
76
|
+
keys["AES-KEY-USER"],
|
|
77
|
+
)
|
|
78
|
+
except Exception:
|
|
79
|
+
original_token = None
|
|
80
|
+
|
|
81
|
+
token = TenantPolicy.resolve(
|
|
82
|
+
token=original_token,
|
|
83
|
+
headers=request.headers,
|
|
84
|
+
decrypted_payload=decrypted_payload,
|
|
85
|
+
operation_type=info.operation.operation.value
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if not token:
|
|
89
|
+
raise UnauthorizedException(
|
|
90
|
+
error="Authorization required"
|
|
91
|
+
)
|
|
92
|
+
# token
|
|
93
|
+
|
|
94
|
+
result = await func(obj, info, **kwargs)
|
|
95
|
+
|
|
96
|
+
if not isinstance(result, Response):
|
|
97
|
+
raise TypeError("Resolver must return a Response instance")
|
|
98
|
+
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
except OSDException as e:
|
|
102
|
+
logger.warning(f"Business error: {str(e)}")
|
|
103
|
+
|
|
104
|
+
return Response(
|
|
105
|
+
status=e.status_code,
|
|
106
|
+
message=e.message,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.exception(f"Unexpected error: {str(e)}")
|
|
111
|
+
|
|
112
|
+
return Response(
|
|
113
|
+
status="DB_ERROR_UNEXPECTED",
|
|
114
|
+
message="Could not process request.",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return wrapper
|
|
118
|
+
|
|
119
|
+
return decorator
|
|
@@ -8,7 +8,7 @@ class OSDException(Exception):
|
|
|
8
8
|
self,
|
|
9
9
|
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
10
10
|
error: str = None,
|
|
11
|
-
status_code: Any = Code.APP_ERROR_CODE
|
|
11
|
+
status_code: Any = Code.APP_ERROR_CODE,
|
|
12
12
|
):
|
|
13
13
|
super().__init__(error)
|
|
14
14
|
self.message = message
|
|
@@ -19,7 +19,9 @@ class OSDException(Exception):
|
|
|
19
19
|
def __str__(self):
|
|
20
20
|
error_detail = self.error if self.error else "None"
|
|
21
21
|
return f"[{self.status_code}] {self.message} | {error_detail}"
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
23
25
|
class UnauthorizedException(OSDException):
|
|
24
26
|
def __init__(
|
|
25
27
|
self,
|
|
@@ -155,11 +157,3 @@ class InvalidFormatException(OSDException):
|
|
|
155
157
|
):
|
|
156
158
|
super().__init__(message=message, error=error, status_code=status_code)
|
|
157
159
|
|
|
158
|
-
class HttpClientException(OSDException):
|
|
159
|
-
def __init__(
|
|
160
|
-
self,
|
|
161
|
-
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
162
|
-
error: str = None,
|
|
163
|
-
status_code: str = Code.HTTP_ERROR_CODE
|
|
164
|
-
):
|
|
165
|
-
super().__init__(message=message, error=error, status_code=status_code)
|
|
@@ -2,19 +2,24 @@ from starlette.datastructures import Headers
|
|
|
2
2
|
from Osdental.Encryptor.Aes import AES
|
|
3
3
|
from Osdental.Encryptor.Jwt import JWT
|
|
4
4
|
from Osdental.Models.Token import AuthToken
|
|
5
|
-
from Osdental.
|
|
5
|
+
from Osdental.Helpers.extract_auth_token import ExtractAuthToken
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class TokenValidationService:
|
|
8
8
|
|
|
9
|
-
def __init__(self,
|
|
10
|
-
self.jwt_user_key = jwt_user_key
|
|
9
|
+
def __init__(self, auth_validator):
|
|
11
10
|
self.auth_validator = auth_validator
|
|
12
11
|
|
|
13
|
-
async def authenticate(
|
|
12
|
+
async def authenticate(
|
|
13
|
+
self,
|
|
14
|
+
headers: Headers,
|
|
15
|
+
jwt_user_key: str,
|
|
16
|
+
aes_user: str
|
|
17
|
+
) -> AuthToken:
|
|
18
|
+
|
|
14
19
|
encrypted_token = ExtractAuthToken.get_auth_token(headers)
|
|
15
20
|
|
|
16
21
|
user_token = AES.decrypt(aes_user, encrypted_token)
|
|
17
|
-
payload = JWT.extract_payload(user_token,
|
|
22
|
+
payload = JWT.extract_payload(user_token, jwt_user_key)
|
|
18
23
|
|
|
19
24
|
token = AuthToken(
|
|
20
25
|
id_token=payload.get("idToken"),
|
|
@@ -33,11 +38,11 @@ class TokenService:
|
|
|
33
38
|
)
|
|
34
39
|
|
|
35
40
|
# Validate via RPC
|
|
36
|
-
|
|
41
|
+
payload = {
|
|
37
42
|
"idToken": token.id_token,
|
|
38
43
|
"idUser": token.id_user
|
|
39
44
|
}
|
|
40
|
-
is_valid = await self.auth_validator.validate_auth_token(
|
|
45
|
+
is_valid = await self.auth_validator.validate_auth_token(payload)
|
|
41
46
|
if not int(is_valid):
|
|
42
47
|
raise ValueError("You are not authorized to access this portal.")
|
|
43
48
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from Osdental.Exception.ControlledException import OSDException
|
|
4
|
+
from Osdental.Http.Constants import (
|
|
5
|
+
INTERNAL_SERVER_ERROR_MSG,
|
|
6
|
+
HTTP_ERROR_CODE,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
class HttpClientException(OSDException):
|
|
10
|
+
"""For failures in calls to external APIs."""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
message: str = INTERNAL_SERVER_ERROR_MSG,
|
|
15
|
+
error: str = None,
|
|
16
|
+
status_code: Any = HTTP_ERROR_CODE,
|
|
17
|
+
http_status_code: int | None = None,
|
|
18
|
+
):
|
|
19
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
20
|
+
self.http_status_code = http_status_code
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def __str__(self):
|
|
24
|
+
base = super().__str__()
|
|
25
|
+
return f"{base} | http={self.http_status_code} code={self.error_code}"
|
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
import httpx
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from Osdental.Http import APIClient
|
|
3
5
|
from Osdental.Decorators.Retry import rest_retry
|
|
4
|
-
from Osdental.Http.
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
from Osdental.Http.Exceptions import HttpClientException
|
|
7
|
+
from Osdental.Http.Types import HTTPMethod
|
|
8
|
+
from Osdental.Http.Constants import (
|
|
9
|
+
HTTP_TIMEOUT,
|
|
10
|
+
MAX_CONNECTIONS,
|
|
11
|
+
MAX_KEEPALIVE_CONNECTIONS,
|
|
7
12
|
)
|
|
8
|
-
from Osdental.Exception.ControlledException import HttpClientException
|
|
9
13
|
from Osdental.Shared.Logger import logger
|
|
10
14
|
|
|
11
|
-
class APIClient:
|
|
15
|
+
class HttpxClient(APIClient):
|
|
12
16
|
|
|
13
17
|
def __init__(
|
|
14
18
|
self,
|
|
15
|
-
timeout:
|
|
16
|
-
limits:
|
|
19
|
+
timeout: httpx.Timeout | None = None,
|
|
20
|
+
limits: httpx.Limits | None = None
|
|
17
21
|
):
|
|
18
22
|
self._client = httpx.AsyncClient(
|
|
19
23
|
follow_redirects=True,
|
|
20
24
|
timeout=timeout or httpx.Timeout(10.0, read=20.0),
|
|
21
25
|
limits=limits or httpx.Limits(
|
|
22
|
-
max_connections=
|
|
23
|
-
max_keepalive_connections=
|
|
26
|
+
max_connections=MAX_CONNECTIONS,
|
|
27
|
+
max_keepalive_connections=MAX_KEEPALIVE_CONNECTIONS,
|
|
24
28
|
)
|
|
25
29
|
)
|
|
26
30
|
|
|
27
|
-
async def close(self):
|
|
28
|
-
await self._client.aclose()
|
|
29
|
-
|
|
30
31
|
@rest_retry
|
|
31
|
-
async def _request(self, method:
|
|
32
|
+
async def _request(self, method: HTTPMethod, url: str, **kwargs) -> httpx.Response:
|
|
32
33
|
|
|
33
34
|
try:
|
|
34
35
|
|
|
@@ -40,35 +41,30 @@ class APIClient:
|
|
|
40
41
|
|
|
41
42
|
response.raise_for_status()
|
|
42
43
|
|
|
43
|
-
audit_success(response, method, url, kwargs)
|
|
44
|
-
|
|
45
44
|
return response
|
|
46
|
-
|
|
47
|
-
except httpx.HTTPStatusError as exc:
|
|
48
|
-
|
|
49
|
-
audit_http_error(exc.response, method, url, kwargs)
|
|
50
|
-
raise HttpClientException(
|
|
51
|
-
status_code=exc.response.status_code,
|
|
52
|
-
error=exc.response.text
|
|
53
|
-
) from exc
|
|
54
45
|
|
|
55
|
-
except httpx.
|
|
56
|
-
|
|
57
|
-
audit_exception_error(exc, method, url, kwargs)
|
|
46
|
+
except httpx.TimeoutException as e:
|
|
58
47
|
raise HttpClientException(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
62
|
-
|
|
48
|
+
error=str(e),
|
|
49
|
+
http_status_code=HTTP_TIMEOUT
|
|
50
|
+
)
|
|
51
|
+
except httpx.HTTPStatusError as e:
|
|
52
|
+
raise HttpClientException(
|
|
53
|
+
error=str(e),
|
|
54
|
+
http_status_code=e.response.status_code
|
|
55
|
+
)
|
|
56
|
+
except httpx.RequestError as e:
|
|
57
|
+
raise HttpClientException(
|
|
58
|
+
error=str(e),
|
|
59
|
+
)
|
|
63
60
|
except Exception as exc:
|
|
64
|
-
|
|
65
|
-
audit_unknown_error(exc, method, url, kwargs)
|
|
66
|
-
|
|
61
|
+
logger.error(f"Unexpected error in APIClient: {exc}")
|
|
67
62
|
raise
|
|
68
63
|
|
|
64
|
+
|
|
69
65
|
async def request(
|
|
70
66
|
self,
|
|
71
|
-
method:
|
|
67
|
+
method: HTTPMethod,
|
|
72
68
|
url: str,
|
|
73
69
|
**kwargs
|
|
74
70
|
) -> Any:
|
|
@@ -96,9 +92,9 @@ class APIClient:
|
|
|
96
92
|
self,
|
|
97
93
|
url: str,
|
|
98
94
|
query: str,
|
|
99
|
-
variables:
|
|
100
|
-
headers:
|
|
101
|
-
) ->
|
|
95
|
+
variables: dict[str, Any],
|
|
96
|
+
headers: dict[str, str] | None = None
|
|
97
|
+
) -> dict[str, Any]:
|
|
102
98
|
|
|
103
99
|
payload = {
|
|
104
100
|
"query": query,
|
|
@@ -120,10 +116,13 @@ class APIClient:
|
|
|
120
116
|
f"GraphQL error: {data['errors']}"
|
|
121
117
|
)
|
|
122
118
|
|
|
123
|
-
audit_graphql_error(data["errors"], url, payload)
|
|
124
119
|
raise HttpClientException(
|
|
125
120
|
message="GraphQL execution failed",
|
|
126
121
|
error=str(data["errors"])
|
|
127
122
|
)
|
|
128
123
|
|
|
129
|
-
return data["data"]
|
|
124
|
+
return data["data"]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
async def close(self):
|
|
128
|
+
await self._client.aclose()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INTERNAL_SERVER_ERROR_MSG = "Your request could not be processed. Please try again later."
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any
|
|
3
|
+
from Osdental.Http.Types import HTTPMethod
|
|
4
|
+
|
|
5
|
+
class APIClient(ABC):
|
|
6
|
+
|
|
7
|
+
@abstractmethod
|
|
8
|
+
async def request(
|
|
9
|
+
self,
|
|
10
|
+
method: HTTPMethod,
|
|
11
|
+
url: str,
|
|
12
|
+
**kwargs
|
|
13
|
+
) -> Any:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
async def execute(
|
|
19
|
+
self,
|
|
20
|
+
url: str,
|
|
21
|
+
query: str,
|
|
22
|
+
variables: dict[str, Any],
|
|
23
|
+
headers: dict[str, str] | None = None
|
|
24
|
+
) -> dict[str, Any]:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
async def close(self) -> None:
|
|
30
|
+
pass
|
|
@@ -28,6 +28,12 @@ class SenderContext:
|
|
|
28
28
|
|
|
29
29
|
class AzureServiceBus(IMessageBus):
|
|
30
30
|
|
|
31
|
+
"""Azure Service Bus implementation of IMessageBus.
|
|
32
|
+
|
|
33
|
+
This implementation maintains one sender per queue and
|
|
34
|
+
automatically recreates senders when transient failures occur.
|
|
35
|
+
"""
|
|
36
|
+
|
|
31
37
|
def __init__(
|
|
32
38
|
self,
|
|
33
39
|
namespace: str,
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
1
|
from typing import Any
|
|
2
|
+
from Osdental.Messaging import IMessageBus
|
|
3
3
|
|
|
4
|
-
class IMessageBus
|
|
4
|
+
class KafkaQueue(IMessageBus):
|
|
5
5
|
|
|
6
|
-
@abstractmethod
|
|
7
6
|
async def publish(
|
|
8
7
|
self,
|
|
9
8
|
queue: str,
|
|
10
9
|
message: dict[str, Any] | str,
|
|
11
10
|
) -> None:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
print(f'[Kafka] Queuing message: {message}')
|
|
12
|
+
|
|
13
|
+
|
|
15
14
|
async def close(self) -> None:
|
|
16
|
-
|
|
15
|
+
print("Closing...")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from Osdental.Messaging import IMessageBus
|
|
3
|
+
|
|
4
|
+
class RabbitMqQueue(IMessageBus):
|
|
5
|
+
|
|
6
|
+
async def publish(
|
|
7
|
+
self,
|
|
8
|
+
queue: str,
|
|
9
|
+
message: dict[str, Any] | str,
|
|
10
|
+
) -> None:
|
|
11
|
+
print(f'[RabbitMQ] Queuing message: {message}')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def close(self) -> None:
|
|
15
|
+
print("Closing...")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
class IMessageBus(ABC):
|
|
5
|
+
|
|
6
|
+
"""Provides message publishing capabilities.
|
|
7
|
+
|
|
8
|
+
Implementations may publish messages using providers such as
|
|
9
|
+
Azure Service Bus, RabbitMQ, Kafka, or other messaging systems.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
async def publish(
|
|
14
|
+
self,
|
|
15
|
+
queue: str,
|
|
16
|
+
message: dict[str, Any] | str,
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Publishes a message to the specified queue.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
queue:
|
|
22
|
+
Name of the destination queue.
|
|
23
|
+
|
|
24
|
+
message:
|
|
25
|
+
Message payload to publish. Implementations may support
|
|
26
|
+
plain strings, dictionaries, or other serializable types.
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
async def close(self) -> None:
|
|
32
|
+
"""Releases any resources held by the messaging provider.
|
|
33
|
+
|
|
34
|
+
Implementations should close network connections, authentication
|
|
35
|
+
credentials, and any other allocated resources.
|
|
36
|
+
"""
|
|
37
|
+
pass
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
from dataclasses import dataclass, field, asdict
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
3
|
from Osdental.Shared.Enums.Code import Code
|
|
4
4
|
from Osdental.Shared.Enums.Message import Message
|
|
5
5
|
|
|
6
6
|
@dataclass
|
|
7
|
-
class
|
|
7
|
+
class Response:
|
|
8
8
|
status: Any = field(default=Code.PROCESS_SUCCESS_CODE)
|
|
9
9
|
message: str = field(default=Message.PROCESS_SUCCESS_MSG)
|
|
10
|
-
data:
|
|
11
|
-
error: Optional[str] = None
|
|
10
|
+
data: Any | None = None
|
|
12
11
|
|
|
13
|
-
def send(self):
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if api_response["error"] is None:
|
|
17
|
-
api_response.pop("error")
|
|
12
|
+
def send(self) -> dict[str, Any]:
|
|
13
|
+
return asdict(self)
|
|
@@ -7,8 +7,5 @@ from Osdental.Models.Token import AuthToken
|
|
|
7
7
|
class BaseGraphQLContext:
|
|
8
8
|
request: Request
|
|
9
9
|
container: Any
|
|
10
|
-
# valores agregados durante ejecución (AuditExtention)
|
|
11
10
|
token: Optional[AuthToken] = None
|
|
12
|
-
|
|
13
|
-
aes_auth: Optional[str] = None
|
|
14
|
-
audit_plain_response: Optional[Any] = None
|
|
11
|
+
aes_auth: Optional[str] = None
|