Encryptors 2.52__tar.gz → 2.54__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 (101) hide show
  1. {encryptors-2.52 → encryptors-2.54}/PKG-INFO +1 -1
  2. {encryptors-2.52 → encryptors-2.54}/setup.py +1 -1
  3. {encryptors-2.52 → encryptors-2.54}/src/Encryptors.egg-info/PKG-INFO +1 -1
  4. {encryptors-2.52 → encryptors-2.54}/src/Encryptors.egg-info/SOURCES.txt +3 -0
  5. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Cache/Redis.py +2 -13
  6. encryptors-2.54/src/Osdental/Cli/__init__.py +137 -0
  7. encryptors-2.54/src/Osdental/Constants/Constant.py +10 -0
  8. encryptors-2.54/src/Osdental/Constants/Message.py +39 -0
  9. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Database/BaseRepository.py +8 -11
  10. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Decorators/Retry.py +4 -1
  11. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Decorators/SecureResolver.py +7 -5
  12. encryptors-2.54/src/Osdental/Encryptor/Aes.py +83 -0
  13. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Encryptor/Bcrypt.py +1 -1
  14. encryptors-2.54/src/Osdental/Encryptor/Jwt.py +35 -0
  15. encryptors-2.54/src/Osdental/Encryptor/Rsa.py +54 -0
  16. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Encryptor/Sha512.py +3 -3
  17. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Exception/ControlledException.py +2 -44
  18. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Graphql/Extensions/AuditExtension.py +1 -1
  19. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Graphql/_Helpers/_TenantPolicy.py +1 -1
  20. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Helpers/AuditDispatcher.py +1 -1
  21. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Helpers/_AuthTokenProcessor.py +1 -1
  22. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Http/APIClient.py +3 -3
  23. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Http/_Helpers.py +1 -1
  24. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Services/JwtAuthTokenService.py +1 -1
  25. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Services/__init__.py +4 -4
  26. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Utils/FileMetaData.py +13 -13
  27. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Utils/Mapper.py +1 -1
  28. encryptors-2.54/src/Osdental/__init__.py +0 -0
  29. encryptors-2.52/src/Osdental/Cli/__init__.py +0 -120
  30. encryptors-2.52/src/Osdental/Encryptor/Aes.py +0 -87
  31. encryptors-2.52/src/Osdental/Encryptor/Jwt.py +0 -45
  32. encryptors-2.52/src/Osdental/Encryptor/Rsa.py +0 -60
  33. {encryptors-2.52 → encryptors-2.54}/README.md +0 -0
  34. {encryptors-2.52 → encryptors-2.54}/setup.cfg +0 -0
  35. {encryptors-2.52 → encryptors-2.54}/src/Encryptors.egg-info/dependency_links.txt +0 -0
  36. {encryptors-2.52 → encryptors-2.54}/src/Encryptors.egg-info/entry_points.txt +0 -0
  37. {encryptors-2.52 → encryptors-2.54}/src/Encryptors.egg-info/requires.txt +0 -0
  38. {encryptors-2.52 → encryptors-2.54}/src/Encryptors.egg-info/top_level.txt +0 -0
  39. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Cache/__init__.py +0 -0
  40. {encryptors-2.52/src/Osdental/Database → encryptors-2.54/src/Osdental/Constants}/__init__.py +0 -0
  41. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Database/Connection.py +0 -0
  42. {encryptors-2.52/src/Osdental/Decorators → encryptors-2.54/src/Osdental/Database}/__init__.py +0 -0
  43. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Decorators/Grpc.py +0 -0
  44. {encryptors-2.52/src/Osdental/Encryptor → encryptors-2.54/src/Osdental/Decorators}/__init__.py +0 -0
  45. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Encryptor/Argon2.py +0 -0
  46. {encryptors-2.52/src/Osdental/Enums → encryptors-2.54/src/Osdental/Encryptor}/__init__.py +0 -0
  47. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Enums/FileType.py +0 -0
  48. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Enums/GrahpqlOperation.py +0 -0
  49. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Enums/Profile.py +0 -0
  50. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Enums/StatusCode.py +0 -0
  51. {encryptors-2.52/src/Osdental/Exception → encryptors-2.54/src/Osdental/Enums}/__init__.py +0 -0
  52. {encryptors-2.52/src/Osdental/Graphql/Extensions → encryptors-2.54/src/Osdental/Exception}/__init__.py +0 -0
  53. {encryptors-2.52/src/Osdental/Graphql/_Helpers → encryptors-2.54/src/Osdental/Graphql/Extensions}/__init__.py +0 -0
  54. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Graphql/Models/__init__.py +0 -0
  55. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Graphql/_Exceptions/__init__.py +0 -0
  56. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Graphql/_Helpers/_AuditHelper.py +0 -0
  57. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py +0 -0
  58. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Graphql/_Helpers/_TokenService.py +0 -0
  59. {encryptors-2.52/src/Osdental/Graphql → encryptors-2.54/src/Osdental/Graphql/_Helpers}/__init__.py +0 -0
  60. {encryptors-2.52/src/Osdental/Helpers → encryptors-2.54/src/Osdental/Graphql}/__init__.py +0 -0
  61. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Helpers/AzureClassifier.py +0 -0
  62. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Helpers/GrpcConnection.py +0 -0
  63. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Helpers/JwtTokenHelper.py +0 -0
  64. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Helpers/Resilience.py +0 -0
  65. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Helpers/ResponseDecryptor.py +0 -0
  66. {encryptors-2.52/src/Osdental/Http → encryptors-2.54/src/Osdental/Helpers}/__init__.py +0 -0
  67. {encryptors-2.52/src/Osdental/Models → encryptors-2.54/src/Osdental/Http}/__init__.py +0 -0
  68. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Messaging/AzureServiceBus.py +0 -0
  69. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Messaging/Kafka.py +0 -0
  70. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Messaging/RabbitMQ.py +0 -0
  71. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Messaging/__init__.py +0 -0
  72. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Models/ApiResponse.py +0 -0
  73. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Models/AuditConfig.py +0 -0
  74. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Models/Notification.py +0 -0
  75. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Models/Response.py +0 -0
  76. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Models/Token.py +0 -0
  77. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Models/TokenClaims.py +0 -0
  78. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Models/_Audit.py +0 -0
  79. {encryptors-2.52/src/Osdental/Rest/Context → encryptors-2.54/src/Osdental/Models}/__init__.py +0 -0
  80. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Rest/Context/RequestContext.py +0 -0
  81. {encryptors-2.52/src/Osdental/Rest/Middlewares → encryptors-2.54/src/Osdental/Rest/Context}/__init__.py +0 -0
  82. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Rest/Middlewares/RequestContextMiddleware.py +0 -0
  83. {encryptors-2.52/src/Osdental/Rest → encryptors-2.54/src/Osdental/Rest/Middlewares}/__init__.py +0 -0
  84. {encryptors-2.52/src/Osdental/Utils → encryptors-2.54/src/Osdental/Rest}/__init__.py +0 -0
  85. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Secrets/AzureKeyVaultProvider.py +0 -0
  86. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Secrets/__init__.py +0 -0
  87. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Services/WebsocketClient.py +0 -0
  88. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
  89. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Storage/S3Storage.py +0 -0
  90. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Storage/__init__.py +0 -0
  91. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Utils/CaseConverter.py +0 -0
  92. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Utils/CodeGenerator.py +0 -0
  93. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Utils/DataNormalizer.py +0 -0
  94. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Utils/DataUtils.py +0 -0
  95. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Utils/DateUtils.py +0 -0
  96. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Utils/HashValidator.py +0 -0
  97. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Utils/PasswordGenerator.py +0 -0
  98. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Utils/QueryGenerator.py +0 -0
  99. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Utils/RsaUtils.py +0 -0
  100. {encryptors-2.52 → encryptors-2.54}/src/Osdental/Utils/TextProcessor.py +0 -0
  101. {encryptors-2.52/src/Osdental → encryptors-2.54/src/Osdental/Utils}/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Encryptors
3
- Version: 2.52
3
+ Version: 2.54
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.52",
5
+ version="2.54",
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.52
3
+ Version: 2.54
4
4
  Summary: End-to-end algorithm library
5
5
  Author: OSDental LLC
6
6
  Author-email: support@osdental.ai
@@ -10,6 +10,9 @@ src/Osdental/__init__.py
10
10
  src/Osdental/Cache/Redis.py
11
11
  src/Osdental/Cache/__init__.py
12
12
  src/Osdental/Cli/__init__.py
13
+ src/Osdental/Constants/Constant.py
14
+ src/Osdental/Constants/Message.py
15
+ src/Osdental/Constants/__init__.py
13
16
  src/Osdental/Database/BaseRepository.py
14
17
  src/Osdental/Database/Connection.py
15
18
  src/Osdental/Database/__init__.py
@@ -7,10 +7,9 @@ from redis_entraid.cred_provider import (
7
7
  create_from_default_azure_credential
8
8
  )
9
9
  from Osdental.Cache import ICacheService
10
- from Osdental.Exception.ControlledException import RedisException
11
10
  from Osdental.Helpers.Resilience import AzureResiliencePolicy, AzureTransientError
12
11
  from Osdental.Helpers.AzureClassifier import classify_redis
13
- from Osdental.Constants.Message import Message
12
+
14
13
 
15
14
 
16
15
  logger = logging.getLogger(__name__)
@@ -59,7 +58,6 @@ class RedisCacheAsync(ICacheService):
59
58
  """
60
59
  Ejecuta una operación de Redis a través de la policy.
61
60
  Clasifica el error y deja que la policy decida si reintenta.
62
- Si se agota o el circuito está abierto, convierte a RedisException.
63
61
  """
64
62
  await self._ensure_connection()
65
63
 
@@ -86,12 +84,6 @@ class RedisCacheAsync(ICacheService):
86
84
  exc
87
85
  )
88
86
 
89
- raise RedisException(
90
- message=Message.UNEXPECTED_ERROR_MSG,
91
- error=str(exc)
92
- )
93
- except Exception as exc:
94
- raise RedisException(message=Message.UNEXPECTED_ERROR_MSG, error=str(exc))
95
87
 
96
88
 
97
89
  async def connect(self):
@@ -137,10 +129,7 @@ class RedisCacheAsync(ICacheService):
137
129
  f"Redis connection error: {str(e)}"
138
130
  )
139
131
 
140
- raise RedisException(
141
- message=Message.UNEXPECTED_ERROR_MSG,
142
- error=str(e)
143
- )
132
+ raise e
144
133
 
145
134
  async def _ensure_connection(self):
146
135
 
@@ -0,0 +1,137 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ import subprocess
5
+ import platform
6
+ import click
7
+
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ SERVER = "uvicorn"
12
+ SERVER_PROD = "gunicorn"
13
+ FILE_RUN_SERVER = "app:app"
14
+ PYCACHE_CLEANUP_SUCCESS_MSG = "All __pycache__ have been removed."
15
+ SERVER_NETWORK_ACCESS_ERROR_MSG = "Error making the server accessible on the network."
16
+ REDIS_CLEANUP_SUCCESS_MSG = "Redis cleanup completed successfully."
17
+ REDIS_CLEANUP_ERROR_MSG = "An error occurred while attempting to clean up Redis."
18
+ PROTO_FILES_GENERATED_MSG = "The {name} Proto files have been successfully generated."
19
+
20
+ @click.group()
21
+ def cli():
22
+ """Custom commands to manage the project."""
23
+ pass
24
+
25
+ @cli.command()
26
+ def clean():
27
+ """Borrar todos los __pycache__."""
28
+ if platform.system() == "Windows":
29
+ subprocess.run('for /d /r . %d in (__pycache__) do @if exist "%d" rd /s/q "%d"', shell=True)
30
+ else:
31
+ subprocess.run("find . -name '__pycache__' -type d -exec rm -rf {} +", shell=True)
32
+
33
+ logger.info(PYCACHE_CLEANUP_SUCCESS_MSG)
34
+
35
+ @cli.command()
36
+ @click.argument("port")
37
+ def start(port: int):
38
+ """Start the FastAPI server.."""
39
+ try:
40
+ subprocess.run([SERVER, FILE_RUN_SERVER, "--port", str(port), "--reload"], check=True)
41
+ except subprocess.CalledProcessError as e:
42
+ logger.exception(f"{SERVER_NETWORK_ACCESS_ERROR_MSG}: {e}")
43
+
44
+ @cli.command()
45
+ @click.argument("port")
46
+ def serve(port: int):
47
+ """Set up the FastAPI server accessible from any machine."""
48
+ try:
49
+ subprocess.run([SERVER, FILE_RUN_SERVER, '--host', "0.0.0.0", "--port", str(port), "--reload"], check=True)
50
+ except subprocess.CalledProcessError as e:
51
+ logger.exception(f"{SERVER_NETWORK_ACCESS_ERROR_MSG}: {e}")
52
+
53
+ @cli.command("clean-redis")
54
+ @click.argument("redis_env")
55
+ async def clean_redis(redis_env: str):
56
+ try:
57
+ from Osdental.Cache.Redis import RedisCacheAsync
58
+ redis_url = os.getenv(redis_env)
59
+ if not redis_url:
60
+ logger.warning(f"Environment variable not found: {redis_env}")
61
+ return
62
+
63
+ redis = RedisCacheAsync(redis_url=redis_url)
64
+ await redis.flush()
65
+ logger.info(REDIS_CLEANUP_SUCCESS_MSG)
66
+ except Exception as e:
67
+ logger.exception(f"{REDIS_CLEANUP_ERROR_MSG}: {e}")
68
+
69
+ @cli.command(name='proto-files')
70
+ @click.argument('name')
71
+ def proto_files(name: str):
72
+
73
+ from pathlib import Path
74
+
75
+ GRPC_BASE_DIR = Path("src") / "Infrastructure" / "Grpc"
76
+ proto_dir = (GRPC_BASE_DIR / "Proto").as_posix()
77
+ gen_dir = (GRPC_BASE_DIR / "Generated").as_posix()
78
+
79
+ # 2. Rutas de los archivos .proto (reutilizando las variables posix anteriores)
80
+ proto_path = f"{proto_dir}/{name}.proto"
81
+ common_path = f"{proto_dir}/Common.proto"
82
+
83
+ # 3. Rutas para verificar si ya existen los archivos generados de Common
84
+ common_py = f"{gen_dir}/Common_pb2.py"
85
+ common_grpc_py = f"{gen_dir}/Common_pb2_grpc.py"
86
+
87
+ # 4. Lógica de compilación condicional
88
+ proto_files_to_compile = [proto_path]
89
+ # Nota: os.path.exists entiende perfectamente las rutas con '/' en Windows
90
+ if not (os.path.exists(common_py) and os.path.exists(common_grpc_py)):
91
+ proto_files_to_compile.append(common_path)
92
+
93
+ # 5. Comando para ejecutar protoc
94
+ cmd = [
95
+ sys.executable, "-m", "grpc_tools.protoc",
96
+ f"-I={proto_dir}",
97
+ f"--python_out={gen_dir}",
98
+ f"--grpc_python_out={gen_dir}",
99
+ *proto_files_to_compile
100
+ ]
101
+
102
+ subprocess.run(cmd, shell=False, check=True)
103
+ logger.info(PROTO_FILES_GENERATED_MSG.format(name=name))
104
+
105
+ @cli.command("run-server")
106
+ @click.option("--host", default="0.0.0.0", help="Host where to set up the server")
107
+ @click.option("--port", default=5000, help="Server port")
108
+ @click.option("--workers", default=4, help="Number of workers")
109
+ def run_server(host, port, workers):
110
+ """
111
+ Launch the application with uvicorn on Windows or gunicorn on other systems
112
+ :host
113
+ :port
114
+ :workers
115
+ """
116
+ if sys.platform.startswith("win"):
117
+ cmd = [
118
+ sys.executable, "-m", SERVER,
119
+ FILE_RUN_SERVER,
120
+ "--host", host,
121
+ "--port", str(port),
122
+ "--workers", str(workers)
123
+ ]
124
+ else:
125
+ cmd = [
126
+ sys.executable, "-m", SERVER_PROD,
127
+ FILE_RUN_SERVER,
128
+ "-k", "uvicorn.workers.UvicornWorker",
129
+ "--bind", f"{host}:{port}",
130
+ "--workers", str(workers)
131
+ ]
132
+
133
+ logger.info(f'Running: {" ".join(cmd)}')
134
+ subprocess.run(cmd)
135
+
136
+ if __name__ == "__main__":
137
+ cli()
@@ -0,0 +1,10 @@
1
+
2
+
3
+ class Constant:
4
+ DEFAULT_ENCODING = "utf-8"
5
+ MESSAGE_LOG_INTERNAL = "MESSAGE_LOG_INTERNAL"
6
+ DEFAULT_EMPTY_VALUE = "*"
7
+ RESPONSE_TYPE_ERROR = "ERROR"
8
+ RESPONSE_TYPE_REQUEST = "REQUEST"
9
+ RESPONSE_TYPE_RESPONSE = "RESPONSE"
10
+ MESSAGE_LOG_EXTERNAL = "MESSAGE_LOG_EXTERNAL"
@@ -0,0 +1,39 @@
1
+
2
+ class Message:
3
+
4
+ ACCESS_DENIED_MSG = "You do not have the necessary privileges for this operation."
5
+ UNEXPECTED_ERROR_MSG = "Something went wrong while processing your request. Please try again later."
6
+ PORTAL_ACCESS_RESTRICTED_MSG = "You are not authorized to access this portal."
7
+ PROCESS_SUCCESS_MSG = "Process executed successfully."
8
+ NO_RESULTS_FOUND_MSG = "No records were found matching your request."
9
+ INVALID_REQUEST_PARAMS_MSG = "Please review the required fields and try again."
10
+
11
+ HEXAGONAL_SERVICE_CREATED_MSG = "The hexagonal service structure was created."
12
+
13
+ NO_PASSWORD_CHARACTERS_MSG = "There are no characters available to generate the password."
14
+ INSUFFICIENT_LENGTH_MSG = "Insufficient length to meet minimum rules."
15
+ LEGACY_NAME_REQUIRED_MSG = "Legacy name cannot be empty."
16
+ REFRESH_TOKEN_EXP_REQUIRED_MSG = "Refresh token expiration time cannot be empty."
17
+ ACCESS_TOKEN_EXP_REQUIRED_MSG = "Access token expiration time cannot be empty."
18
+ PUBLIC_KEY2_REQUIRED_MSG = "Public key 2 cannot be empty."
19
+ PRIVATE_KEY1_REQUIRED_MSG = "Private key 1 cannot be empty."
20
+ PRIVATE_KEY2_REQUIRED_MSG = "Private key 2 cannot be empty."
21
+ AES_KEY_USER_REQUIRED_MSG = "AES key user cannot be empty."
22
+ AES_KEY_AUTH_REQUIRED_MSG = "AES key auth cannot be empty."
23
+ MISSING_FIELD_ERROR_MSG = "A required field is missing. Please review the data."
24
+ EXP_TIME_REQUIRED = "Expiration time (cdata integration) cannot be empty or null"
25
+ KEY_PRIVATE_REQUIRED = "Key private cannot be empty or null"
26
+ SUB_ACCOUNT_REQUIRED = "Sub account parameter is required"
27
+ CATALOG_DATA_CREDENTIALS_MISSED = "Catalog data or/and data credentials not found"
28
+ PROFILE_PERMISSION_DENIED_MSG = "Your profile does not have permission to perform this action."
29
+ INVALID_FORMAT_MSG = "The provided data format is invalid."
30
+ INVALID_AES_JSON_FORMAT_MSG = "Invalid JSON format in AES decrypted data."
31
+ UNEXPECTED_DECRYPTED_DATA_FORMAT_MSG = "Unexpected format in decrypted data."
32
+
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
+ QUERY_NOT_PROVIDED_MSG = "Query not provided. Please include a valid query in your request."
37
+ FILE_PATH_NOT_PROVIDED_MSG = "File path not provided."
38
+
39
+ EXTERNAL_API_ERROR_MESSAGE = "An error occurred while consuming an external API."
@@ -5,8 +5,9 @@ from sqlalchemy.exc import ResourceClosedError, DBAPIError
5
5
  from sqlalchemy.ext.asyncio import AsyncSession
6
6
  from Osdental.Exception.ControlledException import DatabaseException
7
7
  from Osdental.Helpers.Resilience import AzureResiliencePolicy, AzureTransientError
8
- from Osdental.Shared.Utils.DataNormalizer import normalize
9
- from Osdental.Constants.Message import Message
8
+ from Osdental.Utils.DataNormalizer import normalize
9
+ from Osdental.Enums.StatusCode import StatusCode
10
+
10
11
 
11
12
  _TRANSIENT_SQL_ERRORS = {
12
13
  "08S01", "40197", "40501", "40613", "49918",
@@ -109,9 +110,8 @@ class BaseRepository:
109
110
 
110
111
  if status_code not in success_codes:
111
112
  raise DatabaseException(
112
- message=Message.UNEXPECTED_ERROR_MSG,
113
- error=status_message,
114
- status_code=status_code
113
+ message=status_message,
114
+ error=status_message
115
115
  )
116
116
 
117
117
  if as_dict and row is not None:
@@ -127,8 +127,7 @@ class BaseRepository:
127
127
  *,
128
128
  validate: bool = True,
129
129
  success_codes: str | int | Iterable[str | int] | None = None,
130
- as_dict: bool = False,
131
- commit_on_failure: bool = False
130
+ as_dict: bool = False
132
131
  ) -> Any:
133
132
 
134
133
  result = await self._safe_execute(query, params)
@@ -162,7 +161,7 @@ class BaseRepository:
162
161
  raise DatabaseException(
163
162
  message="Status code missing",
164
163
  error="Status code missing",
165
- status_code="DB_ERROR_MISSING"
164
+ status_code=StatusCode.INTERNAL_SERVER_ERROR
166
165
  )
167
166
 
168
167
  else:
@@ -174,13 +173,11 @@ class BaseRepository:
174
173
  success_codes = (success_codes,)
175
174
 
176
175
  if status_code not in success_codes:
177
- if commit_on_failure:
178
- await self.async_session.commit()
179
176
 
180
177
  raise DatabaseException(
181
178
  message=status_message,
182
179
  error=status_message,
183
- status_code=status_code
180
+ status_code=StatusCode.BUSINESS_RULE_WARNING
184
181
  )
185
182
 
186
183
  return normalize(row) if as_dict else row
@@ -1,5 +1,8 @@
1
1
  from functools import wraps
2
- from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
2
+ from tenacity import (
3
+ retry, stop_after_attempt,
4
+ wait_exponential, retry_if_exception_type
5
+ )
3
6
  import grpc
4
7
  import httpx
5
8
 
@@ -3,16 +3,17 @@ 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.Shared.Enums.Profile import Profile
6
+ from Osdental.Graphql.Models import BaseGraphQLContext
7
+ from Osdental.Models.TokenClaims import UserTokenClaims
8
+ from Osdental.Models.Token import AuthToken
9
+ from Osdental.Enums.Profile import Profile
7
10
  from Osdental.Exception.ControlledException import (
8
11
  OSDException, AccessDeniedException
9
12
  )
10
- from Osdental.Graphql.Models import BaseGraphQLContext
11
- from Osdental.Helpers._Ports import IAuthTokenService
13
+ from Osdental.Services import IAuthTokenService
12
14
  from Osdental.Helpers._AuthTokenProcessor import (
13
15
  extract_bearer_token, build_auth_token, decrypt_and_parse_payload
14
16
  )
15
- from Osdental.Models.TokenClaims import UserTokenClaims
16
17
  from Osdental.Enums.StatusCode import StatusCode
17
18
 
18
19
 
@@ -40,6 +41,7 @@ def resolver(public: bool = False, action=None):
40
41
  try:
41
42
  context: BaseGraphQLContext = info.context
42
43
  request = context.request
44
+ token: type[AuthToken] | None = None
43
45
 
44
46
  # ── 1. AUTENTICACIÓN ──────────────────────────────
45
47
  if not public:
@@ -106,7 +108,7 @@ def resolver(public: bool = False, action=None):
106
108
  }
107
109
 
108
110
  if isinstance(result, Response):
109
- decrypted_key = result.key or claims.aes_key_auth
111
+ decrypted_key = result.key if not token else token.aes_key_auth
110
112
 
111
113
  __test(
112
114
  dispatcher=dispatcher,
@@ -0,0 +1,83 @@
1
+ import logging
2
+ import os
3
+ import json
4
+ import base64
5
+ from typing import Dict, List
6
+ from cryptography.hazmat.primitives.ciphers import (
7
+ Cipher, algorithms, modes
8
+ )
9
+ from Osdental.Constants.Constant import Constant
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class AES:
14
+
15
+ IV_LENGTH = 32
16
+ TAG_LENGTH = 16
17
+
18
+ @staticmethod
19
+ def generate_key() -> str:
20
+ """Generates a random 256-bit AES key and returns it Base64 encoded."""
21
+ key = os.urandom(32)
22
+ return base64.b64encode(key).decode(Constant.DEFAULT_ENCODING)
23
+
24
+ @classmethod
25
+ def encrypt(cls, aes_key:str, data:Dict[str,str] | str | List[Dict[str,str]]) -> str:
26
+ """
27
+ Encrypts data using AES-GCM.
28
+ Supports dictionary, string, or list inputs.
29
+
30
+ :param aes_key: AES key in Base64 format.
31
+ :param data: Data to be encrypted (dict, str, or list).
32
+ :return: Data encrypted in Base64.
33
+ """
34
+ if not isinstance(data, (dict, str, list)):
35
+ raise ValueError("Invalid data type: expected dict, str, or list.")
36
+
37
+ key = base64.b64decode(aes_key)
38
+ iv = os.urandom(cls.IV_LENGTH)
39
+ if isinstance(data, (dict, list)):
40
+ json_data = json.dumps(data)
41
+ else:
42
+ json_data = data
43
+
44
+ cipher = Cipher(algorithms.AES(key), modes.GCM(iv))
45
+ encryptor = cipher.encryptor()
46
+ ciphertext = encryptor.update(json_data.encode(Constant.DEFAULT_ENCODING)) + encryptor.finalize()
47
+ tag = encryptor.tag
48
+ encrypted_data = iv + ciphertext + tag
49
+
50
+ return base64.b64encode(encrypted_data).decode(Constant.DEFAULT_ENCODING)
51
+
52
+
53
+ @classmethod
54
+ def decrypt(cls, aes_key:str, encrypted_data:str, silent:bool = False):
55
+ """
56
+ Decrypts data using AES-GCM.
57
+ Expects encrypted data to represent either a JSON object (dict) or a plain string.
58
+
59
+ :param aes_key: AES key in Base64 format.
60
+ :param encrypted_data: Data encrypted in Base64.
61
+ :return: Decrypted data (either dict or str).
62
+ """
63
+
64
+ key = base64.b64decode(aes_key)
65
+ encrypted_data = base64.b64decode(encrypted_data)
66
+ iv = encrypted_data[:cls.IV_LENGTH]
67
+ tag = encrypted_data[-cls.TAG_LENGTH:]
68
+ ciphertext = encrypted_data[cls.IV_LENGTH:-cls.TAG_LENGTH]
69
+ cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag))
70
+ decryptor = cipher.decryptor()
71
+ plaintext = decryptor.update(ciphertext) + decryptor.finalize()
72
+
73
+ try:
74
+ decrypted_data = json.loads(plaintext.decode(Constant.DEFAULT_ENCODING))
75
+ return decrypted_data
76
+ except json.JSONDecodeError:
77
+ return plaintext.decode(Constant.DEFAULT_ENCODING)
78
+
79
+ # except Exception as e:
80
+ # if not silent:
81
+ # logger.exception(f'Unexpected AES decryption error: {str(e)}')
82
+
83
+ # raise AESEncryptException(error=str(e))
@@ -1,6 +1,6 @@
1
1
  import bcrypt
2
2
  from Osdental.Exception.ControlledException import OSDException
3
- from Osdental.Shared.Enums.Constant import Constant
3
+ from Osdental.Constants.Constant import Constant
4
4
 
5
5
  class BcryptPasswordManager:
6
6
  def __init__(self, rounds: int = 12):
@@ -0,0 +1,35 @@
1
+ import logging
2
+ import jwt
3
+ from typing import Dict, Any
4
+ from cryptography.hazmat.primitives import serialization
5
+ from cryptography.hazmat.backends import default_backend
6
+
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class JWT:
11
+
12
+ @staticmethod
13
+ def generate_token(
14
+ payload: Dict[str, Any],
15
+ jwt_secret_key: str,
16
+ algorithm="HS256"
17
+ ) -> str:
18
+
19
+ return jwt.encode(payload, jwt_secret_key, algorithm=algorithm)
20
+
21
+
22
+ @staticmethod
23
+ def extract_payload(
24
+ jwt_token: str,
25
+ jwt_secret_key: str
26
+ ) -> Dict[str, Any]:
27
+
28
+ return jwt.decode(jwt_token, jwt_secret_key, algorithms=["HS256"])
29
+
30
+ @staticmethod
31
+ def generate_private_key(private_rsa: str):
32
+
33
+ return serialization.load_pem_private_key(
34
+ private_rsa.encode(), password=None, backend=default_backend()
35
+ )
@@ -0,0 +1,54 @@
1
+ import logging
2
+ import json
3
+ from base64 import b64decode, b64encode
4
+ from typing import Dict, Any
5
+ from cryptography.hazmat.primitives import serialization
6
+ from cryptography.hazmat.primitives.asymmetric import padding
7
+ from cryptography.hazmat.primitives import hashes
8
+ from Osdental.Constants.Constant import Constant
9
+
10
+
11
+ logger = logging.getLogger(__name__)
12
+ class RSAEncryptor:
13
+
14
+ @staticmethod
15
+ def encrypt(data: str | Dict[str, Any], public_key_rsa: str) -> str:
16
+
17
+ if isinstance(data, dict):
18
+ data = json.dumps(data)
19
+
20
+ public_key = serialization.load_pem_public_key(public_key_rsa.encode(Constant.DEFAULT_ENCODING))
21
+ data_bytes = data.encode(Constant.DEFAULT_ENCODING)
22
+ encrypted_bytes = public_key.encrypt(
23
+ data_bytes,
24
+ padding.OAEP(
25
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
26
+ algorithm=hashes.SHA256(),
27
+ label=None
28
+ )
29
+ )
30
+
31
+ return b64encode(encrypted_bytes).decode(Constant.DEFAULT_ENCODING)
32
+
33
+
34
+ @staticmethod
35
+ def decrypt(data: str, private_key_rsa: str) -> str:
36
+
37
+ encrypted_bytes = b64decode(data)
38
+ private_key = serialization.load_pem_private_key(private_key_rsa.encode(Constant.DEFAULT_ENCODING), password=None)
39
+ decrypted_bytes = private_key.decrypt(
40
+ encrypted_bytes,
41
+ padding.OAEP(
42
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
43
+ algorithm=hashes.SHA256(),
44
+ label=None
45
+ )
46
+ )
47
+
48
+ return decrypted_bytes.decode(Constant.DEFAULT_ENCODING)
49
+
50
+ # except Exception as e:
51
+ # if not silent:
52
+ # logger.exception(f'Unexpected RSA decryption error: {str(e)}')
53
+
54
+ # raise RSAEncryptException(message=Message.UNEXPECTED_ERROR_MSG, error=str(e)) , silent: bool = False
@@ -1,5 +1,5 @@
1
1
  import hashlib
2
- from Osdental.Shared.Enums.Constant import Constant
2
+ from Osdental.Constants.Constant import Constant
3
3
 
4
4
  class SHA512:
5
5
 
@@ -7,8 +7,8 @@ class SHA512:
7
7
  def hash_password(password: str) -> str:
8
8
  """Generates the SHA-512 hash of a password."""
9
9
  hash_object = hashlib.sha512(password.encode(Constant.DEFAULT_ENCODING))
10
- hashed_password = hash_object.hexdigest()
11
- return hashed_password
10
+ return hash_object.hexdigest()
11
+
12
12
 
13
13
  @staticmethod
14
14
  def verify_password(hash_password: str, password: str) -> bool:
@@ -59,56 +59,14 @@ class DatabaseConnectionException(OSDException):
59
59
  class DatabaseException(OSDException):
60
60
  def __init__(
61
61
  self,
62
- message: str = Message.UNEXPECTED_ERROR_MSG,
62
+ message: str = None,
63
63
  error: str = None,
64
64
  status_code: str = StatusCode.DATA_NOT_FOUND_WARNING,
65
65
  ):
66
- super().__init__(message=message, error=error, status_code=status_code)
67
-
68
- class RSAEncryptException(OSDException):
69
- def __init__(
70
- self,
71
- message: str = Message.UNEXPECTED_ERROR_MSG,
72
- error: str = None,
73
- status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
74
- ):
75
- super().__init__(message=message, error=error, status_code=status_code)
66
+ self.message = message or Message.UNEXPECTED_ERROR_MSG
76
67
 
77
- class AESEncryptException(OSDException):
78
- def __init__(
79
- self,
80
- message: str = Message.UNEXPECTED_ERROR_MSG,
81
- error: str = None,
82
- status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
83
- ):
84
68
  super().__init__(message=message, error=error, status_code=status_code)
85
69
 
86
- class JWTokenException(OSDException):
87
- def __init__(
88
- self,
89
- message: str = Message.UNEXPECTED_ERROR_MSG,
90
- error: str = None,
91
- status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
92
- ):
93
- super().__init__(message=message, error=error, status_code=status_code)
94
-
95
- class AzureException(OSDException):
96
- def __init__(
97
- self,
98
- message: str = Message.UNEXPECTED_ERROR_MSG,
99
- error: str = None,
100
- status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
101
- ):
102
- super().__init__(message=message, error=error, status_code=status_code)
103
-
104
- class RedisException(OSDException):
105
- def __init__(
106
- self,
107
- message: str = Message.UNEXPECTED_ERROR_MSG,
108
- error: str = None,
109
- status_code: str = StatusCode.INTERNAL_SERVER_ERROR,
110
- ):
111
- super().__init__(message=message, error=error, status_code=status_code)
112
70
 
113
71
  class ValidationDataException(OSDException):
114
72
  def __init__(
@@ -11,7 +11,7 @@ from Osdental.Rest.Context.RequestContext import (
11
11
  )
12
12
  from Osdental.Models.Response import Response
13
13
  from Osdental.Graphql.Models import BaseGraphQLContext
14
- from Osdental.Shared.Enums.Constant import Constant
14
+ from Osdental.Constants.Constant import Constant
15
15
 
16
16
  logger = logging.getLogger(__name__)
17
17
 
@@ -4,7 +4,7 @@ from graphql import OperationType
4
4
  from starlette.datastructures import Headers
5
5
  from Osdental.Encryptor.Aes import AES
6
6
  from Osdental.Models.Token import AuthToken
7
- from Osdental.Shared.Enums.Profile import Profile
7
+ from Osdental.Enums.Profile import Profile
8
8
 
9
9
  class TenantPolicy:
10
10
 
@@ -10,7 +10,7 @@ from Osdental.Helpers.ResponseDecryptor import decryptor_data, VALID_TYPES
10
10
  from Osdental.Models._Audit import Audit
11
11
  from Osdental.Models.Response import Response
12
12
  from Osdental.Models.ApiResponse import ApiResponse
13
- from Osdental.Shared.Enums.Constant import Constant
13
+ from Osdental.Constants.Constant import Constant
14
14
 
15
15
 
16
16
  logger = logging.getLogger(__name__)