Encryptors 2.56__tar.gz → 2.58__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.56 → encryptors-2.58}/PKG-INFO +1 -1
- {encryptors-2.56 → encryptors-2.58}/setup.py +1 -1
- {encryptors-2.56 → encryptors-2.58}/src/Encryptors.egg-info/PKG-INFO +1 -1
- {encryptors-2.56 → encryptors-2.58}/src/Encryptors.egg-info/SOURCES.txt +5 -2
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Constants/Message.py +0 -2
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Decorators/SecureResolver.py +30 -55
- encryptors-2.58/src/Osdental/Enums/AuditType.py +5 -0
- encryptors-2.58/src/Osdental/Enums/ErrorSource.py +5 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Exception/ControlledException.py +26 -3
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Graphql/_Helpers/_AuditHelper.py +2 -2
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Helpers/AuditDispatcher.py +0 -2
- encryptors-2.58/src/Osdental/Helpers/AuditHelper.py +118 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Helpers/_AuthTokenProcessor.py +1 -2
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Http/APIClient.py +3 -2
- encryptors-2.58/src/Osdental/Models/AuditContext.py +61 -0
- encryptors-2.58/src/Osdental/Services/ServiceBusAuditEmitter.py +17 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Services/__init__.py +7 -1
- encryptors-2.56/src/Osdental/Models/AuditConfig.py +0 -9
- encryptors-2.56/src/Osdental/Models/_Audit.py +0 -14
- {encryptors-2.56 → encryptors-2.58}/README.md +0 -0
- {encryptors-2.56 → encryptors-2.58}/setup.cfg +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Encryptors.egg-info/dependency_links.txt +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Encryptors.egg-info/entry_points.txt +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Encryptors.egg-info/requires.txt +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Encryptors.egg-info/top_level.txt +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Cache/Redis.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Cache/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Cli/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Constants/Constant.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Constants/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Database/BaseRepository.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Database/Connection.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Database/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Decorators/Grpc.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Decorators/Retry.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Decorators/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Encryptor/Aes.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Encryptor/Argon2.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Encryptor/Bcrypt.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Encryptor/Jwt.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Encryptor/Rsa.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Encryptor/Sha512.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Encryptor/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Enums/FileType.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Enums/GrahpqlOperation.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Enums/Profile.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Enums/StatusCode.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Enums/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Exception/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Graphql/Extensions/AuditExtension.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Graphql/Extensions/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Graphql/Models/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Graphql/_Exceptions/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Graphql/_Helpers/_TenantPolicy.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Graphql/_Helpers/_TokenService.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Graphql/_Helpers/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Graphql/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Helpers/AzureClassifier.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Helpers/GrpcConnection.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Helpers/JwtTokenHelper.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Helpers/Resilience.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Helpers/ResponseDecryptor.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Helpers/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Http/_Helpers.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Http/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Messaging/AzureServiceBus.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Messaging/Kafka.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Messaging/RabbitMQ.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Messaging/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Models/ApiResponse.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Models/Notification.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Models/Response.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Models/Token.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Models/TokenClaims.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Models/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Rest/Context/RequestContext.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Rest/Context/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Rest/Middlewares/RequestContextMiddleware.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Rest/Middlewares/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Rest/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Secrets/AzureKeyVaultProvider.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Secrets/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Services/JwtAuthTokenService.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Services/WebsocketClient.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Storage/S3Storage.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Storage/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/CaseConverter.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/CodeGenerator.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/DataNormalizer.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/DataUtils.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/DateUtils.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/FileMetaData.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/HashValidator.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/Mapper.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/PasswordGenerator.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/QueryGenerator.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/RsaUtils.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/TextProcessor.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/src/Osdental/Utils/__init__.py +0 -0
- {encryptors-2.56 → encryptors-2.58}/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.58",
|
|
6
6
|
author="OSDental LLC",
|
|
7
7
|
author_email="support@osdental.ai",
|
|
8
8
|
description="End-to-end algorithm library",
|
|
@@ -27,6 +27,8 @@ src/Osdental/Encryptor/Jwt.py
|
|
|
27
27
|
src/Osdental/Encryptor/Rsa.py
|
|
28
28
|
src/Osdental/Encryptor/Sha512.py
|
|
29
29
|
src/Osdental/Encryptor/__init__.py
|
|
30
|
+
src/Osdental/Enums/AuditType.py
|
|
31
|
+
src/Osdental/Enums/ErrorSource.py
|
|
30
32
|
src/Osdental/Enums/FileType.py
|
|
31
33
|
src/Osdental/Enums/GrahpqlOperation.py
|
|
32
34
|
src/Osdental/Enums/Profile.py
|
|
@@ -45,6 +47,7 @@ src/Osdental/Graphql/_Helpers/_TenantPolicy.py
|
|
|
45
47
|
src/Osdental/Graphql/_Helpers/_TokenService.py
|
|
46
48
|
src/Osdental/Graphql/_Helpers/__init__.py
|
|
47
49
|
src/Osdental/Helpers/AuditDispatcher.py
|
|
50
|
+
src/Osdental/Helpers/AuditHelper.py
|
|
48
51
|
src/Osdental/Helpers/AzureClassifier.py
|
|
49
52
|
src/Osdental/Helpers/GrpcConnection.py
|
|
50
53
|
src/Osdental/Helpers/JwtTokenHelper.py
|
|
@@ -60,12 +63,11 @@ src/Osdental/Messaging/Kafka.py
|
|
|
60
63
|
src/Osdental/Messaging/RabbitMQ.py
|
|
61
64
|
src/Osdental/Messaging/__init__.py
|
|
62
65
|
src/Osdental/Models/ApiResponse.py
|
|
63
|
-
src/Osdental/Models/
|
|
66
|
+
src/Osdental/Models/AuditContext.py
|
|
64
67
|
src/Osdental/Models/Notification.py
|
|
65
68
|
src/Osdental/Models/Response.py
|
|
66
69
|
src/Osdental/Models/Token.py
|
|
67
70
|
src/Osdental/Models/TokenClaims.py
|
|
68
|
-
src/Osdental/Models/_Audit.py
|
|
69
71
|
src/Osdental/Models/__init__.py
|
|
70
72
|
src/Osdental/Rest/__init__.py
|
|
71
73
|
src/Osdental/Rest/Context/RequestContext.py
|
|
@@ -75,6 +77,7 @@ src/Osdental/Rest/Middlewares/__init__.py
|
|
|
75
77
|
src/Osdental/Secrets/AzureKeyVaultProvider.py
|
|
76
78
|
src/Osdental/Secrets/__init__.py
|
|
77
79
|
src/Osdental/Services/JwtAuthTokenService.py
|
|
80
|
+
src/Osdental/Services/ServiceBusAuditEmitter.py
|
|
78
81
|
src/Osdental/Services/WebsocketClient.py
|
|
79
82
|
src/Osdental/Services/__init__.py
|
|
80
83
|
src/Osdental/Storage/AzureBlobStorage.py
|
|
@@ -31,8 +31,6 @@ class Message:
|
|
|
31
31
|
UNEXPECTED_DECRYPTED_DATA_FORMAT_MSG = "Unexpected format in decrypted data."
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
DATABASE_EXECUTION_ERROR_MSG = "An unexpected error occurred while executing a database operation."
|
|
35
|
-
DATABASE_INTEGRITY_ERROR_MSG = "A database integrity constraint was violated during execution."
|
|
36
34
|
QUERY_NOT_PROVIDED_MSG = "Query not provided. Please include a valid query in your request."
|
|
37
35
|
FILE_PATH_NOT_PROVIDED_MSG = "File path not provided."
|
|
38
36
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from functools import wraps
|
|
3
|
-
from typing import Callable
|
|
3
|
+
from typing import Callable
|
|
4
4
|
from graphql import GraphQLResolveInfo
|
|
5
5
|
from Osdental.Models.Response import Response
|
|
6
6
|
from Osdental.Graphql.Models import BaseGraphQLContext
|
|
@@ -15,29 +15,15 @@ from Osdental.Helpers._AuthTokenProcessor import (
|
|
|
15
15
|
extract_bearer_token, build_auth_token, decrypt_and_parse_payload
|
|
16
16
|
)
|
|
17
17
|
from Osdental.Enums.StatusCode import StatusCode
|
|
18
|
-
from Osdental.
|
|
18
|
+
from Osdental.Models.AuditContext import AuditContext
|
|
19
|
+
from Osdental.Helpers.AuditHelper import (
|
|
20
|
+
_build_success_payload, _build_error_payload, _build_unexpected_payload
|
|
21
|
+
)
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
logger = logging.getLogger(__name__)
|
|
22
25
|
|
|
23
26
|
|
|
24
|
-
def __test(
|
|
25
|
-
dispatcher: AuditDispatcher,
|
|
26
|
-
request,
|
|
27
|
-
request_payload: Dict[str, Any],
|
|
28
|
-
result: Response,
|
|
29
|
-
decrypted_key: str
|
|
30
|
-
) -> None:
|
|
31
|
-
|
|
32
|
-
dispatcher.dispatch(
|
|
33
|
-
request=request,
|
|
34
|
-
request_payload=request_payload,
|
|
35
|
-
result=result,
|
|
36
|
-
metadata={
|
|
37
|
-
"decrypted_key": decrypted_key
|
|
38
|
-
},
|
|
39
|
-
audit_type="MESSAGE_LOG_INTERNAL"
|
|
40
|
-
)
|
|
41
27
|
|
|
42
28
|
def resolver(public: bool = False, action=None):
|
|
43
29
|
|
|
@@ -61,7 +47,7 @@ def resolver(public: bool = False, action=None):
|
|
|
61
47
|
token_service: IAuthTokenService = container.auth_token_service
|
|
62
48
|
user_token = extract_bearer_token(headers)
|
|
63
49
|
|
|
64
|
-
claims: UserTokenClaims = token_service.validate_internal_access_token(
|
|
50
|
+
claims: UserTokenClaims = await token_service.validate_internal_access_token(
|
|
65
51
|
user_token
|
|
66
52
|
)
|
|
67
53
|
|
|
@@ -97,57 +83,46 @@ def resolver(public: bool = False, action=None):
|
|
|
97
83
|
error="User not allowed to perform this action"
|
|
98
84
|
)
|
|
99
85
|
|
|
100
|
-
# ── 5.
|
|
86
|
+
# ── 5. CONTEXTO DE AUDITORÍA ──────────────
|
|
87
|
+
# Lo construimos antes del resolver para que siempre exista
|
|
88
|
+
|
|
89
|
+
audit_ctx = AuditContext.from_request(
|
|
90
|
+
request=request,
|
|
91
|
+
settings=settings,
|
|
92
|
+
body=body,
|
|
93
|
+
operation_type=info.operation.operation.value,
|
|
94
|
+
token=token,
|
|
95
|
+
decrypted_payload=decrypted_payload,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# ── 6. EJECUTAR RESOLVER ──────────────────
|
|
101
99
|
result = await func(obj, info, **kwargs)
|
|
102
100
|
|
|
103
101
|
if not isinstance(result, Response):
|
|
104
102
|
raise TypeError("Resolver must return a Response instance")
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
"env": settings.environment,
|
|
110
|
-
"ms_name": settings.microservice_name,
|
|
111
|
-
"ms_version": settings.microservice_version,
|
|
112
|
-
"is_auditable": settings.is_auditable,
|
|
113
|
-
"operation_type": info.operation.operation.value,
|
|
114
|
-
"operation_name": body.get("operationName", "UnknownOperation"),
|
|
115
|
-
"query": body.get("query"),
|
|
116
|
-
"variables": decrypted_payload,
|
|
117
|
-
"user": (
|
|
118
|
-
token.user_full_name
|
|
119
|
-
if token
|
|
120
|
-
else "Public"
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if isinstance(result, Response):
|
|
125
|
-
decrypted_key = result.key if not token else token.aes_key_auth
|
|
126
|
-
|
|
127
|
-
dispatcher = AuditDispatcher(
|
|
128
|
-
messaging=container.az_sb_audit,
|
|
129
|
-
storage=container.az_blob_storage
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
__test(
|
|
133
|
-
dispatcher=dispatcher,
|
|
134
|
-
request_payload=request_payload,
|
|
135
|
-
request=request,
|
|
136
|
-
decrypted_key=decrypted_key,
|
|
137
|
-
result=result
|
|
104
|
+
# ── 7. AUDITAR ÉXITO ──────────────────────
|
|
105
|
+
await container.audit_emitter.emit(
|
|
106
|
+
_build_success_payload(audit_ctx, result)
|
|
138
107
|
)
|
|
139
108
|
|
|
140
109
|
return result
|
|
141
110
|
|
|
142
111
|
except OSDException as e:
|
|
143
|
-
logger.warning(f"Business error: {str(e)}")
|
|
112
|
+
# logger.warning(f"Business error: {str(e)}")
|
|
113
|
+
await container.audit_emitter.emit(
|
|
114
|
+
_build_error_payload(audit_ctx, e)
|
|
115
|
+
)
|
|
144
116
|
return Response(
|
|
145
117
|
status=e.status_code,
|
|
146
118
|
message=e.message,
|
|
147
119
|
error=e.error
|
|
148
120
|
)
|
|
149
121
|
except Exception as e:
|
|
150
|
-
logger.exception(f"Unexpected error: {str(e)}")
|
|
122
|
+
# logger.exception(f"Unexpected error: {str(e)}")
|
|
123
|
+
await container.audit_emitter.emit(
|
|
124
|
+
_build_unexpected_payload(audit_ctx, e)
|
|
125
|
+
)
|
|
151
126
|
return Response(
|
|
152
127
|
status=StatusCode.INTERNAL_SERVER_ERROR,
|
|
153
128
|
message="Could not process request.",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
from Osdental.Enums.StatusCode import StatusCode
|
|
3
3
|
from Osdental.Constants.Message import Message
|
|
4
|
+
from Osdental.Enums.ErrorSource import ErrorSource
|
|
4
5
|
|
|
5
6
|
class OSDException(Exception):
|
|
6
7
|
""" Base class for all custom exceptions. """
|
|
@@ -9,16 +10,28 @@ class OSDException(Exception):
|
|
|
9
10
|
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
10
11
|
error: str = None,
|
|
11
12
|
status_code: Any = StatusCode.INTERNAL_SERVER_ERROR,
|
|
13
|
+
source: ErrorSource = ErrorSource.INTERNAL,
|
|
14
|
+
raw_status_code: Any = None,
|
|
12
15
|
):
|
|
13
16
|
super().__init__(error)
|
|
14
17
|
self.message = message
|
|
15
18
|
self.error = error
|
|
16
19
|
self.status_code = status_code
|
|
17
|
-
|
|
20
|
+
self.source = source
|
|
21
|
+
self.raw_status_code = raw_status_code
|
|
18
22
|
|
|
19
23
|
def __str__(self):
|
|
20
24
|
error_detail = self.error if self.error else "None"
|
|
21
|
-
return f"[{self.status_code}] {self.message} | {error_detail}"
|
|
25
|
+
return f"[{self.source} | {self.status_code}] {self.message} | {error_detail}]"
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def is_external(self) -> bool:
|
|
29
|
+
return self.source == ErrorSource.EXTERNAL
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def audit_status_code(self) -> Any:
|
|
33
|
+
"""El que va a la auditoría: real si existe, propio si no."""
|
|
34
|
+
return self.raw_status_code if self.raw_status_code is not None else self.status_code
|
|
22
35
|
|
|
23
36
|
class UnauthorizedException(OSDException):
|
|
24
37
|
def __init__(
|
|
@@ -29,6 +42,8 @@ class UnauthorizedException(OSDException):
|
|
|
29
42
|
):
|
|
30
43
|
super().__init__(message=message, error=error, status_code=status_code)
|
|
31
44
|
|
|
45
|
+
|
|
46
|
+
|
|
32
47
|
class AccessDeniedException(OSDException):
|
|
33
48
|
def __init__(
|
|
34
49
|
self,
|
|
@@ -119,5 +134,13 @@ class HttpClientException(OSDException):
|
|
|
119
134
|
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
120
135
|
error: str = None,
|
|
121
136
|
status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
|
|
137
|
+
source: ErrorSource = ErrorSource.EXTERNAL,
|
|
138
|
+
raw_status_code: Any = None,
|
|
122
139
|
):
|
|
123
|
-
super().__init__(
|
|
140
|
+
super().__init__(
|
|
141
|
+
message=message,
|
|
142
|
+
error=error,
|
|
143
|
+
status_code=status_code,
|
|
144
|
+
source=source,
|
|
145
|
+
raw_status_code=raw_status_code
|
|
146
|
+
)
|
|
@@ -4,10 +4,10 @@ from typing import Dict, Literal, Any, Optional
|
|
|
4
4
|
class AuditHelper:
|
|
5
5
|
|
|
6
6
|
@staticmethod
|
|
7
|
-
|
|
7
|
+
def build_request_payload(
|
|
8
8
|
request,
|
|
9
9
|
payload: Dict[str, Any],
|
|
10
|
-
audit_type
|
|
10
|
+
audit_type: str
|
|
11
11
|
) -> Dict[str, Any]:
|
|
12
12
|
|
|
13
13
|
default_value = "*"
|
|
@@ -91,8 +91,6 @@ class AuditDispatcher:
|
|
|
91
91
|
result: Response | ApiResponse = payload["result"]
|
|
92
92
|
audit_type = payload["audit_type"]
|
|
93
93
|
|
|
94
|
-
print(request_payload)
|
|
95
|
-
|
|
96
94
|
if not request_payload.get("is_auditable"):
|
|
97
95
|
logger.info("Auditing is disabled. Skipping audit processing.")
|
|
98
96
|
return
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from fastapi import Request
|
|
3
|
+
from typing import Optional, Dict, Any, Literal
|
|
4
|
+
from Osdental.Models.AuditContext import AuditContext
|
|
5
|
+
from Osdental.Models.Response import Response
|
|
6
|
+
from Osdental.Exception.ControlledException import OSDException
|
|
7
|
+
from Osdental.Enums.StatusCode import StatusCode
|
|
8
|
+
from Osdental.Enums.AuditType import AuditType
|
|
9
|
+
|
|
10
|
+
def build_request_payload(
|
|
11
|
+
request: Request,
|
|
12
|
+
payload: Dict[str, Any],
|
|
13
|
+
audit_type: str
|
|
14
|
+
) -> Dict[str, Any]:
|
|
15
|
+
|
|
16
|
+
default_value = "*"
|
|
17
|
+
|
|
18
|
+
user_ip = request.headers.get("X-Forwarded-For")
|
|
19
|
+
if user_ip:
|
|
20
|
+
user_ip = user_ip.split(",")[0]
|
|
21
|
+
else:
|
|
22
|
+
user_ip = getattr(request.client, "host", "*")
|
|
23
|
+
|
|
24
|
+
SAFE_HEADERS = {
|
|
25
|
+
"user-agent",
|
|
26
|
+
"host",
|
|
27
|
+
"origin",
|
|
28
|
+
"referer",
|
|
29
|
+
"content-type"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
headers = {
|
|
33
|
+
k: v
|
|
34
|
+
for k, v in request.headers.items()
|
|
35
|
+
if k.lower() in SAFE_HEADERS
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
"idMessageLog": request.headers.get("Idmessagelog"),
|
|
40
|
+
"environment": payload.get("env"),
|
|
41
|
+
"header": json.dumps(headers),
|
|
42
|
+
"microServiceUrl": str(request.url),
|
|
43
|
+
"microServiceName": payload.get("ms_name"),
|
|
44
|
+
"microServiceVersion": payload.get("ms_version"),
|
|
45
|
+
"serviceName": payload.get("operation_name"),
|
|
46
|
+
"machineNameUser": request.headers.get("Machinenameuser", default_value),
|
|
47
|
+
"ipUser": user_ip or default_value,
|
|
48
|
+
"userName": payload.get("user"),
|
|
49
|
+
"localitation": default_value,
|
|
50
|
+
"httpMethod": request.method,
|
|
51
|
+
"messageIn": json.dumps(payload) if payload else default_value,
|
|
52
|
+
"auditLog": audit_type,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def _build_final_payload(
|
|
56
|
+
type_: Literal["RESPONSE", "ERROR"],
|
|
57
|
+
status_code: int,
|
|
58
|
+
result: Optional[Any] = None,
|
|
59
|
+
error: Optional[Any] = None
|
|
60
|
+
) -> Dict[str, Any]:
|
|
61
|
+
|
|
62
|
+
result = "*" if result is None else result
|
|
63
|
+
error = "*" if error is None else error
|
|
64
|
+
|
|
65
|
+
if isinstance(result, (dict, list)):
|
|
66
|
+
result = json.dumps(result)
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
"type": type_,
|
|
70
|
+
"httpResponseCode": status_code,
|
|
71
|
+
"messageOut": result,
|
|
72
|
+
"errorProducer": error
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
def _build_success_payload(ctx: Optional[AuditContext], result: Response) -> Dict[str, Any]:
|
|
76
|
+
base = _base_payload(ctx, AuditType.INTERNAL)
|
|
77
|
+
return base | _build_final_payload(
|
|
78
|
+
type_="RESPONSE",
|
|
79
|
+
status_code=result.status,
|
|
80
|
+
result=result.data
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def _build_error_payload(ctx: Optional[AuditContext], exc: OSDException) -> Dict[str, Any]:
|
|
84
|
+
base = _base_payload(ctx, AuditType.INTERNAL)
|
|
85
|
+
return base | _build_final_payload(
|
|
86
|
+
type_="ERROR",
|
|
87
|
+
status_code=exc.audit_status_code,
|
|
88
|
+
error=exc.error or exc.message
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def _build_unexpected_payload(ctx: Optional[AuditContext], exc: Exception) -> Dict[str, Any]:
|
|
92
|
+
base = _base_payload(ctx, AuditType.INTERNAL)
|
|
93
|
+
return base | _build_final_payload(
|
|
94
|
+
type_="ERROR",
|
|
95
|
+
status_code=StatusCode.INTERNAL_SERVER_ERROR,
|
|
96
|
+
error=str(exc)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def _base_payload(ctx: Optional[AuditContext], audit_type: AuditType = AuditType.INTERNAL) -> Dict[str, Any]:
|
|
100
|
+
if not ctx:
|
|
101
|
+
return {"user": "unknown", "operation_name": "unknown"}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
"idMessageLog": ctx.message_log_id,
|
|
105
|
+
"environment": ctx.env,
|
|
106
|
+
"header": json.dumps(ctx.headers or {}),
|
|
107
|
+
"microServiceUrl": ctx.url,
|
|
108
|
+
"microServiceName": ctx.ms_name,
|
|
109
|
+
"microServiceVersion": ctx.ms_version,
|
|
110
|
+
"serviceName": ctx.operation_name,
|
|
111
|
+
"machineNameUser": "*",
|
|
112
|
+
"ipUser": ctx.user_ip or "*",
|
|
113
|
+
"userName": ctx.user,
|
|
114
|
+
"localitation": "*",
|
|
115
|
+
"httpMethod": ctx.http_method,
|
|
116
|
+
"messageIn": json.dumps(ctx.variables) if ctx.variables else "*",
|
|
117
|
+
"auditLog": audit_type,
|
|
118
|
+
}
|
|
@@ -17,7 +17,6 @@ def extract_bearer_token(headers: Headers) -> str:
|
|
|
17
17
|
return authorization.removeprefix("Bearer ").strip()
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
|
|
21
20
|
def build_auth_token(
|
|
22
21
|
claims: UserTokenClaims,
|
|
23
22
|
headers: Headers,
|
|
@@ -31,6 +30,7 @@ def build_auth_token(
|
|
|
31
30
|
decrypted_payload.get("idExternalEnterprise")
|
|
32
31
|
if decrypted_payload else None
|
|
33
32
|
)
|
|
33
|
+
mk_id_external_enterprise = None
|
|
34
34
|
|
|
35
35
|
if external_enterprise_req:
|
|
36
36
|
final_id = external_enterprise_req
|
|
@@ -50,7 +50,6 @@ def build_auth_token(
|
|
|
50
50
|
|
|
51
51
|
final_id = str(UUID(int=0))
|
|
52
52
|
|
|
53
|
-
|
|
54
53
|
token = AuthToken(
|
|
55
54
|
id_token=claims.id_token,
|
|
56
55
|
id_user=claims.id_user,
|
|
@@ -53,7 +53,8 @@ class APIClient:
|
|
|
53
53
|
audit_http_error(exc.response, method, url, kwargs)
|
|
54
54
|
raise HttpClientException(
|
|
55
55
|
status_code=StatusCode.BAD_GATEWAY,
|
|
56
|
-
error=exc.response.text
|
|
56
|
+
error=exc.response.text,
|
|
57
|
+
raw_status_code=exc.response.status_code,
|
|
57
58
|
) from exc
|
|
58
59
|
|
|
59
60
|
except httpx.RequestError as exc:
|
|
@@ -61,7 +62,7 @@ class APIClient:
|
|
|
61
62
|
audit_exception_error(exc, method, url, kwargs)
|
|
62
63
|
raise HttpClientException(
|
|
63
64
|
status_code=StatusCode.GATEWAY_TIMEOUT,
|
|
64
|
-
error=str(exc)
|
|
65
|
+
error=str(exc),
|
|
65
66
|
) from exc
|
|
66
67
|
|
|
67
68
|
except Exception as exc:
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional, Dict, Any
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class AuditContext:
|
|
8
|
+
env: str
|
|
9
|
+
ms_name: str
|
|
10
|
+
ms_version: str
|
|
11
|
+
is_auditable: bool
|
|
12
|
+
operation_type: str
|
|
13
|
+
operation_name: str
|
|
14
|
+
query: Optional[str]
|
|
15
|
+
variables: Optional[Dict[str, Any]]
|
|
16
|
+
user: str
|
|
17
|
+
message_log_id: Optional[str]
|
|
18
|
+
decrypted_key: Optional[str] = None
|
|
19
|
+
user_ip: Optional[str] = None
|
|
20
|
+
headers: Optional[Dict[str, str]] = None
|
|
21
|
+
url: Optional[str] = None
|
|
22
|
+
http_method: Optional[str] = None
|
|
23
|
+
|
|
24
|
+
SAFE_HEADERS = {"user-agent", "host", "origin", "referer", "content-type"}
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_request(
|
|
28
|
+
cls,
|
|
29
|
+
request: Request,
|
|
30
|
+
settings,
|
|
31
|
+
body: Dict,
|
|
32
|
+
operation_type: str,
|
|
33
|
+
token=None,
|
|
34
|
+
decrypted_payload=None,
|
|
35
|
+
) -> "AuditContext":
|
|
36
|
+
|
|
37
|
+
user_ip = request.headers.get("X-Forwarded-For")
|
|
38
|
+
user_ip = user_ip.split(",")[0] if user_ip else getattr(request.client, "host", "*")
|
|
39
|
+
|
|
40
|
+
safe_headers = {
|
|
41
|
+
k: v for k, v in request.headers.items()
|
|
42
|
+
if k.lower() in cls.SAFE_HEADERS
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return cls(
|
|
46
|
+
env=settings.environment,
|
|
47
|
+
ms_name=settings.microservice_name,
|
|
48
|
+
ms_version=settings.microservice_version,
|
|
49
|
+
is_auditable=settings.is_auditable,
|
|
50
|
+
operation_type=operation_type,
|
|
51
|
+
operation_name=body.get("operationName", "UnknownOperation"),
|
|
52
|
+
query=body.get("query"),
|
|
53
|
+
variables=decrypted_payload,
|
|
54
|
+
user=token.user_full_name if token else "Public",
|
|
55
|
+
message_log_id=request.headers.get("Idmessagelog"),
|
|
56
|
+
decrypted_key=token.aes_key_auth if token else None,
|
|
57
|
+
user_ip=user_ip,
|
|
58
|
+
headers=safe_headers,
|
|
59
|
+
url=str(request.url),
|
|
60
|
+
http_method=request.method,
|
|
61
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
from Osdental.Messaging import IMessageQueue
|
|
4
|
+
from Osdental.Storage import IStorageService
|
|
5
|
+
|
|
6
|
+
class ServiceBusAuditEmitter:
|
|
7
|
+
|
|
8
|
+
def __init__(self, messaging: IMessageQueue, storage: IStorageService):
|
|
9
|
+
self._messaging = messaging
|
|
10
|
+
self._storage = storage
|
|
11
|
+
|
|
12
|
+
async def emit(self, payload: Dict[str, Any]) -> None:
|
|
13
|
+
message_log_id = payload.get("idMessageLog")
|
|
14
|
+
data = json.dumps(payload)
|
|
15
|
+
blob_name = f"audits/{message_log_id}.text"
|
|
16
|
+
await self._storage.upload(blob_name, data)
|
|
17
|
+
await self._messaging.send_message(blob_name)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Protocol, Any, Dict
|
|
2
3
|
from Osdental.Models.Notification import Notification
|
|
3
4
|
|
|
4
5
|
class INotificationPublisher(ABC):
|
|
@@ -44,4 +45,9 @@ class IAuthTokenService(ABC):
|
|
|
44
45
|
self,
|
|
45
46
|
token: str,
|
|
46
47
|
) -> ActionTokenClaims:
|
|
47
|
-
pass
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class IAuditEmitter(Protocol):
|
|
52
|
+
async def emit(self, payload: Dict[str, Any]) -> None:
|
|
53
|
+
...
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass, field
|
|
2
|
-
from typing import Any, Optional
|
|
3
|
-
from fastapi import Request
|
|
4
|
-
from Osdental.Models.AuditConfig import AuditConfig
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@dataclass
|
|
8
|
-
class Audit:
|
|
9
|
-
request: Request
|
|
10
|
-
audit_config: AuditConfig
|
|
11
|
-
payload: Any = field(default_factory=dict)
|
|
12
|
-
audit_type: str = field(default="MESSAGE_LOG_INTERNAL")
|
|
13
|
-
operation_name: Optional[str] = "Unknown"
|
|
14
|
-
full_name: Optional[str] = "Joe Doe"
|
|
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
|
|
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.56 → encryptors-2.58}/src/Osdental/Rest/Middlewares/RequestContextMiddleware.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
|