Encryptors 2.28__tar.gz → 2.30__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.28 → encryptors-2.30}/PKG-INFO +1 -1
- {encryptors-2.28 → encryptors-2.30}/setup.py +1 -1
- {encryptors-2.28 → encryptors-2.30}/src/Encryptors.egg-info/PKG-INFO +1 -1
- {encryptors-2.28 → encryptors-2.30}/src/Encryptors.egg-info/SOURCES.txt +10 -0
- encryptors-2.30/src/Osdental/Decorators/AuditLog.py +87 -0
- encryptors-2.30/src/Osdental/Decorators/SecureResolver.py +70 -0
- encryptors-2.30/src/Osdental/Graphql/Extensions/AuditExtension.py +96 -0
- encryptors-2.30/src/Osdental/Graphql/_Helpers/_AuditHelper.py +64 -0
- encryptors-2.30/src/Osdental/Graphql/_Helpers/_TenantPolicy.py +33 -0
- encryptors-2.30/src/Osdental/Graphql/_Helpers/_TokenService.py +34 -0
- encryptors-2.30/src/Osdental/Helpers/AuditDispatcher.py +104 -0
- encryptors-2.30/src/Osdental/Models/AuditConfig.py +7 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Models/Response.py +0 -8
- encryptors-2.30/src/Osdental/Models/Token.py +25 -0
- encryptors-2.30/src/Osdental/Shared/Utils/__init__.py +0 -0
- encryptors-2.30/src/Osdental/Shared/__init__.py +0 -0
- encryptors-2.30/src/Osdental/__init__.py +0 -0
- encryptors-2.28/src/Osdental/Decorators/AuditLog.py +0 -158
- encryptors-2.28/src/Osdental/Models/Token.py +0 -43
- {encryptors-2.28 → encryptors-2.30}/README.md +0 -0
- {encryptors-2.28 → encryptors-2.30}/setup.cfg +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Encryptors.egg-info/dependency_links.txt +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Encryptors.egg-info/entry_points.txt +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Encryptors.egg-info/requires.txt +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Encryptors.egg-info/top_level.txt +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/BlobStorage/Storage.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/BlobStorage/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Cli/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Database/BaseRepository.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Database/Connection.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Database/UnitOfWork.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Database/UowFactory.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Database/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Decorators/DecryptedData.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Decorators/Grpc.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Decorators/Retry.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Decorators/SqlDataNormalizer.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Decorators/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Encryptor/Aes.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Encryptor/Argon2.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Encryptor/Bcrypt.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Encryptor/Jwt.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Encryptor/Rsa.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Encryptor/Sha512.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Encryptor/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Exception/ControlledException.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Exception/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/ExternalHttp/Client.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/ExternalHttp/__init__.py +0 -0
- {encryptors-2.28/src/Osdental/Grpc/Base → encryptors-2.30/src/Osdental/Graphql/Extensions}/__init__.py +0 -0
- {encryptors-2.28/src/Osdental/Grpc/Client → encryptors-2.30/src/Osdental/Graphql/_Helpers}/__init__.py +0 -0
- {encryptors-2.28/src/Osdental/Grpc/Generated → encryptors-2.30/src/Osdental/Graphql}/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Grpc/Base/GrpcClientBase.py +0 -0
- {encryptors-2.28/src/Osdental/Grpc → encryptors-2.30/src/Osdental/Grpc/Base}/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Grpc/Client/PortalClient.py +0 -0
- {encryptors-2.28/src/Osdental/Helpers → encryptors-2.30/src/Osdental/Grpc/Client}/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Grpc/Generated/Common_pb2.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Grpc/Generated/Common_pb2_grpc.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Grpc/Generated/Portal_pb2.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Grpc/Generated/Portal_pb2_grpc.py +0 -0
- {encryptors-2.28/src/Osdental/InternalHttp → encryptors-2.30/src/Osdental/Grpc/Generated}/__init__.py +0 -0
- {encryptors-2.28/src/Osdental/Models → encryptors-2.30/src/Osdental/Grpc}/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Helpers/KeyVaultService.py +0 -0
- {encryptors-2.28/src/Osdental/RedisCache → encryptors-2.30/src/Osdental/Helpers}/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/InternalHttp/Request.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/InternalHttp/Response.py +0 -0
- {encryptors-2.28/src/Osdental/ServicesBus → encryptors-2.30/src/Osdental/InternalHttp}/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Messaging/AzureServiceBus.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Messaging/Kafka.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Messaging/RabbitMQ.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Messaging/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Models/CDataIntegration.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Models/Catalog.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Models/Legacy.py +0 -0
- {encryptors-2.28/src/Osdental/Shared/Enums → encryptors-2.30/src/Osdental/Models}/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/RedisCache/Redis.py +0 -0
- {encryptors-2.28/src/Osdental/Shared/Utils → encryptors-2.30/src/Osdental/RedisCache}/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/ServicesBus/ServicesBus.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/ServicesBus/TaskQueue.py +0 -0
- {encryptors-2.28/src/Osdental/Shared → encryptors-2.30/src/Osdental/ServicesBus}/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Config/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Enums/Code.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Enums/Constant.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Enums/FileType.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Enums/GrahpqlOperation.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Enums/Message.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Enums/Profile.py +0 -0
- {encryptors-2.28/src/Osdental → encryptors-2.30/src/Osdental/Shared/Enums}/__init__.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Logger.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Utils/CaseConverter.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Utils/CodeGenerator.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Utils/DataNormalizer.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Utils/DataUtils.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Utils/DateUtils.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Utils/FileMetaData.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Utils/HashValidator.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Utils/Mapper.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Utils/PasswordGenerator.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Utils/QueryGenerator.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Shared/Utils/TextProcessor.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Storage/S3Storage.py +0 -0
- {encryptors-2.28 → encryptors-2.30}/src/Osdental/Storage/__init__.py +0 -0
|
@@ -19,6 +19,7 @@ src/Osdental/Decorators/AuditLog.py
|
|
|
19
19
|
src/Osdental/Decorators/DecryptedData.py
|
|
20
20
|
src/Osdental/Decorators/Grpc.py
|
|
21
21
|
src/Osdental/Decorators/Retry.py
|
|
22
|
+
src/Osdental/Decorators/SecureResolver.py
|
|
22
23
|
src/Osdental/Decorators/SqlDataNormalizer.py
|
|
23
24
|
src/Osdental/Decorators/__init__.py
|
|
24
25
|
src/Osdental/Encryptor/Aes.py
|
|
@@ -32,6 +33,13 @@ src/Osdental/Exception/ControlledException.py
|
|
|
32
33
|
src/Osdental/Exception/__init__.py
|
|
33
34
|
src/Osdental/ExternalHttp/Client.py
|
|
34
35
|
src/Osdental/ExternalHttp/__init__.py
|
|
36
|
+
src/Osdental/Graphql/__init__.py
|
|
37
|
+
src/Osdental/Graphql/Extensions/AuditExtension.py
|
|
38
|
+
src/Osdental/Graphql/Extensions/__init__.py
|
|
39
|
+
src/Osdental/Graphql/_Helpers/_AuditHelper.py
|
|
40
|
+
src/Osdental/Graphql/_Helpers/_TenantPolicy.py
|
|
41
|
+
src/Osdental/Graphql/_Helpers/_TokenService.py
|
|
42
|
+
src/Osdental/Graphql/_Helpers/__init__.py
|
|
35
43
|
src/Osdental/Grpc/__init__.py
|
|
36
44
|
src/Osdental/Grpc/Base/GrpcClientBase.py
|
|
37
45
|
src/Osdental/Grpc/Base/__init__.py
|
|
@@ -42,6 +50,7 @@ src/Osdental/Grpc/Generated/Common_pb2_grpc.py
|
|
|
42
50
|
src/Osdental/Grpc/Generated/Portal_pb2.py
|
|
43
51
|
src/Osdental/Grpc/Generated/Portal_pb2_grpc.py
|
|
44
52
|
src/Osdental/Grpc/Generated/__init__.py
|
|
53
|
+
src/Osdental/Helpers/AuditDispatcher.py
|
|
45
54
|
src/Osdental/Helpers/KeyVaultService.py
|
|
46
55
|
src/Osdental/Helpers/__init__.py
|
|
47
56
|
src/Osdental/InternalHttp/Request.py
|
|
@@ -51,6 +60,7 @@ src/Osdental/Messaging/AzureServiceBus.py
|
|
|
51
60
|
src/Osdental/Messaging/Kafka.py
|
|
52
61
|
src/Osdental/Messaging/RabbitMQ.py
|
|
53
62
|
src/Osdental/Messaging/__init__.py
|
|
63
|
+
src/Osdental/Models/AuditConfig.py
|
|
54
64
|
src/Osdental/Models/CDataIntegration.py
|
|
55
65
|
src/Osdental/Models/Catalog.py
|
|
56
66
|
src/Osdental/Models/Legacy.py
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import asyncio
|
|
3
|
+
from typing import List, Dict, Any
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from Osdental.InternalHttp.Request import CustomRequest
|
|
6
|
+
from Osdental.InternalHttp.Response import CustomResponse
|
|
7
|
+
from Osdental.Encryptor.Rsa import RSAEncryptor
|
|
8
|
+
from Osdental.Exception.ControlledException import OSDException, RSAEncryptException, AESEncryptException
|
|
9
|
+
from Osdental.Encryptor.Aes import AES
|
|
10
|
+
from Osdental.Shared.Utils.TextProcessor import TextProcessor
|
|
11
|
+
from Osdental.Shared.Logger import logger as custom_logger
|
|
12
|
+
from Osdental.Models.Legacy import Legacy
|
|
13
|
+
from Osdental.Grpc.Client.PortalClient import PortalClient
|
|
14
|
+
from Osdental.Shared.Enums.Code import Code
|
|
15
|
+
|
|
16
|
+
portal_client = PortalClient()
|
|
17
|
+
aes = AES()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def try_decrypt_or_return_raw(data: str, private_key_rsa: str, aes_key: str) -> str:
|
|
22
|
+
try:
|
|
23
|
+
return RSAEncryptor.decrypt(data, private_key_rsa, silent=True)
|
|
24
|
+
except RSAEncryptException:
|
|
25
|
+
try:
|
|
26
|
+
return aes.decrypt(aes_key, data, silent=True)
|
|
27
|
+
except AESEncryptException:
|
|
28
|
+
return data
|
|
29
|
+
|
|
30
|
+
def enqueue_response(data: Any, headers: Dict[str,str], msg_info: str = None):
|
|
31
|
+
content = json.dumps(data) if isinstance(data, dict) else msg_info
|
|
32
|
+
custom_response = CustomResponse(content=content, headers=headers)
|
|
33
|
+
_ = asyncio.create_task(custom_response.send_to_service_bus())
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def handle_audit_and_exception():
|
|
37
|
+
def decorator(func):
|
|
38
|
+
@wraps(func)
|
|
39
|
+
async def wrapper(*args, **kwargs):
|
|
40
|
+
headers = {}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Extract request and operation name before opening the span
|
|
44
|
+
try:
|
|
45
|
+
_, info = args[:2]
|
|
46
|
+
request = info.context.get('request')
|
|
47
|
+
headers = info.context.get('headers') or {}
|
|
48
|
+
|
|
49
|
+
if request:
|
|
50
|
+
# Send audit of the request to Service Bus
|
|
51
|
+
custom_request = CustomRequest(request)
|
|
52
|
+
_ = asyncio.create_task(custom_request.send_to_service_bus())
|
|
53
|
+
|
|
54
|
+
except Exception as ex:
|
|
55
|
+
logger.warning(f"Failed to extract operationName: {ex}")
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
# Get legacy
|
|
59
|
+
res = await portal_client.get_legacy()
|
|
60
|
+
data = json.loads(res.data)
|
|
61
|
+
legacy = Legacy.from_db(data) if res.status == Code.PROCESS_SUCCESS_CODE else None
|
|
62
|
+
# Run the resolver
|
|
63
|
+
response = await func(*args, **kwargs)
|
|
64
|
+
# Prepare data and message
|
|
65
|
+
msg_info = TextProcessor.concatenate(response.get('status'), '-', response.get('message'))
|
|
66
|
+
raw_data = response.get('data')
|
|
67
|
+
data_to_enqueue = None
|
|
68
|
+
if raw_data:
|
|
69
|
+
data_to_enqueue = try_decrypt_or_return_raw(raw_data, legacy.private_key2, legacy.aes_key_auth)
|
|
70
|
+
# Enqueue response
|
|
71
|
+
enqueue_response(data_to_enqueue, headers, msg_info)
|
|
72
|
+
return response
|
|
73
|
+
except OSDException as ex:
|
|
74
|
+
# Controlled log
|
|
75
|
+
custom_logger.error(f'Controlled error: {str(ex)}')
|
|
76
|
+
ex.headers = headers
|
|
77
|
+
_ = asyncio.create_task(ex.send_to_service_bus())
|
|
78
|
+
return ex.get_response()
|
|
79
|
+
except Exception as e:
|
|
80
|
+
# Unhandled exception log and span
|
|
81
|
+
custom_logger.error(f'Unexpected error: {str(e)}')
|
|
82
|
+
ex = OSDException(error=str(e), headers=headers)
|
|
83
|
+
_ = asyncio.create_task(ex.send_to_service_bus())
|
|
84
|
+
return ex.get_response()
|
|
85
|
+
|
|
86
|
+
return wrapper
|
|
87
|
+
return decorator
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import asyncio
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Callable, Dict, Any
|
|
5
|
+
from graphql import GraphQLResolveInfo
|
|
6
|
+
from Osdental.Models.Token import AuthToken
|
|
7
|
+
from Osdental.Models.Response import Response
|
|
8
|
+
from Osdental.Shared.Enums.Profile import Profile
|
|
9
|
+
from Osdental.Exception.ControlledException import OSDException
|
|
10
|
+
from Osdental.Encryptor.Aes import AES
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
aes = AES()
|
|
14
|
+
def secure_resolver(action = None):
|
|
15
|
+
def decorator(func: Callable):
|
|
16
|
+
|
|
17
|
+
signature = inspect.signature(func)
|
|
18
|
+
accepts_data = "data" in signature.parameters
|
|
19
|
+
|
|
20
|
+
@wraps(func)
|
|
21
|
+
async def wrapper(obj: Any, info: GraphQLResolveInfo, **kwargs: Dict):
|
|
22
|
+
try:
|
|
23
|
+
token: AuthToken = info.context.token
|
|
24
|
+
payload = getattr(info.context, "decrypted_payload", None)
|
|
25
|
+
aes_key = getattr(info.context, "aes_key", None)
|
|
26
|
+
|
|
27
|
+
if action:
|
|
28
|
+
if Profile(token.abbreviation) not in action.allowed_roles:
|
|
29
|
+
raise OSDException("DB_ERROR_AUTH", "You are not authorized to perform this action.")
|
|
30
|
+
|
|
31
|
+
if accepts_data and payload:
|
|
32
|
+
kwargs["data"] = payload
|
|
33
|
+
|
|
34
|
+
result: Response = await func(obj, info, **kwargs)
|
|
35
|
+
|
|
36
|
+
if result.data is not None:
|
|
37
|
+
result.data = aes.encrypt(aes_key, result.data)
|
|
38
|
+
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
# await info.context.audit_dispatcher.dispatch(
|
|
42
|
+
# security=security,
|
|
43
|
+
# result=result
|
|
44
|
+
# )
|
|
45
|
+
|
|
46
|
+
if result.data is not None:
|
|
47
|
+
result.data = aes.encrypt(security.encryptor.aes_auth, result.data)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
except (Exception, OSDException) as e:
|
|
53
|
+
result = {
|
|
54
|
+
"status": getattr(e, "status_code", "DB_ERROR_UNEXPECTED"),
|
|
55
|
+
"message": getattr(e, "message", "Could not process request."),
|
|
56
|
+
"error": getattr(e, "error", type(e).__name__),
|
|
57
|
+
"data": None
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# _ = asyncio.create_task(
|
|
61
|
+
# info.context.audit_dispatcher.dispatch(
|
|
62
|
+
# security=security,
|
|
63
|
+
# result=result
|
|
64
|
+
# )
|
|
65
|
+
# )
|
|
66
|
+
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
return wrapper
|
|
70
|
+
return decorator
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import json
|
|
3
|
+
from graphql.pyutils import is_awaitable
|
|
4
|
+
from ariadne.types import Extension
|
|
5
|
+
from Osdental.Encryptor.Aes import AES
|
|
6
|
+
from Osdental.Graphql._Helpers._TenantPolicy import TenantPolicy
|
|
7
|
+
from Osdental.Models.Token import AuthToken
|
|
8
|
+
|
|
9
|
+
class AuditExtension(Extension):
|
|
10
|
+
|
|
11
|
+
def request_started(self, context):
|
|
12
|
+
self.start_time = time.perf_counter()
|
|
13
|
+
self.errors = None
|
|
14
|
+
self.request_payload = None
|
|
15
|
+
self.result = None
|
|
16
|
+
self.aes = AES()
|
|
17
|
+
|
|
18
|
+
async def resolve(self, next_, root, info, **kwargs):
|
|
19
|
+
|
|
20
|
+
if not self.request_payload:
|
|
21
|
+
context = info.context
|
|
22
|
+
|
|
23
|
+
request = info.context.request
|
|
24
|
+
self.request = request
|
|
25
|
+
|
|
26
|
+
body = request.state.graphql_body
|
|
27
|
+
|
|
28
|
+
container = context.container
|
|
29
|
+
|
|
30
|
+
token_service = container.token_service
|
|
31
|
+
|
|
32
|
+
aes_key = request.app.state.aes_auth
|
|
33
|
+
if not aes_key:
|
|
34
|
+
raise ValueError("Could not find authorization key Aes.")
|
|
35
|
+
|
|
36
|
+
original_token = await token_service.authenticate(request)
|
|
37
|
+
|
|
38
|
+
variables = body.get("variables") or {}
|
|
39
|
+
encrypted_payload = variables.get("data")
|
|
40
|
+
|
|
41
|
+
decrypted_payload = None
|
|
42
|
+
|
|
43
|
+
if encrypted_payload:
|
|
44
|
+
try:
|
|
45
|
+
decrypted_payload = self.aes.decrypt(aes_key, encrypted_payload)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
decrypted_payload = json.loads(decrypted_payload)
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
except Exception:
|
|
53
|
+
decrypted_payload = None
|
|
54
|
+
|
|
55
|
+
token: AuthToken = TenantPolicy.resolve(
|
|
56
|
+
token=original_token,
|
|
57
|
+
headers=request.headers,
|
|
58
|
+
decrypted_payload=decrypted_payload,
|
|
59
|
+
operation_type=info.operation.operation.value
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
self.request_payload = {
|
|
63
|
+
"operation_type": info.operation.operation.value,
|
|
64
|
+
"operation_name": body.get("operationName", "UnknownOperation"),
|
|
65
|
+
"query": body.get("query"),
|
|
66
|
+
"variables": decrypted_payload,
|
|
67
|
+
"user": token.user_full_name
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
context.decrypted_payload = decrypted_payload
|
|
71
|
+
context.token = token
|
|
72
|
+
context.aes_key = aes_key
|
|
73
|
+
|
|
74
|
+
result = next_(root, info, **kwargs)
|
|
75
|
+
|
|
76
|
+
if is_awaitable(result):
|
|
77
|
+
result = await result
|
|
78
|
+
|
|
79
|
+
if root is None:
|
|
80
|
+
self.result = result
|
|
81
|
+
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
def has_errors(self, errors, context):
|
|
85
|
+
self.errors = errors
|
|
86
|
+
|
|
87
|
+
def request_finished(self, context):
|
|
88
|
+
|
|
89
|
+
dispatcher = context.request.app.state.audit_dispatcher
|
|
90
|
+
|
|
91
|
+
dispatcher.dispatch(
|
|
92
|
+
request=self.request,
|
|
93
|
+
request_payload=self.request_payload,
|
|
94
|
+
result=self.result,
|
|
95
|
+
errors=self.errors
|
|
96
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Dict, Literal, Any, Optional
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
from Osdental.Models.AuditConfig import AuditConfig
|
|
5
|
+
|
|
6
|
+
class AuditHelper:
|
|
7
|
+
|
|
8
|
+
@staticmethod
|
|
9
|
+
async def build_request_payload(
|
|
10
|
+
request: Request,
|
|
11
|
+
audit_config: AuditConfig,
|
|
12
|
+
payload: Any = {},
|
|
13
|
+
operation_name: str = "Unknown",
|
|
14
|
+
full_name: str = "Joe Doe"
|
|
15
|
+
) -> Dict:
|
|
16
|
+
|
|
17
|
+
default_value = "*"
|
|
18
|
+
|
|
19
|
+
user_ip = request.headers.get("X-Forwarded-For")
|
|
20
|
+
if user_ip:
|
|
21
|
+
user_ip = user_ip.split(',')[0]
|
|
22
|
+
else:
|
|
23
|
+
user_ip = getattr(request.client, "host", "*")
|
|
24
|
+
|
|
25
|
+
SAFE_HEADERS = {
|
|
26
|
+
"user-agent",
|
|
27
|
+
"host",
|
|
28
|
+
"origin",
|
|
29
|
+
"referer",
|
|
30
|
+
"content-type"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
headers = {k: v for k, v in request.headers.items() if k.lower() in SAFE_HEADERS}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
"idMessageLog": request.headers.get("Idmessagelog"),
|
|
37
|
+
"environment": audit_config.environment,
|
|
38
|
+
"header": json.dumps(headers),
|
|
39
|
+
"microServiceUrl": str(request.url),
|
|
40
|
+
"microServiceName": audit_config.microservice_name,
|
|
41
|
+
"microServiceVersion": audit_config.microservice_version,
|
|
42
|
+
"serviceName": operation_name,
|
|
43
|
+
"machineNameUser": request.headers.get("Machinenameuser", default_value),
|
|
44
|
+
"ipUser": user_ip or default_value,
|
|
45
|
+
"userName": full_name,
|
|
46
|
+
"localitation": default_value,
|
|
47
|
+
"httpMethod": request.method,
|
|
48
|
+
"messageIn": json.dumps(payload) if payload else default_value,
|
|
49
|
+
"auditLog": "MESSAGE_LOG_INTERNAL"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def build_final_payload(
|
|
54
|
+
_type: Literal["RESPONSE", "ERROR"],
|
|
55
|
+
status_code: Any,
|
|
56
|
+
result: Optional[Any] = "*",
|
|
57
|
+
error: Optional[Any] = "*"
|
|
58
|
+
):
|
|
59
|
+
return {
|
|
60
|
+
"type": _type,
|
|
61
|
+
"httpResponseCode": status_code,
|
|
62
|
+
"messageOut": json.dumps(result) if isinstance(result, dict) else result,
|
|
63
|
+
"errorProducer": error
|
|
64
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from Osdental.Encryptor.Aes import AES
|
|
2
|
+
from uuid import UUID
|
|
3
|
+
from graphql import OperationType
|
|
4
|
+
|
|
5
|
+
class TenantPolicy:
|
|
6
|
+
|
|
7
|
+
@staticmethod
|
|
8
|
+
def resolve(token, headers, decrypted_payload, operation_type: str):
|
|
9
|
+
aes = AES()
|
|
10
|
+
# Solo aplicamos cambios si es QUERY
|
|
11
|
+
if operation_type != OperationType.QUERY.value:
|
|
12
|
+
return token # devolver token tal cual
|
|
13
|
+
|
|
14
|
+
# SUPER ADMIN / OSDEL ADMIN -> UUID 0
|
|
15
|
+
if token.abbreviation.startswith(("SPAU", "OSDA")):
|
|
16
|
+
token.id_external_enterprise = str(UUID(int=0))
|
|
17
|
+
return token
|
|
18
|
+
|
|
19
|
+
# Marketing -> tomar dynamicClientId del header
|
|
20
|
+
if token.abbreviation.startswith("OSDMK"):
|
|
21
|
+
dynamic_client_id = headers.get("dynamicClientId")
|
|
22
|
+
if dynamic_client_id:
|
|
23
|
+
decrypted_mk_id = aes.decrypt(token.aes_key_auth, dynamic_client_id)
|
|
24
|
+
token.id_external_enterprise = decrypted_mk_id
|
|
25
|
+
token.mk_id_external_enterprise = decrypted_mk_id
|
|
26
|
+
return token
|
|
27
|
+
|
|
28
|
+
# If it comes by request, it is taken as priority
|
|
29
|
+
external_enterprise_req = decrypted_payload.get('idExternalEnterprise')
|
|
30
|
+
if external_enterprise_req and token:
|
|
31
|
+
token.id_external_enterprise = external_enterprise_req
|
|
32
|
+
|
|
33
|
+
return token
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from Osdental.Encryptor.Aes import AES
|
|
2
|
+
from Osdental.Encryptor.Jwt import JWT
|
|
3
|
+
from Osdental.Models.Token import AuthToken
|
|
4
|
+
|
|
5
|
+
class TokenService:
|
|
6
|
+
|
|
7
|
+
def __init__(self, jwt_user_key: str, auth_validator):
|
|
8
|
+
self.jwt_user_key = jwt_user_key
|
|
9
|
+
self.auth_validator = auth_validator
|
|
10
|
+
self.aes = AES()
|
|
11
|
+
|
|
12
|
+
async def authenticate(self, request):
|
|
13
|
+
authorization = request.headers.get("authorization")
|
|
14
|
+
if not authorization or not authorization.startswith("Bearer "):
|
|
15
|
+
raise ValueError("Missing Bearer token")
|
|
16
|
+
|
|
17
|
+
encrypted_token = authorization.split(" ")[1]
|
|
18
|
+
|
|
19
|
+
aes_user = request.app.state.aes_user
|
|
20
|
+
|
|
21
|
+
user_token = self.aes.decrypt(aes_user, encrypted_token)
|
|
22
|
+
payload = JWT.extract_payload(user_token, self.jwt_user_key)
|
|
23
|
+
token = AuthToken(**payload)
|
|
24
|
+
|
|
25
|
+
# Validate via RPC
|
|
26
|
+
request = {
|
|
27
|
+
'idToken': token.id_token,
|
|
28
|
+
'idUser': token.id_user
|
|
29
|
+
}
|
|
30
|
+
is_valid = await self.auth_validator.validate_auth_token(request)
|
|
31
|
+
if not is_valid:
|
|
32
|
+
raise ValueError("You are not authorized to access this portal.")
|
|
33
|
+
|
|
34
|
+
return token
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from Osdental.Models.AuditConfig import AuditConfig
|
|
4
|
+
from Osdental.Messaging import IMessageQueue
|
|
5
|
+
from Osdental.Graphql._Helpers._AuditHelper import AuditHelper
|
|
6
|
+
|
|
7
|
+
class AuditDispatcher:
|
|
8
|
+
|
|
9
|
+
def __init__(self, messaging: IMessageQueue, audit_config: AuditConfig, max_queue_size: int = 5000):
|
|
10
|
+
self._messaging = messaging
|
|
11
|
+
self._audit_config = audit_config
|
|
12
|
+
self._queue = asyncio.Queue(maxsize=max_queue_size)
|
|
13
|
+
self._worker_task = None
|
|
14
|
+
|
|
15
|
+
# Se llama en startup
|
|
16
|
+
def start(self):
|
|
17
|
+
self._worker_task = asyncio.create_task(self._worker())
|
|
18
|
+
|
|
19
|
+
# Se llama en shutdown
|
|
20
|
+
async def stop(self):
|
|
21
|
+
# Esperar a que la cola se vacíe
|
|
22
|
+
await self._queue.join()
|
|
23
|
+
|
|
24
|
+
# Enviar sentinel para cerrar worker
|
|
25
|
+
await self._queue.put(None)
|
|
26
|
+
|
|
27
|
+
# Esperar a que el worker termine
|
|
28
|
+
if self._worker_task:
|
|
29
|
+
await self._worker_task
|
|
30
|
+
|
|
31
|
+
# Ultra rápido, no bloquea
|
|
32
|
+
def dispatch(self, request, request_payload, result, errors = None):
|
|
33
|
+
try:
|
|
34
|
+
payload = {
|
|
35
|
+
"request": request,
|
|
36
|
+
"request_payload": request_payload,
|
|
37
|
+
"result": result,
|
|
38
|
+
"errors": errors
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
self._queue.put_nowait(payload)
|
|
42
|
+
|
|
43
|
+
except asyncio.QueueFull:
|
|
44
|
+
logging.error("Audit queue full. Dropping message.")
|
|
45
|
+
|
|
46
|
+
# Worker real
|
|
47
|
+
async def _worker(self):
|
|
48
|
+
while True:
|
|
49
|
+
payload = await self._queue.get()
|
|
50
|
+
# Sentinel detectado → salir limpio
|
|
51
|
+
if payload is None:
|
|
52
|
+
self._queue.task_done()
|
|
53
|
+
break
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
await self._process(payload)
|
|
57
|
+
except Exception:
|
|
58
|
+
logging.exception("Audit processing failed")
|
|
59
|
+
finally:
|
|
60
|
+
self._queue.task_done()
|
|
61
|
+
|
|
62
|
+
# Logica
|
|
63
|
+
async def _process(self, payload):
|
|
64
|
+
request = payload["request"]
|
|
65
|
+
request_payload = payload["request_payload"]
|
|
66
|
+
result = payload["result"]
|
|
67
|
+
|
|
68
|
+
operation_name = request_payload.get("operation_name")
|
|
69
|
+
if operation_name == 'UnknownOperation':
|
|
70
|
+
logging.info("Skipping audit: no data in GraphQL response")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
request_audit_payload = await AuditHelper.build_request_payload(
|
|
74
|
+
request=request,
|
|
75
|
+
audit_config=self._audit_config,
|
|
76
|
+
operation_name=operation_name,
|
|
77
|
+
full_name=request_payload.get("user"),
|
|
78
|
+
payload=request_payload.get("variables")
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
status = result.get("status")
|
|
82
|
+
message = result.get("message")
|
|
83
|
+
data = result.get("data")
|
|
84
|
+
|
|
85
|
+
ERROR_PREFIXES = ("DB_ERROR", "DB_WARNING")
|
|
86
|
+
if status.upper().startswith(ERROR_PREFIXES):
|
|
87
|
+
payload = AuditHelper.build_final_payload(
|
|
88
|
+
type="ERROR",
|
|
89
|
+
status_code=status,
|
|
90
|
+
error=message
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
audit_message = request_audit_payload | payload
|
|
94
|
+
|
|
95
|
+
else:
|
|
96
|
+
payload = AuditHelper.build_final_payload(
|
|
97
|
+
type="RESPONSE",
|
|
98
|
+
status_code="200",
|
|
99
|
+
result=data
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
audit_message = request_audit_payload | payload
|
|
103
|
+
|
|
104
|
+
await self._messaging.enqueue(audit_message)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from dataclasses import dataclass, field, asdict
|
|
2
|
-
from enum import Enum
|
|
3
2
|
from Osdental.Shared.Enums.Code import Code
|
|
4
3
|
from Osdental.Shared.Enums.Message import Message
|
|
5
4
|
|
|
@@ -9,12 +8,5 @@ class Response:
|
|
|
9
8
|
message: str = field(default=Message.PROCESS_SUCCESS_MSG)
|
|
10
9
|
data: str = field(default=None)
|
|
11
10
|
|
|
12
|
-
def __post_init__(self):
|
|
13
|
-
# Asegura que status y message sean str si son Enum
|
|
14
|
-
if isinstance(self.status, Enum):
|
|
15
|
-
self.status = str(self.status)
|
|
16
|
-
if isinstance(self.message, Enum):
|
|
17
|
-
self.message = str(self.message)
|
|
18
|
-
|
|
19
11
|
def send(self):
|
|
20
12
|
return asdict(self)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
from Osdental.Models.Legacy import Legacy
|
|
4
|
+
|
|
5
|
+
class AuthToken(BaseModel):
|
|
6
|
+
|
|
7
|
+
id_token: str = Field(alias="idToken")
|
|
8
|
+
id_user: str = Field(alias="idUser")
|
|
9
|
+
id_external_enterprise: str = Field(alias="idExternalEnterprise")
|
|
10
|
+
id_profile: str = Field(alias="idProfile")
|
|
11
|
+
id_legacy: str = Field(alias="idLegacy")
|
|
12
|
+
id_authorization: str = Field(default=None, alias="idAuthorization")
|
|
13
|
+
id_item_report: str = Field(alias="idItemReport")
|
|
14
|
+
id_enterprise: str = Field(alias="idEnterprise")
|
|
15
|
+
user_full_name: str = Field(alias="userFullName")
|
|
16
|
+
abbreviation: str = Field(alias="abbreviation")
|
|
17
|
+
aes_key_auth: str = Field(alias="aesKeyAuth")
|
|
18
|
+
access_token: Optional[str] = Field(default=None, alias="accessToken")
|
|
19
|
+
base_id_external_enterprise: Optional[str] = Field(default=None, alias="baseIdExternalEnterprise")
|
|
20
|
+
mk_id_external_enterprise: Optional[str] = Field(default=None, alias="mkIdExternalEnterprise")
|
|
21
|
+
jwt_user_key: Optional[str] = Field(default=None, alias="jwtUserKey")
|
|
22
|
+
legacy: Optional[Legacy] = Field(default=None, alias="legacy")
|
|
23
|
+
|
|
24
|
+
class ConfigDict:
|
|
25
|
+
populate_by_name = True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import asyncio
|
|
3
|
-
import logging
|
|
4
|
-
import time
|
|
5
|
-
from typing import List, Dict, Any
|
|
6
|
-
from functools import wraps
|
|
7
|
-
from azure.monitor.opentelemetry import configure_azure_monitor
|
|
8
|
-
from opentelemetry import trace
|
|
9
|
-
from Osdental.InternalHttp.Request import CustomRequest
|
|
10
|
-
from Osdental.InternalHttp.Response import CustomResponse
|
|
11
|
-
from Osdental.Encryptor.Rsa import RSAEncryptor
|
|
12
|
-
from Osdental.Exception.ControlledException import OSDException, RSAEncryptException, AESEncryptException
|
|
13
|
-
from Osdental.Encryptor.Aes import AES
|
|
14
|
-
from Osdental.Shared.Utils.TextProcessor import TextProcessor
|
|
15
|
-
from Osdental.Shared.Logger import logger as custom_logger
|
|
16
|
-
from Osdental.Models.Legacy import Legacy
|
|
17
|
-
from Osdental.Grpc.Client.PortalClient import PortalClient
|
|
18
|
-
from Osdental.Shared.Enums.Code import Code
|
|
19
|
-
from Osdental.Shared.Config import Config
|
|
20
|
-
from Osdental.Shared.Enums.Constant import Constant
|
|
21
|
-
|
|
22
|
-
portal_client = PortalClient()
|
|
23
|
-
aes = AES()
|
|
24
|
-
|
|
25
|
-
# Configuration Azure Monitor
|
|
26
|
-
configure_azure_monitor(
|
|
27
|
-
connection_string=Config.APPLICATIONINSIGHTS_CONNECTION_STRING
|
|
28
|
-
)
|
|
29
|
-
# Logger
|
|
30
|
-
logging.basicConfig(level=logging.INFO)
|
|
31
|
-
logger = logging.getLogger('graphql')
|
|
32
|
-
logger.setLevel(logging.INFO)
|
|
33
|
-
# Tracer for spans
|
|
34
|
-
tracer = trace.get_tracer(__name__)
|
|
35
|
-
|
|
36
|
-
def split_into_batches(data: List[Any], batch:int = 250):
|
|
37
|
-
for i in range(0, len(data), batch):
|
|
38
|
-
yield data[i:i + batch]
|
|
39
|
-
|
|
40
|
-
def try_decrypt_or_return_raw(data: str, private_key_rsa: str, aes_key: str) -> str:
|
|
41
|
-
try:
|
|
42
|
-
return RSAEncryptor.decrypt(data, private_key_rsa, silent=True)
|
|
43
|
-
except RSAEncryptException:
|
|
44
|
-
try:
|
|
45
|
-
return aes.decrypt(aes_key, data, silent=True)
|
|
46
|
-
except AESEncryptException:
|
|
47
|
-
return data
|
|
48
|
-
|
|
49
|
-
def enqueue_response(data: Any, batch: int, headers: Dict[str,str], msg_info: str = None):
|
|
50
|
-
if data and isinstance(data, list):
|
|
51
|
-
if batch > 0 and len(data) > batch:
|
|
52
|
-
batches = split_into_batches(data, batch)
|
|
53
|
-
for idx, data_batch in enumerate(batches, start=1):
|
|
54
|
-
custom_response = CustomResponse(content=json.dumps(data_batch), headers=headers, batch=idx)
|
|
55
|
-
_ = asyncio.create_task(custom_response.send_to_service_bus())
|
|
56
|
-
else:
|
|
57
|
-
custom_response = CustomResponse(content=json.dumps(data), headers=headers)
|
|
58
|
-
_ = asyncio.create_task(custom_response.send_to_service_bus())
|
|
59
|
-
else:
|
|
60
|
-
content = json.dumps(data) if isinstance(data, dict) else msg_info
|
|
61
|
-
custom_response = CustomResponse(content=content, headers=headers)
|
|
62
|
-
_ = asyncio.create_task(custom_response.send_to_service_bus())
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def handle_audit_and_exception(batch: int = 0):
|
|
66
|
-
def decorator(func):
|
|
67
|
-
@wraps(func)
|
|
68
|
-
async def wrapper(*args, **kwargs):
|
|
69
|
-
headers = {}
|
|
70
|
-
operation_name = 'UnknownOperation'
|
|
71
|
-
|
|
72
|
-
# Extract request and operation name before opening the span
|
|
73
|
-
try:
|
|
74
|
-
_, info = args[:2]
|
|
75
|
-
request = info.context.get('request')
|
|
76
|
-
headers = info.context.get('headers') or {}
|
|
77
|
-
|
|
78
|
-
if request:
|
|
79
|
-
body = await request.body()
|
|
80
|
-
try:
|
|
81
|
-
body_data = json.loads(body.decode("utf-8"))
|
|
82
|
-
operation_name = body_data.get('operationName', 'UnknownOperation')
|
|
83
|
-
except Exception:
|
|
84
|
-
pass
|
|
85
|
-
|
|
86
|
-
# Send audit of the request to Service Bus
|
|
87
|
-
custom_request = CustomRequest(request)
|
|
88
|
-
_ = asyncio.create_task(custom_request.send_to_service_bus())
|
|
89
|
-
|
|
90
|
-
except Exception as ex:
|
|
91
|
-
logger.warning(f"Failed to extract operationName: {ex}")
|
|
92
|
-
|
|
93
|
-
# Open the span with the correct operation name
|
|
94
|
-
with tracer.start_as_current_span(f"GraphQL.{operation_name}") as span:
|
|
95
|
-
start_time = time.time()
|
|
96
|
-
try:
|
|
97
|
-
# Get legacy
|
|
98
|
-
res = await portal_client.get_legacy()
|
|
99
|
-
data = json.loads(res.data)
|
|
100
|
-
legacy = Legacy.from_db(data) if res.status == Code.PROCESS_SUCCESS_CODE else None
|
|
101
|
-
|
|
102
|
-
# Run the resolver
|
|
103
|
-
response = await func(*args, **kwargs)
|
|
104
|
-
|
|
105
|
-
# Prepare data and message
|
|
106
|
-
msg_info = TextProcessor.concatenate(response.get('status'), '-', response.get('message'))
|
|
107
|
-
raw_data = response.get('data')
|
|
108
|
-
data_to_enqueue = None
|
|
109
|
-
if raw_data:
|
|
110
|
-
data_to_enqueue = try_decrypt_or_return_raw(raw_data, legacy.private_key2, legacy.aes_key_auth)
|
|
111
|
-
|
|
112
|
-
# Enqueue response
|
|
113
|
-
enqueue_response(data_to_enqueue, batch, headers, msg_info)
|
|
114
|
-
|
|
115
|
-
# Measure duration
|
|
116
|
-
duration = (time.time() - start_time) * 1000
|
|
117
|
-
|
|
118
|
-
# Span attributes
|
|
119
|
-
span.set_attribute(Constant.GRAPHQL_OPERATION_NAME, operation_name)
|
|
120
|
-
span.set_attribute(Constant.GRAPHQL_STATUS, response.get('status'))
|
|
121
|
-
span.set_attribute(Constant.GRAPHQL_MESSAGE, response.get('message'))
|
|
122
|
-
span.set_attribute(Constant.GRAPHQL_DURATION_MS, duration)
|
|
123
|
-
|
|
124
|
-
return response
|
|
125
|
-
|
|
126
|
-
except OSDException as ex:
|
|
127
|
-
# Controlled log
|
|
128
|
-
custom_logger.error(f'Controlled error: {str(ex)}')
|
|
129
|
-
duration = (time.time() - start_time) * 1000
|
|
130
|
-
msg = str(ex) if str(ex) else getattr(ex, 'message', 'OSDException')
|
|
131
|
-
span.set_attribute(Constant.GRAPHQL_OPERATION_NAME, operation_name)
|
|
132
|
-
span.set_attribute(Constant.GRAPHQL_STATUS, ex.status_code)
|
|
133
|
-
span.set_attribute(Constant.GRAPHQL_MESSAGE, msg)
|
|
134
|
-
span.set_attribute(Constant.GRAPHQL_DURATION_MS, duration)
|
|
135
|
-
ex.headers = headers
|
|
136
|
-
span.record_exception(ex)
|
|
137
|
-
|
|
138
|
-
_ = asyncio.create_task(ex.send_to_service_bus())
|
|
139
|
-
return ex.get_response()
|
|
140
|
-
|
|
141
|
-
except Exception as e:
|
|
142
|
-
# Unhandled exception log and span
|
|
143
|
-
custom_logger.error(f'Unexpected error: {str(e)}')
|
|
144
|
-
ex = OSDException(error=str(e), headers=headers)
|
|
145
|
-
msg = str(e) if str(e) else getattr(ex, 'message', 'Unknown Exception')
|
|
146
|
-
duration = (time.time() - start_time) * 1000
|
|
147
|
-
span.set_attribute(Constant.GRAPHQL_OPERATION_NAME, operation_name)
|
|
148
|
-
span.set_attribute(Constant.GRAPHQL_STATUS, ex.status_code)
|
|
149
|
-
span.set_attribute(Constant.GRAPHQL_MESSAGE, msg)
|
|
150
|
-
span.record_exception(e)
|
|
151
|
-
span.set_status(trace.status.Status(trace.status.StatusCode.ERROR))
|
|
152
|
-
span.set_attribute(Constant.GRAPHQL_DURATION_MS, duration)
|
|
153
|
-
|
|
154
|
-
_ = asyncio.create_task(ex.send_to_service_bus())
|
|
155
|
-
return ex.get_response()
|
|
156
|
-
|
|
157
|
-
return wrapper
|
|
158
|
-
return decorator
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from typing import Optional, Dict
|
|
4
|
-
from Osdental.Models.Legacy import Legacy
|
|
5
|
-
from Osdental.Shared.Utils.CaseConverter import CaseConverter
|
|
6
|
-
from Osdental.Exception.ControlledException import MissingFieldException
|
|
7
|
-
|
|
8
|
-
@dataclass
|
|
9
|
-
class AuthToken:
|
|
10
|
-
id_token: str
|
|
11
|
-
id_user: str
|
|
12
|
-
id_external_enterprise: str
|
|
13
|
-
id_profile: str
|
|
14
|
-
id_legacy: str
|
|
15
|
-
id_item_report: str
|
|
16
|
-
id_enterprise: str
|
|
17
|
-
id_authorization: str
|
|
18
|
-
user_full_name: str
|
|
19
|
-
abbreviation: str
|
|
20
|
-
aes_key_auth: str
|
|
21
|
-
access_token: Optional[str] = None
|
|
22
|
-
base_id_external_enterprise: Optional[str] = None
|
|
23
|
-
mk_id_external_enterprise: Optional[str] = None
|
|
24
|
-
jwt_user_key: Optional[str] = None
|
|
25
|
-
legacy: Optional[Legacy] = None
|
|
26
|
-
|
|
27
|
-
def __post_init__(self):
|
|
28
|
-
required_fields = [
|
|
29
|
-
'id_token', 'id_user', 'id_external_enterprise', 'id_profile',
|
|
30
|
-
'id_legacy', 'id_item_report', 'id_enterprise', 'id_authorization',
|
|
31
|
-
'user_full_name', 'abbreviation', 'aes_key_auth'
|
|
32
|
-
]
|
|
33
|
-
missing = [f for f in required_fields if not getattr(self, f)]
|
|
34
|
-
if missing:
|
|
35
|
-
raise MissingFieldException(error=f"Missing required fields: {', '.join(missing)}")
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@classmethod
|
|
39
|
-
def from_jwt(cls, payload: Dict[str,str]) -> AuthToken:
|
|
40
|
-
mapped = {CaseConverter.case_to_snake(key): value for key, value in payload.items()}
|
|
41
|
-
valid_fields = cls.__dataclass_fields__.keys()
|
|
42
|
-
clean = {k: v for k, v in mapped.items() if k in valid_fields}
|
|
43
|
-
return cls(**clean)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{encryptors-2.28/src/Osdental/Grpc/Generated → encryptors-2.30/src/Osdental/Graphql}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{encryptors-2.28/src/Osdental/Helpers → encryptors-2.30/src/Osdental/Grpc/Client}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{encryptors-2.28/src/Osdental/RedisCache → encryptors-2.30/src/Osdental/Helpers}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{encryptors-2.28/src/Osdental/ServicesBus → encryptors-2.30/src/Osdental/InternalHttp}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{encryptors-2.28/src/Osdental/Shared/Enums → encryptors-2.30/src/Osdental/Models}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{encryptors-2.28/src/Osdental/Shared/Utils → encryptors-2.30/src/Osdental/RedisCache}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{encryptors-2.28/src/Osdental/Shared → encryptors-2.30/src/Osdental/ServicesBus}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|