Encryptors 2.35__tar.gz → 2.37__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 (89) hide show
  1. {encryptors-2.35 → encryptors-2.37}/PKG-INFO +1 -1
  2. {encryptors-2.35 → encryptors-2.37}/setup.py +1 -1
  3. {encryptors-2.35 → encryptors-2.37}/src/Encryptors.egg-info/PKG-INFO +1 -1
  4. {encryptors-2.35 → encryptors-2.37}/src/Encryptors.egg-info/SOURCES.txt +1 -0
  5. encryptors-2.37/src/Osdental/Cli/__init__.py +118 -0
  6. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Database/BaseRepository.py +34 -20
  7. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Decorators/SecureResolver.py +11 -13
  8. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Encryptor/Aes.py +10 -10
  9. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Exception/ControlledException.py +9 -0
  10. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Graphql/Extensions/AuditExtension.py +16 -11
  11. encryptors-2.37/src/Osdental/Graphql/Models/__init__.py +14 -0
  12. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Graphql/_Exceptions/__init__.py +1 -1
  13. encryptors-2.37/src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py +11 -0
  14. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Graphql/_Helpers/_TenantPolicy.py +10 -7
  15. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Graphql/_Helpers/_TokenService.py +6 -9
  16. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Models/Token.py +0 -2
  17. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Enums/Code.py +1 -0
  18. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Enums/Message.py +1 -1
  19. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Utils/FileMetaData.py +1 -1
  20. encryptors-2.35/src/Osdental/Cli/__init__.py +0 -505
  21. encryptors-2.35/src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py +0 -11
  22. {encryptors-2.35 → encryptors-2.37}/README.md +0 -0
  23. {encryptors-2.35 → encryptors-2.37}/setup.cfg +0 -0
  24. {encryptors-2.35 → encryptors-2.37}/src/Encryptors.egg-info/dependency_links.txt +0 -0
  25. {encryptors-2.35 → encryptors-2.37}/src/Encryptors.egg-info/entry_points.txt +0 -0
  26. {encryptors-2.35 → encryptors-2.37}/src/Encryptors.egg-info/requires.txt +0 -0
  27. {encryptors-2.35 → encryptors-2.37}/src/Encryptors.egg-info/top_level.txt +0 -0
  28. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Database/Connection.py +0 -0
  29. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Database/__init__.py +0 -0
  30. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Decorators/Grpc.py +0 -0
  31. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Decorators/Retry.py +0 -0
  32. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Decorators/SqlDataNormalizer.py +0 -0
  33. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Decorators/__init__.py +0 -0
  34. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Encryptor/Argon2.py +0 -0
  35. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Encryptor/Bcrypt.py +0 -0
  36. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Encryptor/Jwt.py +0 -0
  37. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Encryptor/Rsa.py +0 -0
  38. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Encryptor/Sha512.py +0 -0
  39. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Encryptor/__init__.py +0 -0
  40. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Exception/__init__.py +0 -0
  41. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Graphql/Extensions/__init__.py +0 -0
  42. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Graphql/_Helpers/_AuditHelper.py +0 -0
  43. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Graphql/_Helpers/__init__.py +0 -0
  44. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Graphql/__init__.py +0 -0
  45. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Helpers/AuditDispatcher.py +0 -0
  46. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Helpers/KeyVaultService.py +0 -0
  47. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Helpers/__init__.py +0 -0
  48. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Http/APIClient.py +0 -0
  49. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Http/_Exceptions.py +0 -0
  50. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Http/__init__.py +0 -0
  51. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Messaging/AzureServiceBus.py +0 -0
  52. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Messaging/Kafka.py +0 -0
  53. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Messaging/RabbitMQ.py +0 -0
  54. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Messaging/__init__.py +0 -0
  55. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Models/AuditConfig.py +0 -0
  56. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Models/Catalog.py +0 -0
  57. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Models/Legacy.py +0 -0
  58. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Models/Response.py +0 -0
  59. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Models/_Audit.py +0 -0
  60. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Models/__init__.py +0 -0
  61. {encryptors-2.35 → encryptors-2.37}/src/Osdental/RedisCache/Redis.py +0 -0
  62. {encryptors-2.35 → encryptors-2.37}/src/Osdental/RedisCache/__init__.py +0 -0
  63. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Rest/Context/RequestContext.py +0 -0
  64. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Rest/Context/__init__.py +0 -0
  65. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Rest/Middlewares/RequestContextMiddleware.py +0 -0
  66. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Rest/Middlewares/__init__.py +0 -0
  67. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Rest/__init__.py +0 -0
  68. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Enums/Constant.py +0 -0
  69. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Enums/FileType.py +0 -0
  70. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Enums/GrahpqlOperation.py +0 -0
  71. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Enums/Profile.py +0 -0
  72. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Enums/__init__.py +0 -0
  73. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Logger.py +0 -0
  74. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Utils/CaseConverter.py +0 -0
  75. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Utils/CodeGenerator.py +0 -0
  76. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Utils/DataNormalizer.py +0 -0
  77. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Utils/DataUtils.py +0 -0
  78. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Utils/DateUtils.py +0 -0
  79. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Utils/HashValidator.py +0 -0
  80. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Utils/Mapper.py +0 -0
  81. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Utils/PasswordGenerator.py +0 -0
  82. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Utils/QueryGenerator.py +0 -0
  83. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Utils/TextProcessor.py +0 -0
  84. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/Utils/__init__.py +0 -0
  85. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Shared/__init__.py +0 -0
  86. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
  87. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Storage/S3Storage.py +0 -0
  88. {encryptors-2.35 → encryptors-2.37}/src/Osdental/Storage/__init__.py +0 -0
  89. {encryptors-2.35 → encryptors-2.37}/src/Osdental/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Encryptors
3
- Version: 2.35
3
+ Version: 2.37
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
 
3
3
  setup(
4
4
  name="Encryptors",
5
- version="2.35",
5
+ version="2.37",
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.35
3
+ Version: 2.37
4
4
  Summary: End-to-end algorithm library
5
5
  Author: OSDental LLC
6
6
  Author-email: support@osdental.ai
@@ -28,6 +28,7 @@ src/Osdental/Exception/__init__.py
28
28
  src/Osdental/Graphql/__init__.py
29
29
  src/Osdental/Graphql/Extensions/AuditExtension.py
30
30
  src/Osdental/Graphql/Extensions/__init__.py
31
+ src/Osdental/Graphql/Models/__init__.py
31
32
  src/Osdental/Graphql/_Exceptions/__init__.py
32
33
  src/Osdental/Graphql/_Helpers/_AuditHelper.py
33
34
  src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py
@@ -0,0 +1,118 @@
1
+ import os
2
+ import sys
3
+ import subprocess
4
+ import platform
5
+ import click
6
+ from Osdental.Shared.Logger import logger
7
+ from Osdental.Shared.Enums.Message import Message
8
+
9
+
10
+ @click.group()
11
+ def cli():
12
+ """Custom commands to manage the project."""
13
+ pass
14
+
15
+ @cli.command()
16
+ def clean():
17
+ """Borrar todos los __pycache__."""
18
+ if platform.system() == 'Windows':
19
+ subprocess.run('for /d /r . %d in (__pycache__) do @if exist "%d" rd /s/q "%d"', shell=True)
20
+ else:
21
+ subprocess.run("find . -name '__pycache__' -type d -exec rm -rf {} +", shell=True)
22
+
23
+ logger.info(Message.PYCACHE_CLEANUP_SUCCESS_MSG)
24
+
25
+ @cli.command()
26
+ @click.argument('port')
27
+ def start(port: int):
28
+ """Start the FastAPI server.."""
29
+ try:
30
+ subprocess.run(['uvicorn', 'app:app', '--port', str(port), '--reload'], check=True)
31
+ except subprocess.CalledProcessError as e:
32
+ logger.error(f'{Message.SERVER_NETWORK_ACCESS_ERROR_MSG}: {e}')
33
+
34
+ @cli.command()
35
+ @click.argument('port')
36
+ def serve(port: int):
37
+ """Set up the FastAPI server accessible from any machine."""
38
+ try:
39
+ subprocess.run(['uvicorn', 'app:app', '--host', '0.0.0.0', '--port', str(port), '--reload'], check=True)
40
+ except subprocess.CalledProcessError as e:
41
+ logger.error(f'{Message.SERVER_NETWORK_ACCESS_ERROR_MSG}: {e}')
42
+
43
+ @cli.command("clean-redis")
44
+ @click.argument('redis_env')
45
+ async def clean_redis(redis_env: str):
46
+ try:
47
+ from Osdental.RedisCache.Redis import RedisCacheAsync
48
+ redis_url = os.getenv(redis_env)
49
+ if not redis_url:
50
+ logger.warning(f'Environment variable not found: {redis_env}')
51
+ return
52
+
53
+ redis = RedisCacheAsync(redis_url=redis_url)
54
+ await redis.flush()
55
+ logger.info(Message.REDIS_CLEANUP_SUCCESS_MSG)
56
+ except Exception as e:
57
+ logger.error(f'{Message.REDIS_CLEANUP_ERROR_MSG}: {e}')
58
+
59
+ @cli.command(name='proto-files')
60
+ @click.argument('name')
61
+ def proto_files(name: str):
62
+ proto_dir = os.path.join('src', 'Infrastructure', 'Grpc', 'Proto').replace('\\', '/')
63
+ gen_dir = os.path.join('src', 'Infrastructure', 'Grpc', 'Generated').replace('\\', '/')
64
+
65
+ proto_path = os.path.join(proto_dir, f'{name}.proto').replace('\\', '/')
66
+ common_path = os.path.join(proto_dir, 'Common.proto').replace('\\', '/')
67
+
68
+ common_py = os.path.join(gen_dir, 'Common_pb2.py').replace('\\', '/')
69
+ common_grpc_py = os.path.join(gen_dir, 'Common_pb2_grpc.py').replace('\\', '/')
70
+
71
+ proto_files = [proto_path]
72
+ if not (os.path.exists(common_py) and os.path.exists(common_grpc_py)):
73
+ proto_files.append(common_path)
74
+
75
+ cmd = [
76
+ sys.executable, "-m", "grpc_tools.protoc",
77
+ f"-I={proto_dir}",
78
+ f"--python_out={gen_dir}",
79
+ f"--grpc_python_out={gen_dir}",
80
+ *proto_files
81
+ ]
82
+
83
+ subprocess.run(cmd, shell=False, check=True)
84
+ logger.info(Message.PROTO_FILES_GENERATED_MSG)
85
+
86
+ @cli.command('run-server')
87
+ @click.option('--host', default="0.0.0.0", help='Host where to set up the server')
88
+ @click.option('--port', default=5000, help='Server port')
89
+ @click.option('--workers', default=4, help='Number of workers')
90
+ def run_server(host, port, workers):
91
+ """
92
+ Launch the application with uvicorn on Windows or gunicorn on other systems
93
+ :host
94
+ :port
95
+ :workers
96
+ """
97
+ if sys.platform.startswith('win'):
98
+ cmd = [
99
+ sys.executable, '-m', 'uvicorn',
100
+ 'app:app',
101
+ '--host', host,
102
+ '--port', str(port),
103
+ '--workers', str(workers)
104
+ ]
105
+ else:
106
+ cmd = [
107
+ sys.executable, '-m', 'gunicorn',
108
+ 'app:app',
109
+ '-k', 'uvicorn.workers.UvicornWorker',
110
+ '--bind', f'{host}:{port}',
111
+ '--workers', str(workers)
112
+ ]
113
+
114
+ logger.info(f'Running: {' '.join(cmd)}')
115
+ subprocess.run(cmd)
116
+
117
+ if __name__ == "__main__":
118
+ cli()
@@ -1,7 +1,7 @@
1
1
  from typing import Any
2
2
  from sqlalchemy import text
3
3
  from sqlalchemy.ext.asyncio import AsyncSession
4
- from Osdental.Exception.ControlledException import DatabaseException
4
+ from Osdental.Exception.ControlledException import DatabaseException, OSDException
5
5
  from Osdental.Shared.Utils.DataNormalizer import normalize
6
6
 
7
7
  class BaseRepository:
@@ -23,6 +23,7 @@ class BaseRepository:
23
23
  many: bool = False,
24
24
  as_dict: bool = False
25
25
  ) -> Any:
26
+
26
27
  result = await self.async_session.execute(
27
28
  text(query),
28
29
  params or {}
@@ -34,13 +35,11 @@ class BaseRepository:
34
35
  else result.mappings().first()
35
36
  )
36
37
 
37
- if not as_dict or data is None:
38
- return data
39
-
40
- if many:
38
+ if as_dict and data is not None:
41
39
  return normalize(data)
42
40
 
43
- return normalize(data)
41
+ return data
42
+
44
43
 
45
44
  async def _execute_command(
46
45
  self,
@@ -48,8 +47,10 @@ class BaseRepository:
48
47
  params: dict | None = None,
49
48
  *,
50
49
  validate: bool = True,
51
- success_codes: str | int | tuple[str | int, ...] | None = None
52
- ):
50
+ success_codes: str | int | tuple[str | int, ...] | None = None,
51
+ as_dict: bool = False
52
+ ) -> Any:
53
+
53
54
  result = await self.async_session.execute(
54
55
  text(query),
55
56
  params or {}
@@ -58,7 +59,8 @@ class BaseRepository:
58
59
  row = result.mappings().first()
59
60
 
60
61
  if not validate or not row:
61
- return row
62
+ return normalize(row) if as_dict else row
63
+
62
64
 
63
65
  status_code = self._get_status_value(
64
66
  row,
@@ -82,17 +84,29 @@ class BaseRepository:
82
84
  error="Status code missing",
83
85
  status_code=500
84
86
  )
85
- return row
86
87
 
87
- # Normalizamos a tupla
88
- if not isinstance(success_codes, tuple):
89
- success_codes = (success_codes,)
88
+ else:
89
+
90
+ if not isinstance(success_codes, tuple):
91
+ success_codes = (success_codes,)
92
+
93
+ if status_code not in success_codes:
94
+ raise DatabaseException(
95
+ message=status_message,
96
+ error=status_message,
97
+ status_code=status_code
98
+ )
90
99
 
91
- if status_code not in success_codes:
92
- raise DatabaseException(
93
- message=status_message,
94
- error=status_message,
95
- status_code=status_code
96
- )
100
+ return normalize(row) if as_dict else row
101
+
97
102
 
98
- return row
103
+ async def _execute_scalar(
104
+ self,
105
+ query: str,
106
+ params: dict | None = None
107
+ ) -> Any | None:
108
+ result = await self.async_session.execute(
109
+ text(query),
110
+ params or {}
111
+ )
112
+ return result.scalar_one_or_none()
@@ -2,15 +2,13 @@ import inspect
2
2
  import copy
3
3
  from functools import wraps
4
4
  from typing import Callable, Dict, Any
5
- from graphql import GraphQLResolveInfo
6
5
  from Osdental.Models.Token import AuthToken
7
6
  from Osdental.Models.Response import Response
8
7
  from Osdental.Shared.Enums.Profile import Profile
9
- from Osdental.Exception.ControlledException import OSDException
8
+ from Osdental.Exception.ControlledException import OSDException, AccessDeniedException
10
9
  from Osdental.Encryptor.Aes import AES
10
+ from Osdental.Graphql.Models import BaseGraphQLContext
11
11
 
12
-
13
- aes = AES()
14
12
  def secure_resolver(action = None):
15
13
  def decorator(func: Callable):
16
14
 
@@ -18,25 +16,25 @@ def secure_resolver(action = None):
18
16
  accepts_data = "data" in signature.parameters
19
17
 
20
18
  @wraps(func)
21
- async def wrapper(obj: Any, info: GraphQLResolveInfo, **kwargs: Dict):
19
+ async def wrapper(obj: Any, context: BaseGraphQLContext, **kwargs: Dict[str, Any]):
22
20
  try:
23
- token: AuthToken = info.context.token
24
- payload = getattr(info.context, "decrypted_payload", None)
25
- aes_key = getattr(info.context, "aes_key", None)
21
+ token: AuthToken = context.token
22
+ payload = getattr(context, "decrypted_payload", None)
23
+ aes_auth = getattr(context, "aes_auth", None)
26
24
 
27
25
  if action:
28
26
  if Profile(token.abbreviation) not in action.allowed_roles:
29
- raise OSDException("DB_ERROR_AUTH", "You are not authorized to perform this action.")
27
+ raise AccessDeniedException(error="The user's profile is not allowed to perform this action.")
30
28
 
31
29
  if accepts_data and payload:
32
30
  kwargs["data"] = payload
33
31
 
34
- result: Response = await func(obj, info, **kwargs)
32
+ result: Response = await func(obj, context, **kwargs)
35
33
 
36
- info.context.audit_plain_response = copy.deepcopy(result)
34
+ context.audit_plain_response = copy.deepcopy(result)
37
35
 
38
36
  if result.data is not None:
39
- result.data = aes.encrypt(aes_key, result.data)
37
+ result.data = AES.encrypt(aes_auth, result.data)
40
38
 
41
39
  return result.send()
42
40
 
@@ -46,7 +44,7 @@ def secure_resolver(action = None):
46
44
  message=getattr(e, "message", "Could not process request."),
47
45
  error=f"{type(e).__name__}: {str(e)}"
48
46
  )
49
- info.context.audit_plain_response = result
47
+ context.audit_plain_response = result
50
48
  return result.send()
51
49
 
52
50
  return wrapper
@@ -10,9 +10,8 @@ from Osdental.Shared.Enums.Constant import Constant
10
10
 
11
11
  class AES:
12
12
 
13
- def __init__(self):
14
- self.IV_LENGTH = 32
15
- self.TAG_LENGTH = 16
13
+ IV_LENGTH = 32
14
+ TAG_LENGTH = 16
16
15
 
17
16
  @staticmethod
18
17
  def generate_key() -> str:
@@ -20,7 +19,8 @@ class AES:
20
19
  key = os.urandom(32)
21
20
  return base64.b64encode(key).decode(Constant.DEFAULT_ENCODING)
22
21
 
23
- def encrypt(self, aes_key:str, data:Dict[str,str] | str | List[Dict[str,str]]) -> str:
22
+ @classmethod
23
+ def encrypt(cls, aes_key:str, data:Dict[str,str] | str | List[Dict[str,str]]) -> str:
24
24
  """
25
25
  Encrypts data using AES-GCM.
26
26
  Supports dictionary, string, or list inputs.
@@ -34,7 +34,7 @@ class AES:
34
34
 
35
35
  try:
36
36
  key = base64.b64decode(aes_key)
37
- iv = os.urandom(self.IV_LENGTH)
37
+ iv = os.urandom(cls.IV_LENGTH)
38
38
  if isinstance(data, (dict, list)):
39
39
  json_data = json.dumps(data)
40
40
  else:
@@ -52,8 +52,8 @@ class AES:
52
52
  logger.error(f'Unexpected AES encryption error: {str(e)}')
53
53
  raise AESEncryptException(error=str(e))
54
54
 
55
-
56
- def decrypt(self, aes_key:str, encrypted_data:str, silent:bool = False):
55
+ @classmethod
56
+ def decrypt(cls, aes_key:str, encrypted_data:str, silent:bool = False):
57
57
  """
58
58
  Decrypts data using AES-GCM.
59
59
  Expects encrypted data to represent either a JSON object (dict) or a plain string.
@@ -65,9 +65,9 @@ class AES:
65
65
  try:
66
66
  key = base64.b64decode(aes_key)
67
67
  encrypted_data = base64.b64decode(encrypted_data)
68
- iv = encrypted_data[:self.IV_LENGTH]
69
- tag = encrypted_data[-self.TAG_LENGTH:]
70
- ciphertext = encrypted_data[self.IV_LENGTH:-self.TAG_LENGTH]
68
+ iv = encrypted_data[:cls.IV_LENGTH]
69
+ tag = encrypted_data[-cls.TAG_LENGTH:]
70
+ ciphertext = encrypted_data[cls.IV_LENGTH:-cls.TAG_LENGTH]
71
71
  cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag))
72
72
  decryptor = cipher.decryptor()
73
73
  plaintext = decryptor.update(ciphertext) + decryptor.finalize()
@@ -24,6 +24,15 @@ class UnauthorizedException(OSDException):
24
24
  ):
25
25
  super().__init__(message=message, error=error, status_code=status_code)
26
26
 
27
+ class AccessDeniedException(OSDException):
28
+ def __init__(
29
+ self,
30
+ message: str = Message.ACCESS_DENIED_MSG,
31
+ error: str = None,
32
+ status_code: str = Code.INVALID_REQUEST_PARAMS_CODE
33
+ ):
34
+ super().__init__(message=message, error=error, status_code=status_code)
35
+
27
36
  class RequestDataException(OSDException):
28
37
  def __init__(
29
38
  self,
@@ -7,6 +7,7 @@ from Osdental.Rest.Context.RequestContext import (
7
7
  current_context,
8
8
  RequestContext
9
9
  )
10
+ from Osdental.Graphql.Models import BaseGraphQLContext
10
11
  from Osdental.Graphql._Exceptions import AESKeyNotFound
11
12
  from Osdental.Shared.Enums.Constant import Constant
12
13
 
@@ -25,27 +26,31 @@ class AuditExtension(Extension):
25
26
  self.errors = None
26
27
  self.request_payload = None
27
28
  self.result = None
28
- self.aes = AES()
29
29
 
30
30
  async def resolve(self, next_, root, info, **kwargs):
31
31
 
32
32
  if not self.request_payload:
33
- context = info.context
33
+ context: BaseGraphQLContext = info.context
34
34
 
35
- request = info.context.request
35
+ request = context.request
36
36
  self.request = request
37
37
 
38
- body = request.state.graphql_body
38
+ body = await request.json()
39
+ headers = request.headers
39
40
 
40
41
  container = context.container
41
-
42
42
  token_service = container.token_service
43
43
 
44
- aes_key = request.app.state.aes_auth
45
- if not aes_key:
46
- raise AESKeyNotFound("Could not find authorization key Aes.")
44
+ aes_auth = request.app.state.aes_auth
45
+ aes_user = request.app.state.aes_user
46
+
47
+ if not aes_auth:
48
+ raise AESKeyNotFound("AES user key is missing.", "aes_auth")
49
+
50
+ if not aes_user:
51
+ raise AESKeyNotFound("AES authorization key is missing.", "aes_user")
47
52
 
48
- original_token = await token_service.authenticate(request)
53
+ original_token = await token_service.authenticate(headers, aes_user)
49
54
 
50
55
  variables = body.get("variables") or {}
51
56
  encrypted_payload = variables.get("data")
@@ -54,7 +59,7 @@ class AuditExtension(Extension):
54
59
 
55
60
  if encrypted_payload:
56
61
  try:
57
- decrypted_payload = self.aes.decrypt(aes_key, encrypted_payload)
62
+ decrypted_payload = AES.decrypt(aes_auth, encrypted_payload)
58
63
 
59
64
  try:
60
65
  decrypted_payload = json.loads(decrypted_payload)
@@ -81,7 +86,7 @@ class AuditExtension(Extension):
81
86
 
82
87
  context.decrypted_payload = decrypted_payload
83
88
  context.token = token
84
- context.aes_key = aes_key
89
+ context.aes_auth = aes_auth
85
90
 
86
91
  result = next_(root, info, **kwargs)
87
92
 
@@ -0,0 +1,14 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional, Any
3
+ from fastapi import Request
4
+ from Osdental.Models.Token import AuthToken
5
+
6
+ @dataclass
7
+ class BaseGraphQLContext:
8
+ request: Request
9
+ container: Any
10
+ # valores agregados durante ejecución (AuditExtention)
11
+ token: Optional[AuthToken] = None
12
+ decrypted_payload: Optional[dict] = None
13
+ aes_auth: Optional[str] = None
14
+ audit_plain_response: Optional[Any] = None
@@ -3,6 +3,6 @@ from Osdental.Exception.ControlledException import OSDException
3
3
  class AESKeyNotFound(OSDException):
4
4
  def __init__(
5
5
  self,
6
- error: str = None,
6
+ error: str = None
7
7
  ):
8
8
  super().__init__(error=error)
@@ -0,0 +1,11 @@
1
+ from starlette.datastructures import Headers
2
+
3
+ class ExtractAuthToken:
4
+
5
+ @staticmethod
6
+ def get_auth_token(headers: Headers) -> str:
7
+ authorization = headers.get("authorization")
8
+ if not authorization or not authorization.startswith("Bearer "):
9
+ raise ValueError("Missing Bearer token")
10
+
11
+ return authorization.split(" ")[1]
@@ -14,13 +14,16 @@ class TenantPolicy:
14
14
  decrypted_payload: Dict[str, Any],
15
15
  operation_type: str
16
16
  ) -> AuthToken:
17
- aes = AES()
18
- # Solo aplicamos cambios si es QUERY
19
- if operation_type != OperationType.QUERY.value:
20
- return token # devolver token tal cual
21
17
 
18
+ # Set original idExternalEnterprise
19
+ token.base_id_external_enterprise = token.id_external_enterprise
22
20
  # SUPER ADMIN / OSDEL ADMIN -> UUID 0
23
- if token.abbreviation.startswith(("SPAU", "OSDA")):
21
+ should_use_zero_uuid = (
22
+ token.abbreviation.startswith(("SPAU", "OSDA"))
23
+ and operation_type == OperationType.QUERY
24
+ )
25
+
26
+ if should_use_zero_uuid:
24
27
  token.id_external_enterprise = str(UUID(int=0))
25
28
  return token
26
29
 
@@ -28,13 +31,13 @@ class TenantPolicy:
28
31
  if token.abbreviation.startswith("OSDMK"):
29
32
  dynamic_client_id = headers.get("dynamicClientId")
30
33
  if dynamic_client_id:
31
- decrypted_mk_id = aes.decrypt(token.aes_key_auth, dynamic_client_id)
34
+ decrypted_mk_id = AES.decrypt(token.aes_key_auth, dynamic_client_id)
32
35
  token.id_external_enterprise = decrypted_mk_id
33
36
  token.mk_id_external_enterprise = decrypted_mk_id
34
37
  return token
35
38
 
36
39
  # If it comes by request, it is taken as priority
37
- external_enterprise_req = decrypted_payload.get('idExternalEnterprise')
40
+ external_enterprise_req = decrypted_payload.get("idExternalEnterprise")
38
41
  if external_enterprise_req and token:
39
42
  token.id_external_enterprise = external_enterprise_req
40
43
 
@@ -1,4 +1,4 @@
1
- from fastapi import Request
1
+ from starlette.datastructures import Headers
2
2
  from Osdental.Encryptor.Aes import AES
3
3
  from Osdental.Encryptor.Jwt import JWT
4
4
  from Osdental.Models.Token import AuthToken
@@ -9,23 +9,20 @@ class TokenService:
9
9
  def __init__(self, jwt_user_key: str, auth_validator):
10
10
  self.jwt_user_key = jwt_user_key
11
11
  self.auth_validator = auth_validator
12
- self.aes = AES()
13
12
 
14
- async def authenticate(self, request: Request) -> AuthToken:
15
- encrypted_token = ExtractAuthToken.get_auth_token(request)
13
+ async def authenticate(self, headers: Headers, aes_user: str) -> AuthToken:
14
+ encrypted_token = ExtractAuthToken.get_auth_token(headers)
16
15
 
17
- aes_user = request.app.state.aes_user
18
-
19
- user_token = self.aes.decrypt(aes_user, encrypted_token)
16
+ user_token = AES.decrypt(aes_user, encrypted_token)
20
17
  payload = JWT.extract_payload(user_token, self.jwt_user_key)
21
18
  token = AuthToken(**payload)
22
19
 
23
20
  # Validate via RPC
24
- request = {
21
+ paylod = {
25
22
  'idToken': token.id_token,
26
23
  'idUser': token.id_user
27
24
  }
28
- is_valid = await self.auth_validator.validate_auth_token(request)
25
+ is_valid = await self.auth_validator.validate_auth_token(paylod)
29
26
  if not is_valid:
30
27
  raise ValueError("You are not authorized to access this portal.")
31
28
 
@@ -18,8 +18,6 @@ class AuthToken(BaseModel):
18
18
  access_token: Optional[str] = Field(default=None, alias="accessToken")
19
19
  base_id_external_enterprise: Optional[str] = Field(default=None, alias="baseIdExternalEnterprise")
20
20
  mk_id_external_enterprise: Optional[str] = Field(default=None, alias="mkIdExternalEnterprise")
21
- jwt_user_key: Optional[str] = Field(default=None, alias="jwtUserKey")
22
- legacy: Optional[Legacy] = Field(default=None, alias="legacy")
23
21
 
24
22
  class ConfigDict:
25
23
  populate_by_name = True
@@ -8,6 +8,7 @@ class Code(StrEnum):
8
8
  APP_ERROR_CODE = 'DB_ERROR_CONTROLLED'
9
9
  UNEXPECTED_ERROR_CODE = 'DB_ERROR_UNEXPECTED'
10
10
  INVALID_REQUEST_PARAMS_CODE = 'DB_WARNING_INVALID_REQUEST_PARAMS'
11
+ ACCESS_DENIED_CODE = "DB_WARNING_ACCESS_DENIED_CODE"
11
12
  DATABASE_ERROR_CODE = 'DB_WARNING_DATABASE_ERROR'
12
13
  DATABASE_CONNECTION_ERROR_CODE = 'DB_ERROR_DATABASE_CONNECTION'
13
14
  RSA_ERROR_CODE = 'DB_ERROR_RSA'
@@ -1,6 +1,7 @@
1
1
  from enum import StrEnum
2
2
 
3
3
  class Message(StrEnum):
4
+ ACCESS_DENIED_MSG = "You do not have the necessary privileges for this operation."
4
5
  UNEXPECTED_ERROR_MSG = 'Something went wrong while processing your request. Please try again later.'
5
6
  PORTAL_ACCESS_RESTRICTED_MSG = 'You are not authorized to access this portal.'
6
7
  PROCESS_SUCCESS_MSG = 'Process executed successfully.'
@@ -20,7 +21,6 @@ class Message(StrEnum):
20
21
  AES_KEY_USER_REQUIRED_MSG = 'AES key user cannot be empty.'
21
22
  AES_KEY_AUTH_REQUIRED_MSG = 'AES key auth cannot be empty.'
22
23
  MISSING_FIELD_ERROR_MSG = 'A required field is missing. Please review the data.'
23
- ID_CDATA_INTEGRATION_REQUIRED = 'Cdata integration cannot be empty or null'
24
24
  EXP_TIME_REQUIRED = 'Expiration time (cdata integration) cannot be empty or null'
25
25
  KEY_PRIVATE_REQUIRED = 'Key private cannot be empty or null'
26
26
  SUB_ACCOUNT_REQUIRED = 'Sub account parameter is required'
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import mimetypes
3
3
  from Osdental.Exception.ControlledException import ValidationDataException
4
- from Osdental.Shared.Message import Message
4
+ from Osdental.Shared.Enums.Message import Message
5
5
  from Osdental.Shared.Enums.FileType import FileType
6
6
 
7
7
  class FileMetaData: