Encryptors 2.58__tar.gz → 2.59__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. {encryptors-2.58 → encryptors-2.59}/PKG-INFO +1 -1
  2. {encryptors-2.58 → encryptors-2.59}/setup.py +1 -1
  3. {encryptors-2.58 → encryptors-2.59}/src/Encryptors.egg-info/PKG-INFO +1 -1
  4. {encryptors-2.58 → encryptors-2.59}/src/Encryptors.egg-info/SOURCES.txt +3 -17
  5. encryptors-2.59/src/Osdental/Context/__init__.py +5 -0
  6. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Decorators/SecureResolver.py +4 -3
  7. encryptors-2.59/src/Osdental/Enums/ResultType.py +5 -0
  8. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/AuditHelper.py +58 -10
  9. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Http/APIClient.py +39 -12
  10. encryptors-2.58/src/Osdental/Graphql/Extensions/AuditExtension.py +0 -196
  11. encryptors-2.58/src/Osdental/Graphql/_Exceptions/__init__.py +0 -8
  12. encryptors-2.58/src/Osdental/Graphql/_Helpers/_AuditHelper.py +0 -67
  13. encryptors-2.58/src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py +0 -11
  14. encryptors-2.58/src/Osdental/Graphql/_Helpers/_TenantPolicy.py +0 -46
  15. encryptors-2.58/src/Osdental/Graphql/_Helpers/_TokenService.py +0 -44
  16. encryptors-2.58/src/Osdental/Helpers/AuditDispatcher.py +0 -169
  17. encryptors-2.58/src/Osdental/Http/_Helpers.py +0 -125
  18. encryptors-2.58/src/Osdental/Models/__init__.py +0 -0
  19. encryptors-2.58/src/Osdental/Rest/Context/RequestContext.py +0 -19
  20. encryptors-2.58/src/Osdental/Rest/Context/__init__.py +0 -0
  21. encryptors-2.58/src/Osdental/Rest/Middlewares/RequestContextMiddleware.py +0 -22
  22. encryptors-2.58/src/Osdental/Rest/Middlewares/__init__.py +0 -0
  23. encryptors-2.58/src/Osdental/Rest/__init__.py +0 -0
  24. encryptors-2.58/src/Osdental/Utils/__init__.py +0 -0
  25. encryptors-2.58/src/Osdental/__init__.py +0 -0
  26. {encryptors-2.58 → encryptors-2.59}/README.md +0 -0
  27. {encryptors-2.58 → encryptors-2.59}/setup.cfg +0 -0
  28. {encryptors-2.58 → encryptors-2.59}/src/Encryptors.egg-info/dependency_links.txt +0 -0
  29. {encryptors-2.58 → encryptors-2.59}/src/Encryptors.egg-info/entry_points.txt +0 -0
  30. {encryptors-2.58 → encryptors-2.59}/src/Encryptors.egg-info/requires.txt +0 -0
  31. {encryptors-2.58 → encryptors-2.59}/src/Encryptors.egg-info/top_level.txt +0 -0
  32. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Cache/Redis.py +0 -0
  33. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Cache/__init__.py +0 -0
  34. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Cli/__init__.py +0 -0
  35. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Constants/Constant.py +0 -0
  36. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Constants/Message.py +0 -0
  37. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Constants/__init__.py +0 -0
  38. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Database/BaseRepository.py +0 -0
  39. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Database/Connection.py +0 -0
  40. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Database/__init__.py +0 -0
  41. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Decorators/Grpc.py +0 -0
  42. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Decorators/Retry.py +0 -0
  43. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Decorators/__init__.py +0 -0
  44. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/Aes.py +0 -0
  45. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/Argon2.py +0 -0
  46. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/Bcrypt.py +0 -0
  47. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/Jwt.py +0 -0
  48. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/Rsa.py +0 -0
  49. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/Sha512.py +0 -0
  50. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Encryptor/__init__.py +0 -0
  51. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/AuditType.py +0 -0
  52. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/ErrorSource.py +0 -0
  53. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/FileType.py +0 -0
  54. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/GrahpqlOperation.py +0 -0
  55. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/Profile.py +0 -0
  56. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/StatusCode.py +0 -0
  57. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Enums/__init__.py +0 -0
  58. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Exception/ControlledException.py +0 -0
  59. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Exception/__init__.py +0 -0
  60. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/AzureClassifier.py +0 -0
  61. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/GrpcConnection.py +0 -0
  62. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/JwtTokenHelper.py +0 -0
  63. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/Resilience.py +0 -0
  64. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/ResponseDecryptor.py +0 -0
  65. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Helpers/_AuthTokenProcessor.py +0 -0
  66. {encryptors-2.58/src/Osdental/Graphql/Extensions → encryptors-2.59/src/Osdental/Helpers}/__init__.py +0 -0
  67. {encryptors-2.58/src/Osdental/Graphql/_Helpers → encryptors-2.59/src/Osdental/Http}/__init__.py +0 -0
  68. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Messaging/AzureServiceBus.py +0 -0
  69. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Messaging/Kafka.py +0 -0
  70. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Messaging/RabbitMQ.py +0 -0
  71. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Messaging/__init__.py +0 -0
  72. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Models/ApiResponse.py +0 -0
  73. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Models/AuditContext.py +0 -0
  74. /encryptors-2.58/src/Osdental/Graphql/Models/__init__.py → /encryptors-2.59/src/Osdental/Models/Graphql.py +0 -0
  75. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Models/Notification.py +0 -0
  76. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Models/Response.py +0 -0
  77. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Models/Token.py +0 -0
  78. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Models/TokenClaims.py +0 -0
  79. {encryptors-2.58/src/Osdental/Graphql → encryptors-2.59/src/Osdental/Models}/__init__.py +0 -0
  80. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Secrets/AzureKeyVaultProvider.py +0 -0
  81. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Secrets/__init__.py +0 -0
  82. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Services/JwtAuthTokenService.py +0 -0
  83. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Services/ServiceBusAuditEmitter.py +0 -0
  84. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Services/WebsocketClient.py +0 -0
  85. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Services/__init__.py +0 -0
  86. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
  87. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Storage/S3Storage.py +0 -0
  88. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Storage/__init__.py +0 -0
  89. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/CaseConverter.py +0 -0
  90. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/CodeGenerator.py +0 -0
  91. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/DataNormalizer.py +0 -0
  92. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/DataUtils.py +0 -0
  93. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/DateUtils.py +0 -0
  94. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/FileMetaData.py +0 -0
  95. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/HashValidator.py +0 -0
  96. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/Mapper.py +0 -0
  97. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/PasswordGenerator.py +0 -0
  98. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/QueryGenerator.py +0 -0
  99. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/RsaUtils.py +0 -0
  100. {encryptors-2.58 → encryptors-2.59}/src/Osdental/Utils/TextProcessor.py +0 -0
  101. {encryptors-2.58/src/Osdental/Helpers → encryptors-2.59/src/Osdental/Utils}/__init__.py +0 -0
  102. {encryptors-2.58/src/Osdental/Http → encryptors-2.59/src/Osdental}/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Encryptors
3
- Version: 2.58
3
+ Version: 2.59
4
4
  Summary: End-to-end algorithm library
5
5
  Author: OSDental LLC
6
6
  Author-email: support@osdental.ai
@@ -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.58",
5
+ version="2.59",
6
6
  author="OSDental LLC",
7
7
  author_email="support@osdental.ai",
8
8
  description="End-to-end algorithm library",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Encryptors
3
- Version: 2.58
3
+ Version: 2.59
4
4
  Summary: End-to-end algorithm library
5
5
  Author: OSDental LLC
6
6
  Author-email: support@osdental.ai
@@ -13,6 +13,7 @@ src/Osdental/Cli/__init__.py
13
13
  src/Osdental/Constants/Constant.py
14
14
  src/Osdental/Constants/Message.py
15
15
  src/Osdental/Constants/__init__.py
16
+ src/Osdental/Context/__init__.py
16
17
  src/Osdental/Database/BaseRepository.py
17
18
  src/Osdental/Database/Connection.py
18
19
  src/Osdental/Database/__init__.py
@@ -32,21 +33,11 @@ src/Osdental/Enums/ErrorSource.py
32
33
  src/Osdental/Enums/FileType.py
33
34
  src/Osdental/Enums/GrahpqlOperation.py
34
35
  src/Osdental/Enums/Profile.py
36
+ src/Osdental/Enums/ResultType.py
35
37
  src/Osdental/Enums/StatusCode.py
36
38
  src/Osdental/Enums/__init__.py
37
39
  src/Osdental/Exception/ControlledException.py
38
40
  src/Osdental/Exception/__init__.py
39
- src/Osdental/Graphql/__init__.py
40
- src/Osdental/Graphql/Extensions/AuditExtension.py
41
- src/Osdental/Graphql/Extensions/__init__.py
42
- src/Osdental/Graphql/Models/__init__.py
43
- src/Osdental/Graphql/_Exceptions/__init__.py
44
- src/Osdental/Graphql/_Helpers/_AuditHelper.py
45
- src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py
46
- src/Osdental/Graphql/_Helpers/_TenantPolicy.py
47
- src/Osdental/Graphql/_Helpers/_TokenService.py
48
- src/Osdental/Graphql/_Helpers/__init__.py
49
- src/Osdental/Helpers/AuditDispatcher.py
50
41
  src/Osdental/Helpers/AuditHelper.py
51
42
  src/Osdental/Helpers/AzureClassifier.py
52
43
  src/Osdental/Helpers/GrpcConnection.py
@@ -56,7 +47,6 @@ src/Osdental/Helpers/ResponseDecryptor.py
56
47
  src/Osdental/Helpers/_AuthTokenProcessor.py
57
48
  src/Osdental/Helpers/__init__.py
58
49
  src/Osdental/Http/APIClient.py
59
- src/Osdental/Http/_Helpers.py
60
50
  src/Osdental/Http/__init__.py
61
51
  src/Osdental/Messaging/AzureServiceBus.py
62
52
  src/Osdental/Messaging/Kafka.py
@@ -64,16 +54,12 @@ src/Osdental/Messaging/RabbitMQ.py
64
54
  src/Osdental/Messaging/__init__.py
65
55
  src/Osdental/Models/ApiResponse.py
66
56
  src/Osdental/Models/AuditContext.py
57
+ src/Osdental/Models/Graphql.py
67
58
  src/Osdental/Models/Notification.py
68
59
  src/Osdental/Models/Response.py
69
60
  src/Osdental/Models/Token.py
70
61
  src/Osdental/Models/TokenClaims.py
71
62
  src/Osdental/Models/__init__.py
72
- src/Osdental/Rest/__init__.py
73
- src/Osdental/Rest/Context/RequestContext.py
74
- src/Osdental/Rest/Context/__init__.py
75
- src/Osdental/Rest/Middlewares/RequestContextMiddleware.py
76
- src/Osdental/Rest/Middlewares/__init__.py
77
63
  src/Osdental/Secrets/AzureKeyVaultProvider.py
78
64
  src/Osdental/Secrets/__init__.py
79
65
  src/Osdental/Services/JwtAuthTokenService.py
@@ -0,0 +1,5 @@
1
+ from contextvars import ContextVar
2
+ from typing import Optional
3
+ from Osdental.Models.AuditContext import AuditContext
4
+
5
+ _ctx: ContextVar[Optional[AuditContext]] = ContextVar('ctx', default=None)
@@ -3,7 +3,7 @@ from functools import wraps
3
3
  from typing import Callable
4
4
  from graphql import GraphQLResolveInfo
5
5
  from Osdental.Models.Response import Response
6
- from Osdental.Graphql.Models import BaseGraphQLContext
6
+ from Osdental.Models.Graphql import BaseGraphQLContext
7
7
  from Osdental.Models.TokenClaims import UserTokenClaims
8
8
  from Osdental.Models.Token import AuthToken
9
9
  from Osdental.Enums.Profile import Profile
@@ -19,12 +19,11 @@ from Osdental.Models.AuditContext import AuditContext
19
19
  from Osdental.Helpers.AuditHelper import (
20
20
  _build_success_payload, _build_error_payload, _build_unexpected_payload
21
21
  )
22
-
22
+ from Osdental.Context import _ctx
23
23
 
24
24
  logger = logging.getLogger(__name__)
25
25
 
26
26
 
27
-
28
27
  def resolver(public: bool = False, action=None):
29
28
 
30
29
  def decorator(func: Callable):
@@ -95,6 +94,8 @@ def resolver(public: bool = False, action=None):
95
94
  decrypted_payload=decrypted_payload,
96
95
  )
97
96
 
97
+ _ctx.set(audit_ctx)
98
+
98
99
  # ── 6. EJECUTAR RESOLVER ──────────────────
99
100
  result = await func(obj, info, **kwargs)
100
101
 
@@ -0,0 +1,5 @@
1
+ from enum import StrEnum
2
+
3
+ class ResultType(StrEnum):
4
+ RESPONSE = "Response"
5
+ ERROR = "Error"
@@ -1,11 +1,14 @@
1
1
  import json
2
+ from typing import Optional, List, Dict, Any
2
3
  from fastapi import Request
3
- from typing import Optional, Dict, Any, Literal
4
+ from httpx import HTTPStatusError, RequestError
4
5
  from Osdental.Models.AuditContext import AuditContext
5
6
  from Osdental.Models.Response import Response
7
+ from Osdental.Models.ApiResponse import ApiResponse
6
8
  from Osdental.Exception.ControlledException import OSDException
7
9
  from Osdental.Enums.StatusCode import StatusCode
8
10
  from Osdental.Enums.AuditType import AuditType
11
+ from Osdental.Enums.ResultType import ResultType
9
12
 
10
13
  def build_request_payload(
11
14
  request: Request,
@@ -53,8 +56,8 @@ def build_request_payload(
53
56
  }
54
57
 
55
58
  def _build_final_payload(
56
- type_: Literal["RESPONSE", "ERROR"],
57
- status_code: int,
59
+ status_code: int | str,
60
+ type_: ResultType = ResultType.ERROR,
58
61
  result: Optional[Any] = None,
59
62
  error: Optional[Any] = None
60
63
  ) -> Dict[str, Any]:
@@ -73,25 +76,23 @@ def _build_final_payload(
73
76
  }
74
77
 
75
78
  def _build_success_payload(ctx: Optional[AuditContext], result: Response) -> Dict[str, Any]:
76
- base = _base_payload(ctx, AuditType.INTERNAL)
79
+ base = _base_payload(ctx)
77
80
  return base | _build_final_payload(
78
- type_="RESPONSE",
81
+ type_=ResultType.RESPONSE,
79
82
  status_code=result.status,
80
83
  result=result.data
81
84
  )
82
85
 
83
86
  def _build_error_payload(ctx: Optional[AuditContext], exc: OSDException) -> Dict[str, Any]:
84
- base = _base_payload(ctx, AuditType.INTERNAL)
87
+ base = _base_payload(ctx)
85
88
  return base | _build_final_payload(
86
- type_="ERROR",
87
89
  status_code=exc.audit_status_code,
88
90
  error=exc.error or exc.message
89
91
  )
90
92
 
91
93
  def _build_unexpected_payload(ctx: Optional[AuditContext], exc: Exception) -> Dict[str, Any]:
92
- base = _base_payload(ctx, AuditType.INTERNAL)
94
+ base = _base_payload(ctx)
93
95
  return base | _build_final_payload(
94
- type_="ERROR",
95
96
  status_code=StatusCode.INTERNAL_SERVER_ERROR,
96
97
  error=str(exc)
97
98
  )
@@ -115,4 +116,51 @@ def _base_payload(ctx: Optional[AuditContext], audit_type: AuditType = AuditType
115
116
  "httpMethod": ctx.http_method,
116
117
  "messageIn": json.dumps(ctx.variables) if ctx.variables else "*",
117
118
  "auditLog": audit_type,
118
- }
119
+ }
120
+
121
+
122
+ # ------------ FOR EXTERNAL APIS --------------------
123
+ def _build_success_http_payload(ctx: Optional[AuditContext], result: ApiResponse) -> Dict[str, Any]:
124
+ base = _base_payload(ctx, AuditType.EXTERNAL)
125
+ return base | _build_final_payload(
126
+ type_=ResultType.RESPONSE,
127
+ status_code=result.status,
128
+ result=result.data
129
+ )
130
+
131
+ def _build_error_http_payload(ctx: Optional[AuditContext], exc: HTTPStatusError) -> Dict[str, Any]:
132
+ base = _base_payload(ctx, AuditType.EXTERNAL)
133
+ return base | _build_final_payload(
134
+ status_code=exc.response.status_code,
135
+ error=exc.response.text
136
+ )
137
+
138
+ def _build_error_http_request_payload(ctx: Optional[AuditContext], exc: RequestError) -> Dict[str, Any]:
139
+ base = _base_payload(ctx, AuditType.EXTERNAL)
140
+ return base | _build_final_payload(
141
+ status_code=503,
142
+ error=str(exc)
143
+ )
144
+
145
+ def _build_unexpected_http_payload(ctx: Optional[AuditContext], exc: Exception) -> Dict[str, Any]:
146
+ base = _base_payload(ctx, AuditType.EXTERNAL)
147
+ return base | _build_final_payload(
148
+ status_code=StatusCode.INTERNAL_SERVER_ERROR,
149
+ error=str(exc)
150
+ )
151
+
152
+ def _build_success_graphql_payload(ctx: Optional[AuditContext], result: Dict[str, Any]) -> Dict[str, Any]:
153
+ base = _base_payload(ctx, AuditType.EXTERNAL)
154
+ return base | _build_final_payload(
155
+ type_=ResultType.RESPONSE,
156
+ status_code=200,
157
+ result=result
158
+ )
159
+
160
+
161
+ def _build_error_graphql_payload(ctx: Optional[AuditContext], errors: List[Dict[str, Any]]) -> Dict[str, Any]:
162
+ base = _base_payload(ctx, AuditType.EXTERNAL)
163
+ return base | _build_final_payload(
164
+ status_code=StatusCode.INTERNAL_SERVER_ERROR,
165
+ error=json.dumps(errors)
166
+ )
@@ -3,12 +3,16 @@ from typing import Optional, Dict, Any
3
3
  import httpx
4
4
  from http import HTTPMethod
5
5
  from Osdental.Decorators.Retry import rest_retry
6
- from Osdental.Http._Helpers import (
7
- audit_success, audit_http_error, audit_exception_error,
8
- audit_unknown_error, audit_graphql_error
9
- )
6
+ from Osdental.Models.ApiResponse import ApiResponse
7
+ from Osdental.Services import IAuditEmitter
10
8
  from Osdental.Exception.ControlledException import HttpClientException
11
9
  from Osdental.Enums.StatusCode import StatusCode
10
+ from Osdental.Context import _ctx
11
+ from Osdental.Helpers.AuditHelper import (
12
+ _build_success_http_payload, _build_error_http_payload,
13
+ _build_error_http_request_payload, _build_unexpected_http_payload,
14
+ _build_success_graphql_payload, _build_error_graphql_payload
15
+ )
12
16
 
13
17
  logger = logging.getLogger(__name__)
14
18
 
@@ -16,9 +20,12 @@ class APIClient:
16
20
 
17
21
  def __init__(
18
22
  self,
23
+ audit: Optional[IAuditEmitter] = None,
19
24
  timeout: Optional[httpx.Timeout] = None,
20
25
  limits: Optional[httpx.Limits] = None
21
26
  ):
27
+ self._audit = audit
28
+
22
29
  self._client = httpx.AsyncClient(
23
30
  follow_redirects=True,
24
31
  timeout=timeout or httpx.Timeout(10.0, read=20.0),
@@ -42,24 +49,37 @@ class APIClient:
42
49
  **kwargs
43
50
  )
44
51
 
52
+ audit_ctx = _ctx.get()
53
+
54
+ audit_ctx.headers = kwargs["headers"] or {}
55
+
45
56
  response.raise_for_status()
46
57
 
47
- audit_success(response, method, url, kwargs)
58
+ api_res = ApiResponse(
59
+ status=response.status_code,
60
+ data=response.text
61
+ )
62
+
63
+ await self._audit_emit.emit(
64
+ _build_success_http_payload(audit_ctx, api_res)
65
+ )
48
66
 
49
67
  return response
50
68
 
51
69
  except httpx.HTTPStatusError as exc:
52
70
 
53
- audit_http_error(exc.response, method, url, kwargs)
71
+ await self._audit_emit.emit(
72
+ _build_error_http_payload(audit_ctx, exc)
73
+ )
74
+
54
75
  raise HttpClientException(
55
76
  status_code=StatusCode.BAD_GATEWAY,
56
77
  error=exc.response.text,
57
- raw_status_code=exc.response.status_code,
58
78
  ) from exc
59
79
 
60
80
  except httpx.RequestError as exc:
61
81
 
62
- audit_exception_error(exc, method, url, kwargs)
82
+ _build_error_http_request_payload(audit_ctx, exc)
63
83
  raise HttpClientException(
64
84
  status_code=StatusCode.GATEWAY_TIMEOUT,
65
85
  error=str(exc),
@@ -67,7 +87,7 @@ class APIClient:
67
87
 
68
88
  except Exception as exc:
69
89
 
70
- audit_unknown_error(exc, method, url, kwargs)
90
+ _build_unexpected_http_payload(audit_ctx, exc)
71
91
 
72
92
  raise
73
93
 
@@ -119,13 +139,20 @@ class APIClient:
119
139
 
120
140
  data = response.json()
121
141
 
122
- if "errors" in data:
142
+ audit_ctx = _ctx.get()
143
+
144
+ audit_ctx.headers = headers or {}
123
145
 
146
+ _build_success_graphql_payload(audit_ctx, data)
147
+
148
+ if "errors" in data:
149
+
150
+ errors = data['errors']
124
151
  logger.error(
125
- f"GraphQL error: {data['errors']}"
152
+ f"GraphQL error: {errors}"
126
153
  )
127
154
 
128
- audit_graphql_error(data["errors"], url, payload)
155
+ _build_error_graphql_payload(audit_ctx, errors)
129
156
  raise HttpClientException(
130
157
  message="GraphQL execution failed",
131
158
  error=str(data["errors"])
@@ -1,196 +0,0 @@
1
- import inspect
2
- import logging
3
- import json
4
- from graphql.pyutils import is_awaitable
5
- from ariadne.types import Extension
6
- from Osdental.Encryptor.Aes import AES
7
- from Osdental.Graphql._Helpers._TenantPolicy import TenantPolicy
8
- from Osdental.Rest.Context.RequestContext import (
9
- current_context,
10
- RequestContext
11
- )
12
- from Osdental.Models.Response import Response
13
- from Osdental.Graphql.Models import BaseGraphQLContext
14
- from Osdental.Constants.Constant import Constant
15
-
16
- logger = logging.getLogger(__name__)
17
-
18
- class AuditExtension(Extension):
19
-
20
- def request_started(self, context):
21
- request = context.request
22
- self._ctx_token = current_context.set(
23
- RequestContext(
24
- request=request,
25
- request_id=request.headers.get("x-request-id"),
26
- user=None
27
- )
28
- )
29
-
30
- self.errors = None
31
- self.request_payload = None
32
- self.result = None
33
-
34
- def _should_skip(self, body) -> bool:
35
- query = body.get("query", "") or ""
36
- return "__schema" in query or "__type(" in query
37
-
38
- async def resolve(self, next_, root, info, **kwargs):
39
-
40
- context: BaseGraphQLContext = info.context
41
- request = context.request
42
-
43
- # cache body
44
- context._cached_body = (
45
- getattr(context, "_cached_body", None) or await request.json()
46
- )
47
- body = context._cached_body
48
-
49
- # skip introspection
50
- if self._should_skip(body):
51
- result = next_(root, info, **kwargs)
52
- if is_awaitable(result):
53
- result = await result
54
- return result
55
-
56
- is_root = root is None
57
-
58
- # SOLO ROOT
59
- if is_root:
60
-
61
- # identificar resolver público
62
- resolver_fn = inspect.unwrap(next_)
63
- is_public = getattr(resolver_fn, "_is_public", False)
64
-
65
- # inicializar auth UNA SOLA VEZ
66
- if not hasattr(context, "_auth_initialized"):
67
- context._auth_initialized = True
68
-
69
- headers = request.headers
70
- container = context.container
71
-
72
- original_token = None
73
- context.aes_auth = None
74
- if not is_public and headers.get("authorization"):
75
- token_service = container.token_service
76
- aes_auth = request.app.state.aes_auth
77
- aes_user = request.app.state.aes_user
78
-
79
- try:
80
- original_token = await token_service.authenticate(headers, aes_user)
81
- except Exception:
82
- original_token = None
83
-
84
- context.aes_auth = aes_auth
85
-
86
- # VALIDACIÓN SOLO ROOT
87
- if not is_public and not original_token:
88
- raise ValueError("Authorization required")
89
-
90
- # payload
91
- variables = body.get("variables") or {}
92
-
93
- encrypted_payload = (
94
- kwargs.get("data")
95
- or variables.get("data")
96
- )
97
-
98
- decrypted_payload = None
99
-
100
- if not is_public and encrypted_payload is not None:
101
-
102
- if not isinstance(encrypted_payload, str):
103
- raise ValueError("Encrypted payload must be a string")
104
-
105
- if not context.aes_auth:
106
- raise ValueError("Missing AES configuration")
107
-
108
- try:
109
- decrypted = AES.decrypt(context.aes_auth, encrypted_payload)
110
- except Exception:
111
- raise ValueError("Invalid encrypted payload")
112
-
113
- if isinstance(decrypted, dict):
114
- decrypted_payload = decrypted
115
-
116
- elif isinstance(decrypted, str):
117
- try:
118
- decrypted_payload = json.loads(decrypted)
119
- except Exception:
120
- raise ValueError("Decrypted payload is not valid JSON")
121
-
122
- else:
123
- raise ValueError("Unsupported decrypted payload type")
124
-
125
- if decrypted_payload is not None:
126
- kwargs["data"] = decrypted_payload
127
-
128
- if not is_public:
129
- token = TenantPolicy.resolve(
130
- token=original_token,
131
- headers=request.headers,
132
- decrypted_payload=decrypted_payload,
133
- operation_type=info.operation.operation.value
134
- )
135
- context.token = token
136
-
137
- # auditoría request payload
138
- if not self.request_payload:
139
- token = context.token
140
-
141
- self.request = request
142
- self.request_payload = {
143
- "operation_type": info.operation.operation.value,
144
- "operation_name": body.get("operationName", "UnknownOperation"),
145
- "query": body.get("query"),
146
- "variables": decrypted_payload,
147
- "user": token.user_full_name if token else "Public"
148
- }
149
-
150
- # SIEMPRE ejecutar resolver (root + fields)
151
- result = next_(root, info, **kwargs)
152
-
153
- if is_awaitable(result):
154
- result = await result
155
-
156
- # SOLO ROOT captura resultado final
157
- if is_root and self.result is None:
158
- self.result = result
159
-
160
- return result
161
-
162
- def has_errors(self, errors, context):
163
- self.errors = errors
164
-
165
- def request_finished(self, context):
166
-
167
- if not self.request_payload:
168
- return
169
-
170
- query = self.request_payload.get("query", "")
171
-
172
- if self._should_skip({"query": query}):
173
- return
174
-
175
- if self.result is None:
176
- return
177
-
178
- decrypted_key = context.aes_auth
179
-
180
- if isinstance(self.result, Response):
181
- decrypted_key = self.result.key or decrypted_key
182
-
183
- dispatcher = context.request.app.state.audit_dispatcher
184
-
185
- try:
186
- dispatcher.dispatch(
187
- request=self.request,
188
- request_payload=self.request_payload,
189
- result=self.result,
190
- metadata={
191
- "decrypted_key": decrypted_key
192
- },
193
- audit_type=Constant.MESSAGE_LOG_INTERNAL
194
- )
195
- except Exception as e:
196
- logger.warning(f"[AUDIT ERROR]: {str(e)}")
@@ -1,8 +0,0 @@
1
- from Osdental.Exception.ControlledException import OSDException
2
-
3
- class AESKeyNotFound(OSDException):
4
- def __init__(
5
- self,
6
- error: str = None
7
- ):
8
- super().__init__(error=error)
@@ -1,67 +0,0 @@
1
- import json
2
- from typing import Dict, Literal, Any, Optional
3
-
4
- class AuditHelper:
5
-
6
- @staticmethod
7
- def build_request_payload(
8
- request,
9
- payload: Dict[str, Any],
10
- audit_type: str
11
- ) -> Dict[str, Any]:
12
-
13
- default_value = "*"
14
-
15
- user_ip = request.headers.get("X-Forwarded-For")
16
- if user_ip:
17
- user_ip = user_ip.split(",")[0]
18
- else:
19
- user_ip = getattr(request.client, "host", "*")
20
-
21
- SAFE_HEADERS = {
22
- "user-agent",
23
- "host",
24
- "origin",
25
- "referer",
26
- "content-type"
27
- }
28
-
29
- headers = {k: v for k, v in request.headers.items() if k.lower() in SAFE_HEADERS}
30
-
31
- return {
32
- "idMessageLog": request.headers.get("Idmessagelog"),
33
- "environment": payload.get("env"),
34
- "header": json.dumps(headers),
35
- "microServiceUrl": str(request.url),
36
- "microServiceName": payload.get("ms_name"),
37
- "microServiceVersion": payload.get("ms_version"),
38
- "serviceName": payload.get("operation_name"),
39
- "machineNameUser": request.headers.get("Machinenameuser", default_value),
40
- "ipUser": user_ip or default_value,
41
- "userName": payload.get("user"),
42
- "localitation": default_value,
43
- "httpMethod": request.method,
44
- "messageIn": json.dumps(payload) if payload else default_value,
45
- "auditLog": audit_type,
46
- }
47
-
48
- @staticmethod
49
- def build_final_payload(
50
- _type: Literal["RESPONSE", "ERROR"],
51
- status_code: int,
52
- result: Optional[Any] = None,
53
- error: Optional[Any] = None
54
- ) -> Dict[str, Any]:
55
-
56
- result = "*" if result is None else result
57
- error = "*" if error is None else error
58
-
59
- if isinstance(result, (dict, list)):
60
- result = json.dumps(result)
61
-
62
- return {
63
- "type": _type,
64
- "httpResponseCode": status_code,
65
- "messageOut": result,
66
- "errorProducer": error
67
- }
@@ -1,11 +0,0 @@
1
- from starlette.datastructures import Headers
2
-
3
- class ExtractAuthToken:
4
-
5
- @staticmethod
6
- def get_auth_token(headers: Headers) -> str:
7
- authorization = headers.get("authorization")
8
- if not authorization or not authorization.startswith("Bearer "):
9
- raise ValueError("Missing Bearer token")
10
-
11
- return authorization.split(" ")[1]
@@ -1,46 +0,0 @@
1
- from typing import Dict, Any
2
- from uuid import UUID
3
- from graphql import OperationType
4
- from starlette.datastructures import Headers
5
- from Osdental.Encryptor.Aes import AES
6
- from Osdental.Models.Token import AuthToken
7
- from Osdental.Enums.Profile import Profile
8
-
9
- class TenantPolicy:
10
-
11
- @staticmethod
12
- def resolve(
13
- token: AuthToken,
14
- headers: Headers,
15
- decrypted_payload: Dict[str, Any],
16
- operation_type: str
17
- ) -> AuthToken:
18
-
19
- final_id = None
20
- dynamic_client_id = headers.get("dynamicClientId", None)
21
- external_enterprise_req = (
22
- decrypted_payload.get("idExternalEnterprise")
23
- if decrypted_payload else None
24
- )
25
-
26
- if external_enterprise_req:
27
- final_id = external_enterprise_req
28
-
29
-
30
- elif token.abbreviation.startswith(
31
- (Profile.MARKETING, Profile.SUPER_ADMIN, Profile.ADMIN_OSD)
32
- ) and dynamic_client_id:
33
- decrypted_mk_id = AES.decrypt(token.aes_key_auth, dynamic_client_id)
34
- final_id = decrypted_mk_id
35
- token.mk_id_external_enterprise = decrypted_mk_id
36
-
37
- elif (
38
- token.abbreviation.startswith((Profile.SUPER_ADMIN, Profile.ADMIN_OSD))
39
- and operation_type == OperationType.QUERY.value and not dynamic_client_id
40
- ):
41
- final_id = str(UUID(int=0))
42
-
43
- if final_id:
44
- token.id_external_enterprise = final_id
45
-
46
- return token