Encryptors 2.49__tar.gz → 2.50__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 (104) hide show
  1. {encryptors-2.49 → encryptors-2.50}/PKG-INFO +2 -1
  2. {encryptors-2.49 → encryptors-2.50}/setup.py +3 -2
  3. {encryptors-2.49 → encryptors-2.50}/src/Encryptors.egg-info/PKG-INFO +2 -1
  4. {encryptors-2.49 → encryptors-2.50}/src/Encryptors.egg-info/SOURCES.txt +5 -1
  5. {encryptors-2.49 → encryptors-2.50}/src/Encryptors.egg-info/requires.txt +1 -0
  6. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Cache/Redis.py +86 -28
  7. encryptors-2.50/src/Osdental/Decorators/SecureResolver.py +134 -0
  8. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Exception/ControlledException.py +18 -18
  9. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Helpers/AzureClassifier.py +4 -0
  10. encryptors-2.50/src/Osdental/Helpers/JwtAuthTokenService.py +147 -0
  11. encryptors-2.50/src/Osdental/Helpers/JwtTokenHelper.py +183 -0
  12. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Helpers/Resilience.py +96 -29
  13. encryptors-2.50/src/Osdental/Helpers/_AuthTokenProcessor.py +99 -0
  14. encryptors-2.50/src/Osdental/Helpers/_Ports.py +47 -0
  15. encryptors-2.50/src/Osdental/Messaging/AzureServiceBus.py +146 -0
  16. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Models/ApiResponse.py +2 -2
  17. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Models/Response.py +2 -2
  18. encryptors-2.50/src/Osdental/Models/TokenClaims.py +47 -0
  19. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Enums/Profile.py +2 -1
  20. encryptors-2.50/src/Osdental/Shared/Enums/StatusCode.py +40 -0
  21. encryptors-2.50/src/Osdental/Shared/Utils/RsaUtils.py +56 -0
  22. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Storage/AzureBlobStorage.py +19 -16
  23. encryptors-2.49/src/Osdental/Decorators/SecureResolver.py +0 -59
  24. encryptors-2.49/src/Osdental/Helpers/_Ports.py +0 -11
  25. encryptors-2.49/src/Osdental/Messaging/AzureServiceBus.py +0 -70
  26. encryptors-2.49/src/Osdental/Shared/Enums/Code.py +0 -26
  27. encryptors-2.49/src/Osdental/Shared/Utils/RsaUtils.py +0 -30
  28. {encryptors-2.49 → encryptors-2.50}/README.md +0 -0
  29. {encryptors-2.49 → encryptors-2.50}/setup.cfg +0 -0
  30. {encryptors-2.49 → encryptors-2.50}/src/Encryptors.egg-info/dependency_links.txt +0 -0
  31. {encryptors-2.49 → encryptors-2.50}/src/Encryptors.egg-info/entry_points.txt +0 -0
  32. {encryptors-2.49 → encryptors-2.50}/src/Encryptors.egg-info/top_level.txt +0 -0
  33. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Cache/__init__.py +0 -0
  34. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Cli/__init__.py +0 -0
  35. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Database/BaseRepository.py +0 -0
  36. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Database/Connection.py +0 -0
  37. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Database/__init__.py +0 -0
  38. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Decorators/Grpc.py +0 -0
  39. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Decorators/PublicResolver.py +0 -0
  40. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Decorators/Retry.py +0 -0
  41. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Decorators/SqlDataNormalizer.py +0 -0
  42. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Decorators/__init__.py +0 -0
  43. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Encryptor/Aes.py +0 -0
  44. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Encryptor/Argon2.py +0 -0
  45. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Encryptor/Bcrypt.py +0 -0
  46. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Encryptor/Jwt.py +0 -0
  47. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Encryptor/Rsa.py +0 -0
  48. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Encryptor/Sha512.py +0 -0
  49. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Encryptor/__init__.py +0 -0
  50. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Exception/__init__.py +0 -0
  51. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Graphql/Extensions/AuditExtension.py +0 -0
  52. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Graphql/Extensions/__init__.py +0 -0
  53. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Graphql/Models/__init__.py +0 -0
  54. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Graphql/_Exceptions/__init__.py +0 -0
  55. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Graphql/_Helpers/_AuditHelper.py +0 -0
  56. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py +0 -0
  57. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Graphql/_Helpers/_TenantPolicy.py +0 -0
  58. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Graphql/_Helpers/_TokenService.py +0 -0
  59. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Graphql/_Helpers/__init__.py +0 -0
  60. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Graphql/__init__.py +0 -0
  61. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Helpers/AuditDispatcher.py +0 -0
  62. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Helpers/GrpcConnection.py +0 -0
  63. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Helpers/KeyVaultService.py +0 -0
  64. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Helpers/ResponseDecryptor.py +0 -0
  65. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Helpers/WebsocketClient.py +0 -0
  66. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Helpers/__init__.py +0 -0
  67. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Http/APIClient.py +0 -0
  68. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Http/_Helpers.py +0 -0
  69. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Http/__init__.py +0 -0
  70. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Messaging/Kafka.py +0 -0
  71. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Messaging/RabbitMQ.py +0 -0
  72. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Messaging/__init__.py +0 -0
  73. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Models/AuditConfig.py +0 -0
  74. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Models/Notification.py +0 -0
  75. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Models/Token.py +0 -0
  76. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Models/_Audit.py +0 -0
  77. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Models/__init__.py +0 -0
  78. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Rest/Context/RequestContext.py +0 -0
  79. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Rest/Context/__init__.py +0 -0
  80. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Rest/Middlewares/RequestContextMiddleware.py +0 -0
  81. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Rest/Middlewares/__init__.py +0 -0
  82. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Rest/__init__.py +0 -0
  83. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Enums/Constant.py +0 -0
  84. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Enums/FileType.py +0 -0
  85. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Enums/GrahpqlOperation.py +0 -0
  86. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Enums/Message.py +0 -0
  87. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Enums/__init__.py +0 -0
  88. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Logger.py +0 -0
  89. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Utils/CaseConverter.py +0 -0
  90. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Utils/CodeGenerator.py +0 -0
  91. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Utils/DataNormalizer.py +0 -0
  92. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Utils/DataUtils.py +0 -0
  93. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Utils/DateUtils.py +0 -0
  94. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Utils/FileMetaData.py +0 -0
  95. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Utils/HashValidator.py +0 -0
  96. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Utils/Mapper.py +0 -0
  97. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Utils/PasswordGenerator.py +0 -0
  98. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Utils/QueryGenerator.py +0 -0
  99. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Utils/TextProcessor.py +0 -0
  100. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/Utils/__init__.py +0 -0
  101. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Shared/__init__.py +0 -0
  102. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Storage/S3Storage.py +0 -0
  103. {encryptors-2.49 → encryptors-2.50}/src/Osdental/Storage/__init__.py +0 -0
  104. {encryptors-2.49 → encryptors-2.50}/src/Osdental/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Encryptors
3
- Version: 2.49
3
+ Version: 2.50
4
4
  Summary: End-to-end algorithm library
5
5
  Author: OSDental LLC
6
6
  Author-email: support@osdental.ai
@@ -57,6 +57,7 @@ Requires-Dist: bcrypt==4.3.0
57
57
  Requires-Dist: azure-monitor-opentelemetry==1.8.1
58
58
  Requires-Dist: uvicorn==0.37.0
59
59
  Requires-Dist: gunicorn==23.0.0
60
+ Requires-Dist: jwcrypto==1.5.7
60
61
  Dynamic: author
61
62
  Dynamic: author-email
62
63
  Dynamic: classifier
@@ -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.49",
5
+ version="2.50",
6
6
  author="OSDental LLC",
7
7
  author_email="support@osdental.ai",
8
8
  description="End-to-end algorithm library",
@@ -64,7 +64,8 @@ setup(
64
64
  "bcrypt==4.3.0",
65
65
  "azure-monitor-opentelemetry==1.8.1",
66
66
  "uvicorn==0.37.0",
67
- "gunicorn==23.0.0"
67
+ "gunicorn==23.0.0",
68
+ "jwcrypto==1.5.7",
68
69
  ],
69
70
  entry_points={
70
71
  'console_scripts': [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Encryptors
3
- Version: 2.49
3
+ Version: 2.50
4
4
  Summary: End-to-end algorithm library
5
5
  Author: OSDental LLC
6
6
  Author-email: support@osdental.ai
@@ -57,6 +57,7 @@ Requires-Dist: bcrypt==4.3.0
57
57
  Requires-Dist: azure-monitor-opentelemetry==1.8.1
58
58
  Requires-Dist: uvicorn==0.37.0
59
59
  Requires-Dist: gunicorn==23.0.0
60
+ Requires-Dist: jwcrypto==1.5.7
60
61
  Dynamic: author
61
62
  Dynamic: author-email
62
63
  Dynamic: classifier
@@ -41,10 +41,13 @@ src/Osdental/Graphql/_Helpers/__init__.py
41
41
  src/Osdental/Helpers/AuditDispatcher.py
42
42
  src/Osdental/Helpers/AzureClassifier.py
43
43
  src/Osdental/Helpers/GrpcConnection.py
44
+ src/Osdental/Helpers/JwtAuthTokenService.py
45
+ src/Osdental/Helpers/JwtTokenHelper.py
44
46
  src/Osdental/Helpers/KeyVaultService.py
45
47
  src/Osdental/Helpers/Resilience.py
46
48
  src/Osdental/Helpers/ResponseDecryptor.py
47
49
  src/Osdental/Helpers/WebsocketClient.py
50
+ src/Osdental/Helpers/_AuthTokenProcessor.py
48
51
  src/Osdental/Helpers/_Ports.py
49
52
  src/Osdental/Helpers/__init__.py
50
53
  src/Osdental/Http/APIClient.py
@@ -59,6 +62,7 @@ src/Osdental/Models/AuditConfig.py
59
62
  src/Osdental/Models/Notification.py
60
63
  src/Osdental/Models/Response.py
61
64
  src/Osdental/Models/Token.py
65
+ src/Osdental/Models/TokenClaims.py
62
66
  src/Osdental/Models/_Audit.py
63
67
  src/Osdental/Models/__init__.py
64
68
  src/Osdental/Rest/__init__.py
@@ -68,12 +72,12 @@ src/Osdental/Rest/Middlewares/RequestContextMiddleware.py
68
72
  src/Osdental/Rest/Middlewares/__init__.py
69
73
  src/Osdental/Shared/Logger.py
70
74
  src/Osdental/Shared/__init__.py
71
- src/Osdental/Shared/Enums/Code.py
72
75
  src/Osdental/Shared/Enums/Constant.py
73
76
  src/Osdental/Shared/Enums/FileType.py
74
77
  src/Osdental/Shared/Enums/GrahpqlOperation.py
75
78
  src/Osdental/Shared/Enums/Message.py
76
79
  src/Osdental/Shared/Enums/Profile.py
80
+ src/Osdental/Shared/Enums/StatusCode.py
77
81
  src/Osdental/Shared/Enums/__init__.py
78
82
  src/Osdental/Shared/Utils/CaseConverter.py
79
83
  src/Osdental/Shared/Utils/CodeGenerator.py
@@ -46,3 +46,4 @@ bcrypt==4.3.0
46
46
  azure-monitor-opentelemetry==1.8.1
47
47
  uvicorn==0.37.0
48
48
  gunicorn==23.0.0
49
+ jwcrypto==1.5.7
@@ -18,12 +18,6 @@ class RedisCacheAsync(ICacheService):
18
18
  _instances: Dict[str, "RedisCacheAsync"] = {}
19
19
  _lock = asyncio.Lock()
20
20
 
21
- _policy = AzureResiliencePolicy(
22
- "redis-cache",
23
- max_attempts=3,
24
- base_delay=0.5,
25
- failure_threshold=5,
26
- )
27
21
 
28
22
  def __new__(cls, redis_host: str, redis_port: int = 6380):
29
23
 
@@ -48,7 +42,15 @@ class RedisCacheAsync(ICacheService):
48
42
 
49
43
  self.client = None
50
44
 
45
+ self._policy = AzureResiliencePolicy(
46
+ "redis-cache",
47
+ max_attempts=3,
48
+ base_delay=0.5,
49
+ failure_threshold=5,
50
+ )
51
+
51
52
  self._initialized = True
53
+ self._reconnect_lock = asyncio.Lock()
52
54
 
53
55
 
54
56
  async def _call(self, func, *args, **kwargs):
@@ -68,8 +70,24 @@ class RedisCacheAsync(ICacheService):
68
70
  try:
69
71
  return await self._policy.execute(_inner)
70
72
  except AzureTransientError as exc:
71
- logger.error("redis.exhausted error=%s", exc)
72
- raise RedisException(message=Message.UNEXPECTED_ERROR_MSG, error=str(exc))
73
+ try:
74
+ await self._reconnect()
75
+
76
+ except Exception as reconnect_exc:
77
+ logger.error(
78
+ "redis.reconnect.failed error=%s",
79
+ reconnect_exc
80
+ )
81
+
82
+ logger.error(
83
+ "redis.exhausted error=%s",
84
+ exc
85
+ )
86
+
87
+ raise RedisException(
88
+ message=Message.UNEXPECTED_ERROR_MSG,
89
+ error=str(exc)
90
+ )
73
91
  except Exception as exc:
74
92
  raise RedisException(message=Message.UNEXPECTED_ERROR_MSG, error=str(exc))
75
93
 
@@ -100,7 +118,7 @@ class RedisCacheAsync(ICacheService):
100
118
  retry_on_timeout=True,
101
119
  socket_connect_timeout=5,
102
120
  socket_timeout=5,
103
- max_connections=50,
121
+ max_connections=100,
104
122
  socket_keepalive=True
105
123
  )
106
124
 
@@ -135,11 +153,10 @@ class RedisCacheAsync(ICacheService):
135
153
  ):
136
154
 
137
155
  await self._call(
138
- self.client.set(
139
- key,
140
- json.dumps(value),
141
- ex=ttl
142
- )
156
+ self.client.set,
157
+ key,
158
+ json.dumps(value),
159
+ ex=ttl
143
160
  )
144
161
 
145
162
 
@@ -151,11 +168,10 @@ class RedisCacheAsync(ICacheService):
151
168
  ):
152
169
 
153
170
  await self._call(
154
- self.client.set(
155
- key,
156
- value,
157
- ex=ttl
158
- )
171
+ self.client.set,
172
+ key,
173
+ value,
174
+ ex=ttl
159
175
  )
160
176
 
161
177
  async def get_dict(
@@ -164,10 +180,19 @@ class RedisCacheAsync(ICacheService):
164
180
  ) -> Optional[Dict[str, Any]]:
165
181
 
166
182
  value = await self._call(
167
- self.client.get(key)
183
+ self.client.get,
184
+ key
168
185
  )
169
186
 
170
- return json.loads(value) if value else None
187
+ try:
188
+ return json.loads(value) if value else None
189
+
190
+ except json.JSONDecodeError:
191
+ logger.error(
192
+ "redis.invalid_json key=%s",
193
+ key
194
+ )
195
+ return None
171
196
 
172
197
 
173
198
  async def get_str(
@@ -176,14 +201,16 @@ class RedisCacheAsync(ICacheService):
176
201
  ) -> Optional[str]:
177
202
 
178
203
  return await self._call(
179
- self.client.get(key)
204
+ self.client.get,
205
+ key
180
206
  )
181
207
 
182
208
 
183
209
  async def delete(self, key: str) -> bool:
184
210
 
185
211
  result = await self._call(
186
- self.client.delete(key)
212
+ self.client.delete,
213
+ key
187
214
  )
188
215
 
189
216
  return result > 0
@@ -192,7 +219,8 @@ class RedisCacheAsync(ICacheService):
192
219
  async def exists(self, key: str) -> bool:
193
220
 
194
221
  result = await self._call(
195
- self.client.exists(key)
222
+ self.client.exists,
223
+ key
196
224
  )
197
225
 
198
226
  return result > 0
@@ -201,14 +229,14 @@ class RedisCacheAsync(ICacheService):
201
229
  async def flush(self):
202
230
 
203
231
  await self._call(
204
- self.client.flushdb()
232
+ self.client.flushdb
205
233
  )
206
234
 
207
235
 
208
236
  async def flush_all(self):
209
237
 
210
238
  await self._call(
211
- self.client.flushall()
239
+ self.client.flushall
212
240
  )
213
241
 
214
242
 
@@ -218,7 +246,8 @@ class RedisCacheAsync(ICacheService):
218
246
  ) -> List[Optional[Any]]:
219
247
 
220
248
  values = await self._call(
221
- self.client.mget(keys)
249
+ self.client.mget,
250
+ keys
222
251
  )
223
252
 
224
253
  return [
@@ -236,7 +265,10 @@ class RedisCacheAsync(ICacheService):
236
265
  match=f"{prefix}*"
237
266
  ):
238
267
 
239
- await self._call(self.client.delete(key))
268
+ await self._call(
269
+ self.client.delete,
270
+ key
271
+ )
240
272
 
241
273
 
242
274
  async def _scan_keys(self, match: str = "*"):
@@ -246,6 +278,32 @@ class RedisCacheAsync(ICacheService):
246
278
  ):
247
279
  yield key
248
280
 
281
+
282
+ async def _reconnect(self):
283
+
284
+ async with self._reconnect_lock:
285
+
286
+ try:
287
+
288
+ if self.client:
289
+ await self.client.ping()
290
+ return
291
+
292
+ except Exception:
293
+ pass
294
+
295
+ try:
296
+
297
+ if self.client:
298
+ await self.client.aclose()
299
+
300
+ except Exception:
301
+ pass
302
+
303
+ self.client = None
304
+
305
+ await self.connect()
306
+
249
307
  async def close(self):
250
308
 
251
309
  try:
@@ -0,0 +1,134 @@
1
+ from functools import wraps
2
+ from typing import Callable
3
+ from graphql import GraphQLResolveInfo
4
+ from Osdental.Models.Response import Response
5
+ from Osdental.Shared.Enums.Profile import Profile
6
+ from Osdental.Exception.ControlledException import (
7
+ OSDException, AccessDeniedException
8
+ )
9
+ from Osdental.Graphql.Models import BaseGraphQLContext
10
+ from Osdental.Shared.Logger import logger
11
+ from Osdental.Helpers._Ports import IAuthTokenService
12
+ from Osdental.Helpers._AuthTokenProcessor import (
13
+ extract_bearer_token, build_auth_token, decrypt_and_parse_payload
14
+ )
15
+ from Osdental.Models.TokenClaims import UserTokenClaims
16
+ from Osdental.Shared.Enums.StatusCode import StatusCode
17
+
18
+
19
+ def __test(dispatcher, request, request_payload, result, decrypted_key):
20
+ dispatcher.dispatch(
21
+ request=request,
22
+ request_payload=request_payload,
23
+ result=result,
24
+ metadata={
25
+ "decrypted_key": decrypted_key
26
+ },
27
+ audit_type="MESSAGE_LOG_INTERNAL"
28
+ )
29
+
30
+ def resolver(public: bool = False, action=None):
31
+
32
+ def decorator(func: Callable):
33
+ func._is_public = public
34
+
35
+ @wraps(func)
36
+ async def wrapper(obj, info: GraphQLResolveInfo, **kwargs):
37
+ try:
38
+ context: BaseGraphQLContext = info.context
39
+ request = context.request
40
+
41
+ # ── 1. AUTENTICACIÓN ──────────────────────────────
42
+ if not public:
43
+
44
+ headers = request.headers
45
+ container = context.container
46
+ token_service: IAuthTokenService = container.auth_token_service
47
+ user_token = extract_bearer_token(headers)
48
+
49
+ claims: UserTokenClaims = token_service.validate_internal_access_token(
50
+ user_token
51
+ )
52
+
53
+ # ── 2. DECRYPT DEL PAYLOAD ────────────────────────
54
+ body = await request.json()
55
+ variables = body.get("variables") or {}
56
+ encrypted_payload = kwargs.get("data") or variables.get("data")
57
+ decrypted_payload = None
58
+
59
+ if not public and encrypted_payload is not None:
60
+ decrypted_payload = decrypt_and_parse_payload(
61
+ aes_key_auth=claims.aes_key_auth,
62
+ encrypted_payload=encrypted_payload
63
+ )
64
+
65
+ kwargs["data"] = decrypted_payload
66
+
67
+ # ── 3. TENANT POLICY ──────────────────────────────
68
+ if not public:
69
+
70
+ token = build_auth_token(
71
+ claims=claims,
72
+ headers=request.headers,
73
+ decrypted_payload=decrypted_payload,
74
+ operation_type=info.operation.operation.value
75
+ )
76
+
77
+ # ── 4. CONTROL DE ACCESO POR ROL ─────────────────
78
+ if not public and action:
79
+ if Profile(token.abbreviation) not in action.allowed_roles:
80
+ raise AccessDeniedException(
81
+ status_code=StatusCode.BUSINESS_RULE_WARNING,
82
+ error="User not allowed to perform this action"
83
+ )
84
+
85
+ # ── 5. EJECUTAR RESOLVER ──────────────────────────
86
+ result = await func(obj, info, **kwargs)
87
+
88
+ if not isinstance(result, Response):
89
+ raise TypeError("Resolver must return a Response instance")
90
+
91
+ dispatcher = request.app.state.audit_dispatcher
92
+
93
+ request_payload = {
94
+ "operation_type": info.operation.operation.value,
95
+ "operation_name": body.get("operationName", "UnknownOperation"),
96
+ "query": body.get("query"),
97
+ "variables": decrypted_payload,
98
+ "user": (
99
+ token.user_full_name
100
+ if token
101
+ else "Public"
102
+ )
103
+ }
104
+
105
+ if isinstance(result, Response):
106
+ decrypted_key = result.key or claims.aes_key_auth
107
+
108
+ __test(
109
+ dispatcher=dispatcher,
110
+ request_payload=request_payload,
111
+ request=request,
112
+ decrypted_key=decrypted_key,
113
+ result=result
114
+ )
115
+
116
+ return result
117
+
118
+ except OSDException as e:
119
+ logger.warning(f"Business error: {str(e)}")
120
+ return Response(
121
+ status=e.status_code,
122
+ message=e.message,
123
+ error=e.error
124
+ )
125
+ except Exception as e:
126
+ logger.exception(f"Unexpected error: {str(e)}")
127
+ return Response(
128
+ status=StatusCode.INTERNAL_SERVER_ERROR,
129
+ message="Could not process request.",
130
+ error=str(e)
131
+ )
132
+
133
+ return wrapper
134
+ return decorator
@@ -1,5 +1,5 @@
1
1
  from typing import Any
2
- from Osdental.Shared.Enums.Code import Code
2
+ from Osdental.Shared.Enums.StatusCode import StatusCode
3
3
  from Osdental.Shared.Enums.Message import Message
4
4
 
5
5
  class OSDException(Exception):
@@ -8,7 +8,7 @@ class OSDException(Exception):
8
8
  self,
9
9
  message: str = Message.UNEXPECTED_ERROR_MSG,
10
10
  error: str = None,
11
- status_code: Any = Code.APP_ERROR_CODE
11
+ status_code: Any = StatusCode.INTERNAL_SERVER_ERROR,
12
12
  ):
13
13
  super().__init__(error)
14
14
  self.message = message
@@ -25,7 +25,7 @@ class UnauthorizedException(OSDException):
25
25
  self,
26
26
  message: str = Message.PORTAL_ACCESS_RESTRICTED_MSG,
27
27
  error: str = None,
28
- status_code: str = Code.UNAUTHORIZATED_CODE
28
+ status_code: str = StatusCode.UNAUTHORIZED,
29
29
  ):
30
30
  super().__init__(message=message, error=error, status_code=status_code)
31
31
 
@@ -34,7 +34,7 @@ class AccessDeniedException(OSDException):
34
34
  self,
35
35
  message: str = Message.ACCESS_DENIED_MSG,
36
36
  error: str = None,
37
- status_code: str = Code.INVALID_REQUEST_PARAMS_CODE
37
+ status_code: str = StatusCode.BUSINESS_RULE_WARNING,
38
38
  ):
39
39
  super().__init__(message=message, error=error, status_code=status_code)
40
40
 
@@ -43,7 +43,7 @@ class RequestDataException(OSDException):
43
43
  self,
44
44
  message: str = Message.INVALID_REQUEST_PARAMS_MSG,
45
45
  error: str = None,
46
- status_code: str = Code.INVALID_REQUEST_PARAMS_CODE
46
+ status_code: str = StatusCode.VALIDATION_WARNING,
47
47
  ):
48
48
  super().__init__(message=message, error=error, status_code=status_code)
49
49
 
@@ -52,7 +52,7 @@ class DatabaseConnectionException(OSDException):
52
52
  self,
53
53
  message: str = Message.INVALID_FORMAT_MSG,
54
54
  error: str = None,
55
- status_code: str = Code.DATABASE_CONNECTION_ERROR_CODE
55
+ status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
56
56
  ):
57
57
  super().__init__(message=message, error=error, status_code=status_code)
58
58
 
@@ -61,7 +61,7 @@ class DatabaseException(OSDException):
61
61
  self,
62
62
  message: str = Message.UNEXPECTED_ERROR_MSG,
63
63
  error: str = None,
64
- status_code: str = Code.DATABASE_ERROR_CODE
64
+ status_code: str = StatusCode.DATA_NOT_FOUND_WARNING,
65
65
  ):
66
66
  super().__init__(message=message, error=error, status_code=status_code)
67
67
 
@@ -70,7 +70,7 @@ class RSAEncryptException(OSDException):
70
70
  self,
71
71
  message: str = Message.UNEXPECTED_ERROR_MSG,
72
72
  error: str = None,
73
- status_code: str = Code.RSA_ERROR_CODE
73
+ status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
74
74
  ):
75
75
  super().__init__(message=message, error=error, status_code=status_code)
76
76
 
@@ -79,7 +79,7 @@ class AESEncryptException(OSDException):
79
79
  self,
80
80
  message: str = Message.UNEXPECTED_ERROR_MSG,
81
81
  error: str = None,
82
- status_code: str = Code.AES_ERROR_CODE
82
+ status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
83
83
  ):
84
84
  super().__init__(message=message, error=error, status_code=status_code)
85
85
 
@@ -88,7 +88,7 @@ class JWTokenException(OSDException):
88
88
  self,
89
89
  message: str = Message.UNEXPECTED_ERROR_MSG,
90
90
  error: str = None,
91
- status_code: str = Code.JWT_ERROR_CODE
91
+ status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
92
92
  ):
93
93
  super().__init__(message=message, error=error, status_code=status_code)
94
94
 
@@ -97,7 +97,7 @@ class AzureException(OSDException):
97
97
  self,
98
98
  message: str = Message.UNEXPECTED_ERROR_MSG,
99
99
  error: str = None,
100
- status_code: str = Code.AZURE_ERROR_CODE
100
+ status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
101
101
  ):
102
102
  super().__init__(message=message, error=error, status_code=status_code)
103
103
 
@@ -106,7 +106,7 @@ class RedisException(OSDException):
106
106
  self,
107
107
  message: str = Message.UNEXPECTED_ERROR_MSG,
108
108
  error: str = None,
109
- status_code: str = Code.REDIS_ERROR_CODE
109
+ status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
110
110
  ):
111
111
  super().__init__(message=message, error=error, status_code=status_code)
112
112
 
@@ -115,7 +115,7 @@ class ValidationDataException(OSDException):
115
115
  self,
116
116
  message: str = Message.UNEXPECTED_ERROR_MSG,
117
117
  error: str = None,
118
- status_code: str = Code.REQUEST_VALIDATION_ERROR_CODE
118
+ status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
119
119
  ):
120
120
  super().__init__(message=message, error=error, status_code=status_code)
121
121
 
@@ -124,7 +124,7 @@ class UnexpectedException(OSDException):
124
124
  self,
125
125
  message: str = Message.UNEXPECTED_ERROR_MSG,
126
126
  error: str = None,
127
- status_code: str = Code.UNEXPECTED_ERROR_CODE
127
+ status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
128
128
  ):
129
129
  super().__init__(message=message, error=error, status_code=status_code)
130
130
 
@@ -133,7 +133,7 @@ class MissingFieldException(OSDException):
133
133
  self,
134
134
  message: str = Message.MISSING_FIELD_ERROR_MSG,
135
135
  error: str = None,
136
- status_code: str = Code.MISSING_FIELD_ERROR_CODE
136
+ status_code: str = StatusCode.VALIDATION_WARNING,
137
137
  ):
138
138
  super().__init__(message=message, error=error, status_code=status_code)
139
139
 
@@ -142,7 +142,7 @@ class ProfilePermissionDeniedException(OSDException):
142
142
  self,
143
143
  message: str = Message.PROFILE_PERMISSION_DENIED_MSG,
144
144
  error: str = None,
145
- status_code: str = Code.PROFILE_PERMISSION_DENIED_CODE
145
+ status_code: str = StatusCode.BUSINESS_RULE_WARNING,
146
146
  ):
147
147
  super().__init__(message=message, error=error, status_code=status_code)
148
148
 
@@ -151,7 +151,7 @@ class InvalidFormatException(OSDException):
151
151
  self,
152
152
  message: str = Message.INVALID_FORMAT_MSG,
153
153
  error: str = None,
154
- status_code: str = Code.INVALID_FORMAT_CODE
154
+ status_code: str = StatusCode.VALIDATION_WARNING,
155
155
  ):
156
156
  super().__init__(message=message, error=error, status_code=status_code)
157
157
 
@@ -160,6 +160,6 @@ class HttpClientException(OSDException):
160
160
  self,
161
161
  message: str = Message.UNEXPECTED_ERROR_MSG,
162
162
  error: str = None,
163
- status_code: str = Code.HTTP_ERROR_CODE
163
+ status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
164
164
  ):
165
165
  super().__init__(message=message, error=error, status_code=status_code)
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from azure.core.exceptions import (
2
3
  HttpResponseError,
3
4
  ResourceNotFoundError,
@@ -21,6 +22,9 @@ def classify(exc: Exception) -> Exception:
21
22
  Convierte excepciones del SDK de Azure en AzureTransientError
22
23
  o AzurePermanentError según si tiene sentido reintentar.
23
24
  """
25
+ if isinstance(exc, asyncio.TimeoutError):
26
+ return AzureTransientError(str(exc))
27
+
24
28
  # Fallos de red/conexión → siempre transitorio
25
29
  if isinstance(exc, (ServiceRequestError, ServiceResponseError)):
26
30
  return AzureTransientError(str(exc))