orionis 0.546.0__py3-none-any.whl → 0.548.0__py3-none-any.whl

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 (160) hide show
  1. orionis/console/base/scheduler_event_listener.py +0 -17
  2. orionis/console/contracts/schedule_event_listener.py +0 -18
  3. orionis/foundation/config/app/entities/app.py +3 -2
  4. orionis/foundation/config/app/enums/ciphers.py +5 -19
  5. orionis/foundation/config/session/entities/session.py +2 -2
  6. orionis/metadata/framework.py +1 -1
  7. orionis/services/encrypter/encrypter.py +115 -0
  8. orionis/services/environment/dynamic/caster.py +35 -27
  9. orionis/services/environment/key/key_generator.py +32 -11
  10. {orionis-0.546.0.dist-info → orionis-0.548.0.dist-info}/METADATA +1 -1
  11. {orionis-0.546.0.dist-info → orionis-0.548.0.dist-info}/RECORD +14 -159
  12. {orionis-0.546.0.dist-info → orionis-0.548.0.dist-info}/top_level.txt +0 -1
  13. orionis-0.546.0.dist-info/zip-safe +0 -1
  14. tests/container/__init__.py +0 -0
  15. tests/container/context/__init__.py +0 -0
  16. tests/container/context/test_manager.py +0 -38
  17. tests/container/context/test_scope.py +0 -32
  18. tests/container/core/__init__.py +0 -0
  19. tests/container/core/test_advanced_async.py +0 -234
  20. tests/container/core/test_async_optimizations.py +0 -268
  21. tests/container/core/test_container.py +0 -453
  22. tests/container/core/test_singleton.py +0 -122
  23. tests/container/core/test_thread_safety.py +0 -90
  24. tests/container/entities/__init__.py +0 -0
  25. tests/container/entities/test_binding.py +0 -242
  26. tests/container/enums/__init__.py +0 -0
  27. tests/container/enums/test_lifetimes.py +0 -97
  28. tests/container/facades/__init__.py +0 -0
  29. tests/container/facades/test_facade.py +0 -78
  30. tests/container/mocks/__init__.py +0 -0
  31. tests/container/mocks/mock_advanced_async.py +0 -332
  32. tests/container/mocks/mock_async_optimizations.py +0 -407
  33. tests/container/mocks/mock_auto_resolution.py +0 -192
  34. tests/container/mocks/mock_complex_classes.py +0 -792
  35. tests/container/mocks/mock_simple_classes.py +0 -98
  36. tests/container/providers/__init__.py +0 -0
  37. tests/container/providers/test_providers.py +0 -55
  38. tests/container/validators/__init__.py +0 -0
  39. tests/container/validators/test_implements.py +0 -186
  40. tests/container/validators/test_is_abstract_class.py +0 -147
  41. tests/container/validators/test_is_callable.py +0 -102
  42. tests/container/validators/test_is_concrete_class.py +0 -160
  43. tests/container/validators/test_is_instance.py +0 -150
  44. tests/container/validators/test_is_not_subclass.py +0 -49
  45. tests/container/validators/test_is_subclass.py +0 -178
  46. tests/container/validators/test_is_valid_alias.py +0 -147
  47. tests/container/validators/test_lifetime.py +0 -106
  48. tests/example/__init__.py +0 -0
  49. tests/example/test_example.py +0 -725
  50. tests/foundation/__init__.py +0 -0
  51. tests/foundation/config/__init__.py +0 -0
  52. tests/foundation/config/app/__init__.py +0 -0
  53. tests/foundation/config/app/test_foundation_config_app.py +0 -262
  54. tests/foundation/config/auth/__init__.py +0 -0
  55. tests/foundation/config/auth/test_foundation_config_auth.py +0 -29
  56. tests/foundation/config/cache/__init__.py +0 -0
  57. tests/foundation/config/cache/test_foundation_config_cache.py +0 -143
  58. tests/foundation/config/cache/test_foundation_config_cache_file.py +0 -126
  59. tests/foundation/config/cache/test_foundation_config_cache_stores.py +0 -156
  60. tests/foundation/config/cors/__init__.py +0 -0
  61. tests/foundation/config/cors/test_foundation_config_cors.py +0 -190
  62. tests/foundation/config/database/__init__.py +0 -0
  63. tests/foundation/config/database/test_foundation_config_database.py +0 -158
  64. tests/foundation/config/database/test_foundation_config_database_connections.py +0 -203
  65. tests/foundation/config/database/test_foundation_config_database_mysql.py +0 -354
  66. tests/foundation/config/database/test_foundation_config_database_oracle.py +0 -288
  67. tests/foundation/config/database/test_foundation_config_database_pgsql.py +0 -257
  68. tests/foundation/config/database/test_foundation_config_database_sqlite.py +0 -207
  69. tests/foundation/config/filesystems/__init__.py +0 -0
  70. tests/foundation/config/filesystems/test_foundation_config_filesystems.py +0 -160
  71. tests/foundation/config/filesystems/test_foundation_config_filesystems_aws.py +0 -189
  72. tests/foundation/config/filesystems/test_foundation_config_filesystems_disks.py +0 -184
  73. tests/foundation/config/filesystems/test_foundation_config_filesystems_local.py +0 -143
  74. tests/foundation/config/filesystems/test_foundation_config_filesystems_public.py +0 -184
  75. tests/foundation/config/logging/__init__.py +0 -0
  76. tests/foundation/config/logging/test_foundation_config_logging.py +0 -112
  77. tests/foundation/config/logging/test_foundation_config_logging_channels.py +0 -246
  78. tests/foundation/config/logging/test_foundation_config_logging_chunked.py +0 -217
  79. tests/foundation/config/logging/test_foundation_config_logging_daily.py +0 -220
  80. tests/foundation/config/logging/test_foundation_config_logging_hourly.py +0 -196
  81. tests/foundation/config/logging/test_foundation_config_logging_monthly.py +0 -214
  82. tests/foundation/config/logging/test_foundation_config_logging_stack.py +0 -178
  83. tests/foundation/config/logging/test_foundation_config_logging_weekly.py +0 -224
  84. tests/foundation/config/mail/__init__.py +0 -0
  85. tests/foundation/config/mail/test_foundation_config_mail.py +0 -145
  86. tests/foundation/config/mail/test_foundation_config_mail_file.py +0 -97
  87. tests/foundation/config/mail/test_foundation_config_mail_mailers.py +0 -106
  88. tests/foundation/config/mail/test_foundation_config_mail_smtp.py +0 -146
  89. tests/foundation/config/queue/__init__.py +0 -0
  90. tests/foundation/config/queue/test_foundation_config_queue.py +0 -88
  91. tests/foundation/config/queue/test_foundation_config_queue_brokers.py +0 -72
  92. tests/foundation/config/queue/test_foundation_config_queue_database.py +0 -134
  93. tests/foundation/config/root/__init__.py +0 -0
  94. tests/foundation/config/root/test_foundation_config_root_paths.py +0 -112
  95. tests/foundation/config/session/__init__.py +0 -0
  96. tests/foundation/config/session/test_foundation_config_session.py +0 -213
  97. tests/foundation/config/startup/__init__.py +0 -0
  98. tests/foundation/config/startup/test_foundation_config_startup.py +0 -202
  99. tests/foundation/config/testing/__init__.py +0 -0
  100. tests/foundation/config/testing/test_foundation_config_testing.py +0 -235
  101. tests/metadata/__init__.py +0 -0
  102. tests/metadata/test_metadata_framework.py +0 -140
  103. tests/metadata/test_metadata_package.py +0 -139
  104. tests/services/__init__.py +0 -0
  105. tests/services/asynchrony/__init__.py +0 -0
  106. tests/services/asynchrony/test_services_asynchrony_coroutine.py +0 -85
  107. tests/services/environment/__init__.py +0 -0
  108. tests/services/environment/test_services_environment.py +0 -226
  109. tests/services/introspection/__init__.py +0 -0
  110. tests/services/introspection/dependencies/__init__.py +0 -0
  111. tests/services/introspection/dependencies/mocks/__init__.py +0 -0
  112. tests/services/introspection/dependencies/mocks/mock_user.py +0 -30
  113. tests/services/introspection/dependencies/mocks/mock_user_controller.py +0 -27
  114. tests/services/introspection/dependencies/mocks/mock_users_permissions.py +0 -41
  115. tests/services/introspection/dependencies/test_reflect_dependencies.py +0 -261
  116. tests/services/introspection/reflection/__init__.py +0 -0
  117. tests/services/introspection/reflection/mock/__init__.py +0 -0
  118. tests/services/introspection/reflection/mock/fake_reflect_instance.py +0 -1115
  119. tests/services/introspection/reflection/test_reflection_abstract.py +0 -1011
  120. tests/services/introspection/reflection/test_reflection_callable.py +0 -206
  121. tests/services/introspection/reflection/test_reflection_concrete.py +0 -952
  122. tests/services/introspection/reflection/test_reflection_instance.py +0 -1233
  123. tests/services/introspection/reflection/test_reflection_module.py +0 -567
  124. tests/services/introspection/test_reflection.py +0 -462
  125. tests/services/log/__init__.py +0 -0
  126. tests/services/log/test_log.py +0 -97
  127. tests/services/system/__init__.py +0 -0
  128. tests/services/system/test_services_system_imports.py +0 -204
  129. tests/services/system/test_services_system_workers.py +0 -131
  130. tests/support/__init__.py +0 -0
  131. tests/support/entities/__init__.py +0 -0
  132. tests/support/entities/mock_dataclass.py +0 -40
  133. tests/support/entities/test_base.py +0 -64
  134. tests/support/patterns/__init__.py +0 -0
  135. tests/support/patterns/singleton/__init__.py +0 -0
  136. tests/support/patterns/singleton/test_patterns_singleton.py +0 -39
  137. tests/support/standard/__init__.py +0 -0
  138. tests/support/standard/test_services_std.py +0 -226
  139. tests/support/wrapper/__init__.py +0 -0
  140. tests/support/wrapper/test_services_wrapper_docdict.py +0 -202
  141. tests/testing/__init__.py +0 -0
  142. tests/testing/cases/__init__.py +0 -0
  143. tests/testing/cases/test_testing_asynchronous.py +0 -63
  144. tests/testing/cases/test_testing_synchronous.py +0 -57
  145. tests/testing/entities/__init__.py +0 -0
  146. tests/testing/entities/test_testing_result.py +0 -146
  147. tests/testing/enums/__init__.py +0 -0
  148. tests/testing/enums/test_testing_status.py +0 -63
  149. tests/testing/output/__init__.py +0 -0
  150. tests/testing/output/test_testing_dumper.py +0 -29
  151. tests/testing/output/test_testing_printer.py +0 -42
  152. tests/testing/records/__init__.py +0 -0
  153. tests/testing/records/test_testing_records.py +0 -171
  154. tests/testing/test_testing_unit.py +0 -164
  155. tests/testing/validators/__init__.py +0 -0
  156. tests/testing/validators/test_testing_validators.py +0 -392
  157. tests/testing/view/__init__.py +0 -0
  158. tests/testing/view/test_render.py +0 -30
  159. {orionis-0.546.0.dist-info → orionis-0.548.0.dist-info}/WHEEL +0 -0
  160. {orionis-0.546.0.dist-info → orionis-0.548.0.dist-info}/licenses/LICENCE +0 -0
@@ -44,23 +44,6 @@ class BaseScheduleEventListener(IScheduleEventListener):
44
44
  """
45
45
  pass # Placeholder for post-job execution logic
46
46
 
47
- async def onSuccess(self, event: EventJob, schedule):
48
- """
49
- Called when a job is successfully executed.
50
-
51
- Parameters
52
- ----------
53
- event : EventJob
54
- The successful job execution event containing details about the job.
55
- schedule : ISchedule
56
- The associated schedule instance managing the job.
57
-
58
- Returns
59
- -------
60
- None
61
- """
62
- pass # Placeholder for logic to handle successful job execution
63
-
64
47
  async def onFailure(self, event: EventJob, schedule):
65
48
  """
66
49
  Called when a job execution fails.
@@ -43,24 +43,6 @@ class IScheduleEventListener(ABC):
43
43
  """
44
44
  pass
45
45
 
46
- @abstractmethod
47
- async def onSuccess(self, event: EventJob, schedule):
48
- """
49
- Called when a job is successfully executed.
50
-
51
- Parameters
52
- ----------
53
- event : EventJob
54
- The successful job execution event containing details about the job.
55
- schedule : ISchedule
56
- The associated schedule instance managing the job.
57
-
58
- Returns
59
- -------
60
- None
61
- """
62
- pass
63
-
64
46
  @abstractmethod
65
47
  async def onFailure(self, event: EventJob, schedule):
66
48
  """
@@ -237,8 +237,9 @@ class App(BaseEntity):
237
237
  # Validate `key` attribute
238
238
  if self.key is None:
239
239
  self.key = SecureKeyGenerator.generate()
240
- if not isinstance(self.key, str):
241
- raise OrionisIntegrityException("The 'key' attribute must be a string.")
240
+ Env.set('APP_KEY', self.key)
241
+ if not isinstance(self.key, (bytes, str)) or not self.key.strip():
242
+ raise OrionisIntegrityException("The 'key' attribute must be a non-empty string.")
242
243
 
243
244
  # Validate `maintenance` attribute
244
245
  if not isinstance(self.maintenance, str) or not self.name.strip() or not self.maintenance.startswith('/'):
@@ -8,27 +8,13 @@ class Cipher(Enum):
8
8
  commonly used for encryption and decryption operations.
9
9
 
10
10
  Members:
11
- AES_128_CBC: AES with 128-bit key in Cipher Block Chaining (CBC) mode.
12
- AES_192_CBC: AES with 192-bit key in CBC mode.
13
- AES_256_CBC: AES with 256-bit key in CBC mode.
14
- AES_128_GCM: AES with 128-bit key in Galois/Counter Mode (GCM).
15
- AES_256_GCM: AES with 256-bit key in GCM.
16
- AES_CTR: AES in Counter (CTR) mode.
17
- AES_CFB: AES in Cipher Feedback (CFB) mode.
18
- AES_CFB8: AES in CFB mode with 8-bit feedback.
19
- AES_CFB128: AES in CFB mode with 128-bit feedback.
20
- AES_OFB: AES in Output Feedback (OFB) mode.
21
- AES_ECB: AES in Electronic Codebook (ECB) mode.
11
+ - AES_128_CBC: AES with a 128-bit key in Cipher Block Chaining (CBC) mode.
12
+ - AES_256_CBC: AES with a 256-bit key in Cipher Block Chaining (CBC) mode.
13
+ - AES_128_GCM: AES with a 128-bit key in Galois/Counter Mode (GCM).
14
+ - AES_256_GCM: AES with a 256-bit key in Galois/Counter Mode (GCM).
22
15
  """
23
16
 
24
17
  AES_128_CBC = "AES-128-CBC"
25
- AES_192_CBC = "AES-192-CBC"
26
18
  AES_256_CBC = "AES-256-CBC"
27
19
  AES_128_GCM = "AES-128-GCM"
28
- AES_256_GCM = "AES-256-GCM"
29
- AES_CTR = "AES-CTR"
30
- AES_CFB = "AES-CFB"
31
- AES_CFB8 = "AES-CFB8"
32
- AES_CFB128 = "AES-CFB128"
33
- AES_OFB = "AES-OFB"
34
- AES_ECB = "AES-ECB"
20
+ AES_256_GCM = "AES-256-GCM"
@@ -94,8 +94,8 @@ class Session(BaseEntity):
94
94
  # Validate secret_key
95
95
  if self.secret_key is None:
96
96
  self.secret_key = SecretKey.random()
97
- if not isinstance(self.secret_key, str) or not self.secret_key.strip():
98
- raise OrionisIntegrityException("Session secret_key must be a non-empty string")
97
+ if not isinstance(self.secret_key, (bytes, str)) or not self.secret_key.strip():
98
+ raise OrionisIntegrityException("secret_key must be a non-empty string")
99
99
 
100
100
  # Validate session_cookie
101
101
  if not isinstance(self.session_cookie, str) or not self.session_cookie.strip():
@@ -5,7 +5,7 @@
5
5
  NAME = "orionis"
6
6
 
7
7
  # Current version of the framework
8
- VERSION = "0.546.0"
8
+ VERSION = "0.548.0"
9
9
 
10
10
  # Full name of the author or maintainer of the project
11
11
  AUTHOR = "Raul Mauricio Uñate Castro"
@@ -0,0 +1,115 @@
1
+ import base64
2
+ import json
3
+ import os
4
+ from typing import Optional
5
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
6
+ from cryptography.hazmat.backends import default_backend
7
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
8
+
9
+
10
+ class Encrypter:
11
+
12
+ SUPPORTED_CIPHERS = ["AES-128-CBC", "AES-256-CBC", "AES-128-GCM", "AES-256-GCM"]
13
+
14
+ def __init__(self, app_key: str, cipher: str = "AES-256-CBC"):
15
+ # Decodificar APP_KEY de Laravel
16
+ if app_key.startswith("base64:"):
17
+ key_bytes = base64.b64decode(app_key[7:])
18
+ else:
19
+ key_bytes = app_key.encode()
20
+
21
+ self.key = key_bytes
22
+ self.cipher = cipher
23
+
24
+ # Validar cipher
25
+ if cipher not in self.SUPPORTED_CIPHERS:
26
+ raise ValueError(f"Cipher '{cipher}' no soportado. Usa uno de: {self.SUPPORTED_CIPHERS}")
27
+
28
+ # Validar longitud de clave según el cipher
29
+ key_len = len(self.key)
30
+ if cipher.startswith("AES-128") and key_len != 16:
31
+ raise ValueError("La clave debe ser de 16 bytes para AES-128")
32
+ if cipher.startswith("AES-256") and key_len != 32:
33
+ raise ValueError("La clave debe ser de 32 bytes para AES-256")
34
+
35
+ def encrypt(self, plaintext: str) -> str:
36
+ data = plaintext.encode()
37
+
38
+ if "GCM" in self.cipher:
39
+ return self._encrypt_gcm(data)
40
+ else:
41
+ return self._encrypt_cbc(data)
42
+
43
+ def decrypt(self, payload: str) -> str:
44
+ # Decodificar base64 -> JSON
45
+ decoded = base64.b64decode(payload).decode()
46
+ data = json.loads(decoded)
47
+
48
+ cipher = data.get("cipher")
49
+ iv = base64.b64decode(data["iv"])
50
+ value = base64.b64decode(data["value"])
51
+ tag = base64.b64decode(data["tag"]) if data.get("tag") else None
52
+
53
+ if cipher != self.cipher:
54
+ raise ValueError(f"El cipher del payload '{cipher}' no coincide con el configurado '{self.cipher}'")
55
+
56
+ if "GCM" in cipher:
57
+ return self._decrypt_gcm(value, iv, tag).decode()
58
+ else:
59
+ return self._decrypt_cbc(value, iv).decode()
60
+
61
+ # -------------------
62
+ # Métodos privados CBC
63
+ # -------------------
64
+ def _encrypt_cbc(self, data: bytes) -> str:
65
+ iv = os.urandom(16)
66
+ cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv), backend=default_backend())
67
+ encryptor = cipher.encryptor()
68
+
69
+ # PKCS7 padding
70
+ pad_len = 16 - (len(data) % 16)
71
+ data += bytes([pad_len]) * pad_len
72
+
73
+ ct = encryptor.update(data) + encryptor.finalize()
74
+
75
+ payload = {
76
+ "iv": base64.b64encode(iv).decode(),
77
+ "value": base64.b64encode(ct).decode(),
78
+ "tag": None,
79
+ "cipher": self.cipher,
80
+ }
81
+
82
+ return base64.b64encode(json.dumps(payload).encode()).decode()
83
+
84
+ def _decrypt_cbc(self, ct: bytes, iv: bytes) -> bytes:
85
+ cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv), backend=default_backend())
86
+ decryptor = cipher.decryptor()
87
+ data = decryptor.update(ct) + decryptor.finalize()
88
+
89
+ # Unpad PKCS7
90
+ pad_len = data[-1]
91
+ return data[:-pad_len]
92
+
93
+ # -------------------
94
+ # Métodos privados GCM
95
+ # -------------------
96
+ def _encrypt_gcm(self, data: bytes) -> str:
97
+ iv = os.urandom(12)
98
+ aesgcm = AESGCM(self.key)
99
+ ct = aesgcm.encrypt(iv, data, None)
100
+
101
+ # separar ciphertext y tag (últimos 16 bytes)
102
+ value, tag = ct[:-16], ct[-16:]
103
+
104
+ payload = {
105
+ "iv": base64.b64encode(iv).decode(),
106
+ "value": base64.b64encode(value).decode(),
107
+ "tag": base64.b64encode(tag).decode(),
108
+ "cipher": self.cipher,
109
+ }
110
+
111
+ return base64.b64encode(json.dumps(payload).encode()).decode()
112
+
113
+ def _decrypt_gcm(self, value: bytes, iv: bytes, tag: Optional[bytes]) -> bytes:
114
+ aesgcm = AESGCM(self.key)
115
+ return aesgcm.decrypt(iv, value + tag, None)
@@ -255,64 +255,72 @@ class EnvironmentCaster(IEnvironmentCaster):
255
255
  """
256
256
  Convert the internal value to a Base64 encoded string with the type hint prefix.
257
257
 
258
+ - If the value is already valid base64, leave it as-is.
259
+ - Otherwise, encode it to base64.
260
+
258
261
  Returns
259
262
  -------
260
263
  str
261
- A string in the format "<type_hint>:<base64_value>", where <base64_value> is the Base64
262
- encoded representation of the internal value.
263
-
264
- Raises
265
- ------
266
- OrionisEnvironmentValueError
267
- If the internal value is not a string or bytes.
264
+ A string in the format "<type_hint>:<base64_value>".
268
265
  """
269
-
270
- # Import the base64 module for encoding
271
266
  import base64
272
267
 
273
- # Ensure the internal value is a string or bytes before encoding
274
268
  if not isinstance(self.__value_raw, (str, bytes)):
275
269
  raise OrionisEnvironmentValueError(
276
270
  f"Value must be a string or bytes to convert to Base64, got {type(self.__value_raw).__name__} instead."
277
271
  )
278
272
 
279
- # Encode the value in Base64
280
- encoded_value = base64.b64encode(str(self.__value_raw).encode()).decode()
273
+ # Normalizar a str
274
+ if isinstance(self.__value_raw, bytes):
275
+ candidate = self.__value_raw.decode("utf-8", errors="ignore")
276
+ else:
277
+ candidate = self.__value_raw
278
+
279
+ try:
280
+ # Validar si ya es base64 válido
281
+ base64.b64decode(candidate, validate=True)
282
+ encoded_value = candidate
283
+ except Exception:
284
+ # No era base64, entonces convertirlo
285
+ raw_bytes = (
286
+ self.__value_raw.encode() if isinstance(self.__value_raw, str) else self.__value_raw
287
+ )
288
+ encoded_value = base64.b64encode(raw_bytes).decode("utf-8")
281
289
 
282
- # Return the formatted string with type hint and Base64 encoded value
283
290
  return f"{self.__type_hint}:{encoded_value}"
284
291
 
285
- def __parseBase64(self) -> str:
292
+ def __parseBase64(self) -> bytes:
286
293
  """
287
294
  Decode the internal raw value from Base64 encoding.
288
295
 
289
- Decodes the Base64-encoded string stored in the internal raw value and returns the decoded result as a string.
290
- If decoding fails, raises an OrionisEnvironmentValueException.
291
-
292
296
  Returns
293
297
  -------
294
- str
295
- The decoded string from the Base64-encoded internal value.
298
+ bytes
299
+ The decoded raw bytes from the Base64-encoded internal value.
296
300
 
297
301
  Raises
298
302
  ------
299
303
  OrionisEnvironmentValueException
300
304
  If the internal value cannot be decoded from Base64.
301
305
  """
302
-
303
- # Import the base64 module for decoding
304
306
  import base64
305
307
 
306
308
  try:
309
+ raw_value = self.__value_raw
310
+ if isinstance(raw_value, bytes):
311
+ raw_value = raw_value.decode("utf-8", errors="ignore")
307
312
 
308
- # Decode the Base64 encoded value and return as string
309
- decoded_value = base64.b64decode(self.__value_raw).decode()
310
- return decoded_value
313
+ decoded = base64.b64decode(raw_value, validate=True)
311
314
 
312
- except Exception as e:
315
+ try:
316
+ return decoded.decode("utf-8")
317
+ except UnicodeDecodeError:
318
+ return decoded
313
319
 
314
- # Raise a custom exception if decoding fails
315
- raise OrionisEnvironmentValueException(f"Cannot decode Base64 value '{self.__value_raw}': {str(e)}")
320
+ except Exception as e:
321
+ raise OrionisEnvironmentValueException(
322
+ f"Cannot decode Base64 value '{self.__value_raw}': {str(e)}"
323
+ )
316
324
 
317
325
  def __parsePath(self):
318
326
  """
@@ -1,26 +1,47 @@
1
1
  import os
2
+ import base64
2
3
 
3
4
  class SecureKeyGenerator:
4
5
  """
5
- Static utility class for generating cryptographically secure random keys.
6
+ Utility class for generating Laravel-compatible APP_KEY values.
6
7
 
7
- This class provides a static method to generate a secure random key,
8
- encoded as a hexadecimal string, suitable for use in security-sensitive
9
- applications such as cryptographic secrets or tokens.
8
+ Laravel expects keys in Base64 format, prefixed with 'base64:'.
9
+ Supported ciphers: AES-128-CBC, AES-256-CBC, AES-128-GCM, AES-256-GCM.
10
10
  """
11
11
 
12
+ KEY_SIZES = {
13
+ "AES-128-CBC": 16,
14
+ "AES-256-CBC": 32,
15
+ "AES-128-GCM": 16,
16
+ "AES-256-GCM": 32,
17
+ }
18
+
12
19
  @staticmethod
13
- def generate() -> str:
20
+ def generate(cipher: str = "AES-256-CBC") -> str:
14
21
  """
15
- Generate a cryptographically secure random key encoded in hexadecimal.
22
+ Generate a Laravel-compatible APP_KEY.
23
+
24
+ Parameters
25
+ ----------
26
+ cipher : str
27
+ The cipher algorithm. Options: AES-128-CBC, AES-256-CBC,
28
+ AES-128-GCM, AES-256-GCM. Default is AES-256-CBC.
16
29
 
17
30
  Returns
18
31
  -------
19
32
  str
20
- A 64-character hexadecimal string representing a 32-byte
21
- cryptographically secure random key.
33
+ A string formatted like Laravel's APP_KEY (e.g., base64:xxxx).
22
34
  """
35
+ if cipher not in SecureKeyGenerator.KEY_SIZES:
36
+ raise ValueError(
37
+ f"Cipher '{cipher}' no soportado. "
38
+ f"Opciones: {', '.join(SecureKeyGenerator.KEY_SIZES.keys())}"
39
+ )
40
+
41
+ key_length = SecureKeyGenerator.KEY_SIZES[cipher]
42
+
43
+ # Generate secure random bytes
44
+ key = os.urandom(key_length)
23
45
 
24
- # Generate 32 random bytes using a cryptographically secure RNG
25
- # Encode the bytes as a hexadecimal string and return
26
- return os.urandom(32).hex()
46
+ # Encode in Base64 and prepend 'base64:'
47
+ return "base64:" + base64.b64encode(key).decode("utf-8")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orionis
3
- Version: 0.546.0
3
+ Version: 0.548.0
4
4
  Summary: Orionis Framework – Elegant, Fast, and Powerful.
5
5
  Home-page: https://github.com/orionis-framework/framework
6
6
  Author: Raul Mauricio Uñate Castro