Encryptors 2.53__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.
- {encryptors-2.53 → encryptors-2.54}/PKG-INFO +1 -1
- {encryptors-2.53 → encryptors-2.54}/setup.py +1 -1
- {encryptors-2.53 → encryptors-2.54}/src/Encryptors.egg-info/PKG-INFO +1 -1
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Cache/Redis.py +2 -13
- encryptors-2.54/src/Osdental/Cli/__init__.py +137 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Constants/Message.py +5 -6
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Database/BaseRepository.py +8 -11
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Decorators/Retry.py +4 -1
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Decorators/SecureResolver.py +7 -5
- encryptors-2.54/src/Osdental/Encryptor/Aes.py +83 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Encryptor/Bcrypt.py +1 -1
- encryptors-2.54/src/Osdental/Encryptor/Jwt.py +35 -0
- encryptors-2.54/src/Osdental/Encryptor/Rsa.py +54 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Encryptor/Sha512.py +3 -3
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Exception/ControlledException.py +2 -44
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Graphql/Extensions/AuditExtension.py +1 -1
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Graphql/_Helpers/_TenantPolicy.py +1 -1
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Helpers/AuditDispatcher.py +1 -1
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Helpers/_AuthTokenProcessor.py +1 -1
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Http/APIClient.py +3 -3
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Http/_Helpers.py +1 -1
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Services/JwtAuthTokenService.py +1 -1
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Services/__init__.py +4 -4
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/FileMetaData.py +13 -13
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/Mapper.py +1 -1
- encryptors-2.53/src/Osdental/Cli/__init__.py +0 -120
- encryptors-2.53/src/Osdental/Encryptor/Aes.py +0 -87
- encryptors-2.53/src/Osdental/Encryptor/Jwt.py +0 -45
- encryptors-2.53/src/Osdental/Encryptor/Rsa.py +0 -60
- {encryptors-2.53 → encryptors-2.54}/README.md +0 -0
- {encryptors-2.53 → encryptors-2.54}/setup.cfg +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Encryptors.egg-info/SOURCES.txt +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Encryptors.egg-info/dependency_links.txt +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Encryptors.egg-info/entry_points.txt +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Encryptors.egg-info/requires.txt +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Encryptors.egg-info/top_level.txt +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Cache/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Constants/Constant.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Constants/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Database/Connection.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Database/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Decorators/Grpc.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Decorators/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Encryptor/Argon2.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Encryptor/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Enums/FileType.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Enums/GrahpqlOperation.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Enums/Profile.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Enums/StatusCode.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Enums/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Exception/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Graphql/Extensions/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Graphql/Models/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Graphql/_Exceptions/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Graphql/_Helpers/_AuditHelper.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Graphql/_Helpers/_TokenService.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Graphql/_Helpers/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Graphql/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Helpers/AzureClassifier.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Helpers/GrpcConnection.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Helpers/JwtTokenHelper.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Helpers/Resilience.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Helpers/ResponseDecryptor.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Helpers/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Http/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Messaging/AzureServiceBus.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Messaging/Kafka.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Messaging/RabbitMQ.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Messaging/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Models/ApiResponse.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Models/AuditConfig.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Models/Notification.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Models/Response.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Models/Token.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Models/TokenClaims.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Models/_Audit.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Models/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Rest/Context/RequestContext.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Rest/Context/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Rest/Middlewares/RequestContextMiddleware.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Rest/Middlewares/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Rest/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Secrets/AzureKeyVaultProvider.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Secrets/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Services/WebsocketClient.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Storage/S3Storage.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Storage/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/CaseConverter.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/CodeGenerator.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/DataNormalizer.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/DataUtils.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/DateUtils.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/HashValidator.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/PasswordGenerator.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/QueryGenerator.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/RsaUtils.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/TextProcessor.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/src/Osdental/Utils/__init__.py +0 -0
- {encryptors-2.53 → encryptors-2.54}/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.54",
|
|
6
6
|
author="OSDental LLC",
|
|
7
7
|
author_email="support@osdental.ai",
|
|
8
8
|
description="End-to-end algorithm library",
|
|
@@ -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
|
-
|
|
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
|
|
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()
|
|
@@ -7,9 +7,9 @@ class Message:
|
|
|
7
7
|
PROCESS_SUCCESS_MSG = "Process executed successfully."
|
|
8
8
|
NO_RESULTS_FOUND_MSG = "No records were found matching your request."
|
|
9
9
|
INVALID_REQUEST_PARAMS_MSG = "Please review the required fields and try again."
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
HEXAGONAL_SERVICE_CREATED_MSG = "The hexagonal service structure was created."
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
NO_PASSWORD_CHARACTERS_MSG = "There are no characters available to generate the password."
|
|
14
14
|
INSUFFICIENT_LENGTH_MSG = "Insufficient length to meet minimum rules."
|
|
15
15
|
LEGACY_NAME_REQUIRED_MSG = "Legacy name cannot be empty."
|
|
@@ -29,12 +29,11 @@ class Message:
|
|
|
29
29
|
INVALID_FORMAT_MSG = "The provided data format is invalid."
|
|
30
30
|
INVALID_AES_JSON_FORMAT_MSG = "Invalid JSON format in AES decrypted data."
|
|
31
31
|
UNEXPECTED_DECRYPTED_DATA_FORMAT_MSG = "Unexpected format in decrypted data."
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
|
|
33
|
+
|
|
34
34
|
DATABASE_EXECUTION_ERROR_MSG = "An unexpected error occurred while executing a database operation."
|
|
35
35
|
DATABASE_INTEGRITY_ERROR_MSG = "A database integrity constraint was violated during execution."
|
|
36
36
|
QUERY_NOT_PROVIDED_MSG = "Query not provided. Please include a valid query in your request."
|
|
37
37
|
FILE_PATH_NOT_PROVIDED_MSG = "File path not provided."
|
|
38
|
-
|
|
39
|
-
ERROR_INVALID_DATA_TYPE = "Invalid data type: expected dict, str, or list."
|
|
38
|
+
|
|
40
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.
|
|
9
|
-
from Osdental.
|
|
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=
|
|
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=
|
|
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=
|
|
180
|
+
status_code=StatusCode.BUSINESS_RULE_WARNING
|
|
184
181
|
)
|
|
185
182
|
|
|
186
183
|
return normalize(row) if as_dict else row
|
|
@@ -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.
|
|
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.
|
|
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
|
|
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))
|
|
@@ -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.
|
|
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
|
-
|
|
11
|
-
|
|
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 =
|
|
62
|
+
message: str = None,
|
|
63
63
|
error: str = None,
|
|
64
64
|
status_code: str = StatusCode.DATA_NOT_FOUND_WARNING,
|
|
65
65
|
):
|
|
66
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
13
|
+
from Osdental.Constants.Constant import Constant
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
@@ -5,7 +5,7 @@ from graphql import OperationType
|
|
|
5
5
|
from starlette.datastructures import Headers
|
|
6
6
|
from Osdental.Encryptor.Aes import AES
|
|
7
7
|
from Osdental.Models.Token import AuthToken
|
|
8
|
-
from Osdental.
|
|
8
|
+
from Osdental.Enums.Profile import Profile
|
|
9
9
|
from Osdental.Models.TokenClaims import UserTokenClaims
|
|
10
10
|
|
|
11
11
|
def extract_bearer_token(headers: Headers) -> str:
|
|
@@ -8,7 +8,7 @@ from Osdental.Http._Helpers import (
|
|
|
8
8
|
audit_unknown_error, audit_graphql_error
|
|
9
9
|
)
|
|
10
10
|
from Osdental.Exception.ControlledException import HttpClientException
|
|
11
|
-
|
|
11
|
+
from Osdental.Enums.StatusCode import StatusCode
|
|
12
12
|
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
@@ -52,7 +52,7 @@ class APIClient:
|
|
|
52
52
|
|
|
53
53
|
audit_http_error(exc.response, method, url, kwargs)
|
|
54
54
|
raise HttpClientException(
|
|
55
|
-
status_code=
|
|
55
|
+
status_code=StatusCode.BAD_GATEWAY,
|
|
56
56
|
error=exc.response.text
|
|
57
57
|
) from exc
|
|
58
58
|
|
|
@@ -60,7 +60,7 @@ class APIClient:
|
|
|
60
60
|
|
|
61
61
|
audit_exception_error(exc, method, url, kwargs)
|
|
62
62
|
raise HttpClientException(
|
|
63
|
-
status_code=
|
|
63
|
+
status_code=StatusCode.GATEWAY_TIMEOUT,
|
|
64
64
|
error=str(exc)
|
|
65
65
|
) from exc
|
|
66
66
|
|