orionis 0.545.0__py3-none-any.whl → 0.547.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.
- orionis/console/base/scheduler_event_listener.py +0 -17
- orionis/console/contracts/schedule_event_listener.py +0 -18
- orionis/console/request/cli_request.py +2 -2
- orionis/foundation/config/app/entities/app.py +3 -2
- orionis/foundation/config/app/enums/ciphers.py +5 -19
- orionis/foundation/config/session/entities/session.py +2 -2
- orionis/foundation/providers/cli_request_provider.py +44 -0
- orionis/metadata/framework.py +1 -1
- orionis/services/encrypter/encrypter.py +115 -0
- orionis/services/environment/dynamic/caster.py +35 -27
- orionis/services/environment/key/key_generator.py +32 -11
- {orionis-0.545.0.dist-info → orionis-0.547.0.dist-info}/METADATA +1 -1
- {orionis-0.545.0.dist-info → orionis-0.547.0.dist-info}/RECORD +17 -160
- {orionis-0.545.0.dist-info → orionis-0.547.0.dist-info}/top_level.txt +0 -1
- tests/container/__init__.py +0 -0
- tests/container/context/__init__.py +0 -0
- tests/container/context/test_manager.py +0 -38
- tests/container/context/test_scope.py +0 -32
- tests/container/core/__init__.py +0 -0
- tests/container/core/test_advanced_async.py +0 -234
- tests/container/core/test_async_optimizations.py +0 -268
- tests/container/core/test_container.py +0 -453
- tests/container/core/test_singleton.py +0 -122
- tests/container/core/test_thread_safety.py +0 -90
- tests/container/entities/__init__.py +0 -0
- tests/container/entities/test_binding.py +0 -242
- tests/container/enums/__init__.py +0 -0
- tests/container/enums/test_lifetimes.py +0 -97
- tests/container/facades/__init__.py +0 -0
- tests/container/facades/test_facade.py +0 -78
- tests/container/mocks/__init__.py +0 -0
- tests/container/mocks/mock_advanced_async.py +0 -332
- tests/container/mocks/mock_async_optimizations.py +0 -407
- tests/container/mocks/mock_auto_resolution.py +0 -192
- tests/container/mocks/mock_complex_classes.py +0 -792
- tests/container/mocks/mock_simple_classes.py +0 -98
- tests/container/providers/__init__.py +0 -0
- tests/container/providers/test_providers.py +0 -55
- tests/container/validators/__init__.py +0 -0
- tests/container/validators/test_implements.py +0 -186
- tests/container/validators/test_is_abstract_class.py +0 -147
- tests/container/validators/test_is_callable.py +0 -102
- tests/container/validators/test_is_concrete_class.py +0 -160
- tests/container/validators/test_is_instance.py +0 -150
- tests/container/validators/test_is_not_subclass.py +0 -49
- tests/container/validators/test_is_subclass.py +0 -178
- tests/container/validators/test_is_valid_alias.py +0 -147
- tests/container/validators/test_lifetime.py +0 -106
- tests/example/__init__.py +0 -0
- tests/example/test_example.py +0 -725
- tests/foundation/__init__.py +0 -0
- tests/foundation/config/__init__.py +0 -0
- tests/foundation/config/app/__init__.py +0 -0
- tests/foundation/config/app/test_foundation_config_app.py +0 -262
- tests/foundation/config/auth/__init__.py +0 -0
- tests/foundation/config/auth/test_foundation_config_auth.py +0 -29
- tests/foundation/config/cache/__init__.py +0 -0
- tests/foundation/config/cache/test_foundation_config_cache.py +0 -143
- tests/foundation/config/cache/test_foundation_config_cache_file.py +0 -126
- tests/foundation/config/cache/test_foundation_config_cache_stores.py +0 -156
- tests/foundation/config/cors/__init__.py +0 -0
- tests/foundation/config/cors/test_foundation_config_cors.py +0 -190
- tests/foundation/config/database/__init__.py +0 -0
- tests/foundation/config/database/test_foundation_config_database.py +0 -158
- tests/foundation/config/database/test_foundation_config_database_connections.py +0 -203
- tests/foundation/config/database/test_foundation_config_database_mysql.py +0 -354
- tests/foundation/config/database/test_foundation_config_database_oracle.py +0 -288
- tests/foundation/config/database/test_foundation_config_database_pgsql.py +0 -257
- tests/foundation/config/database/test_foundation_config_database_sqlite.py +0 -207
- tests/foundation/config/filesystems/__init__.py +0 -0
- tests/foundation/config/filesystems/test_foundation_config_filesystems.py +0 -160
- tests/foundation/config/filesystems/test_foundation_config_filesystems_aws.py +0 -189
- tests/foundation/config/filesystems/test_foundation_config_filesystems_disks.py +0 -184
- tests/foundation/config/filesystems/test_foundation_config_filesystems_local.py +0 -143
- tests/foundation/config/filesystems/test_foundation_config_filesystems_public.py +0 -184
- tests/foundation/config/logging/__init__.py +0 -0
- tests/foundation/config/logging/test_foundation_config_logging.py +0 -112
- tests/foundation/config/logging/test_foundation_config_logging_channels.py +0 -246
- tests/foundation/config/logging/test_foundation_config_logging_chunked.py +0 -217
- tests/foundation/config/logging/test_foundation_config_logging_daily.py +0 -220
- tests/foundation/config/logging/test_foundation_config_logging_hourly.py +0 -196
- tests/foundation/config/logging/test_foundation_config_logging_monthly.py +0 -214
- tests/foundation/config/logging/test_foundation_config_logging_stack.py +0 -178
- tests/foundation/config/logging/test_foundation_config_logging_weekly.py +0 -224
- tests/foundation/config/mail/__init__.py +0 -0
- tests/foundation/config/mail/test_foundation_config_mail.py +0 -145
- tests/foundation/config/mail/test_foundation_config_mail_file.py +0 -97
- tests/foundation/config/mail/test_foundation_config_mail_mailers.py +0 -106
- tests/foundation/config/mail/test_foundation_config_mail_smtp.py +0 -146
- tests/foundation/config/queue/__init__.py +0 -0
- tests/foundation/config/queue/test_foundation_config_queue.py +0 -88
- tests/foundation/config/queue/test_foundation_config_queue_brokers.py +0 -72
- tests/foundation/config/queue/test_foundation_config_queue_database.py +0 -134
- tests/foundation/config/root/__init__.py +0 -0
- tests/foundation/config/root/test_foundation_config_root_paths.py +0 -112
- tests/foundation/config/session/__init__.py +0 -0
- tests/foundation/config/session/test_foundation_config_session.py +0 -213
- tests/foundation/config/startup/__init__.py +0 -0
- tests/foundation/config/startup/test_foundation_config_startup.py +0 -202
- tests/foundation/config/testing/__init__.py +0 -0
- tests/foundation/config/testing/test_foundation_config_testing.py +0 -235
- tests/metadata/__init__.py +0 -0
- tests/metadata/test_metadata_framework.py +0 -140
- tests/metadata/test_metadata_package.py +0 -139
- tests/services/__init__.py +0 -0
- tests/services/asynchrony/__init__.py +0 -0
- tests/services/asynchrony/test_services_asynchrony_coroutine.py +0 -85
- tests/services/environment/__init__.py +0 -0
- tests/services/environment/test_services_environment.py +0 -226
- tests/services/introspection/__init__.py +0 -0
- tests/services/introspection/dependencies/__init__.py +0 -0
- tests/services/introspection/dependencies/mocks/__init__.py +0 -0
- tests/services/introspection/dependencies/mocks/mock_user.py +0 -30
- tests/services/introspection/dependencies/mocks/mock_user_controller.py +0 -27
- tests/services/introspection/dependencies/mocks/mock_users_permissions.py +0 -41
- tests/services/introspection/dependencies/test_reflect_dependencies.py +0 -261
- tests/services/introspection/reflection/__init__.py +0 -0
- tests/services/introspection/reflection/mock/__init__.py +0 -0
- tests/services/introspection/reflection/mock/fake_reflect_instance.py +0 -1115
- tests/services/introspection/reflection/test_reflection_abstract.py +0 -1011
- tests/services/introspection/reflection/test_reflection_callable.py +0 -206
- tests/services/introspection/reflection/test_reflection_concrete.py +0 -952
- tests/services/introspection/reflection/test_reflection_instance.py +0 -1233
- tests/services/introspection/reflection/test_reflection_module.py +0 -567
- tests/services/introspection/test_reflection.py +0 -462
- tests/services/log/__init__.py +0 -0
- tests/services/log/test_log.py +0 -97
- tests/services/system/__init__.py +0 -0
- tests/services/system/test_services_system_imports.py +0 -204
- tests/services/system/test_services_system_workers.py +0 -131
- tests/support/__init__.py +0 -0
- tests/support/entities/__init__.py +0 -0
- tests/support/entities/mock_dataclass.py +0 -40
- tests/support/entities/test_base.py +0 -64
- tests/support/patterns/__init__.py +0 -0
- tests/support/patterns/singleton/__init__.py +0 -0
- tests/support/patterns/singleton/test_patterns_singleton.py +0 -39
- tests/support/standard/__init__.py +0 -0
- tests/support/standard/test_services_std.py +0 -226
- tests/support/wrapper/__init__.py +0 -0
- tests/support/wrapper/test_services_wrapper_docdict.py +0 -202
- tests/testing/__init__.py +0 -0
- tests/testing/cases/__init__.py +0 -0
- tests/testing/cases/test_testing_asynchronous.py +0 -63
- tests/testing/cases/test_testing_synchronous.py +0 -57
- tests/testing/entities/__init__.py +0 -0
- tests/testing/entities/test_testing_result.py +0 -146
- tests/testing/enums/__init__.py +0 -0
- tests/testing/enums/test_testing_status.py +0 -63
- tests/testing/output/__init__.py +0 -0
- tests/testing/output/test_testing_dumper.py +0 -29
- tests/testing/output/test_testing_printer.py +0 -42
- tests/testing/records/__init__.py +0 -0
- tests/testing/records/test_testing_records.py +0 -171
- tests/testing/test_testing_unit.py +0 -164
- tests/testing/validators/__init__.py +0 -0
- tests/testing/validators/test_testing_validators.py +0 -392
- tests/testing/view/__init__.py +0 -0
- tests/testing/view/test_render.py +0 -30
- {orionis-0.545.0.dist-info → orionis-0.547.0.dist-info}/WHEEL +0 -0
- {orionis-0.545.0.dist-info → orionis-0.547.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.545.0.dist-info → orionis-0.547.0.dist-info}/zip-safe +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
|
-
|
|
241
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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("
|
|
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():
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from orionis.console.contracts.cli_request import ICLIRequest
|
|
2
|
+
from orionis.console.request.cli_request import CLIRequest
|
|
3
|
+
from orionis.container.providers.service_provider import ServiceProvider
|
|
4
|
+
|
|
5
|
+
class CLRequestProvider(ServiceProvider):
|
|
6
|
+
"""
|
|
7
|
+
Service provider for registering CLI request services in the Orionis framework.
|
|
8
|
+
|
|
9
|
+
This provider handles the registration and binding of CLI request interfaces
|
|
10
|
+
to their concrete implementations within the application container.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def register(self) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Register CLI request services in the application container.
|
|
16
|
+
|
|
17
|
+
Binds the ICLIRequest interface to the CLIRequest implementation as a
|
|
18
|
+
transient service, making it available for dependency injection throughout
|
|
19
|
+
the application with the specified alias.
|
|
20
|
+
|
|
21
|
+
Returns
|
|
22
|
+
-------
|
|
23
|
+
None
|
|
24
|
+
This method does not return any value.
|
|
25
|
+
"""
|
|
26
|
+
# Register CLIRequest as a transient service bound to ICLIRequest interface
|
|
27
|
+
# Transient services create a new instance each time they are resolved
|
|
28
|
+
self.app.transient(ICLIRequest, CLIRequest, alias="x-orionis.console.request.cli_request")
|
|
29
|
+
|
|
30
|
+
def boot(self) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Perform any necessary bootstrapping after service registration.
|
|
33
|
+
|
|
34
|
+
This method is called after all services have been registered and can be
|
|
35
|
+
used to perform additional setup or configuration tasks. Currently, no
|
|
36
|
+
bootstrapping logic is required for CLI request services.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
None
|
|
41
|
+
This method does not return any value.
|
|
42
|
+
"""
|
|
43
|
+
# No bootstrapping logic required for CLI request services
|
|
44
|
+
pass
|
orionis/metadata/framework.py
CHANGED
|
@@ -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>"
|
|
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
|
-
#
|
|
280
|
-
|
|
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) ->
|
|
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
|
-
|
|
295
|
-
The decoded
|
|
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
|
-
|
|
309
|
-
decoded_value = base64.b64decode(self.__value_raw).decode()
|
|
310
|
-
return decoded_value
|
|
313
|
+
decoded = base64.b64decode(raw_value, validate=True)
|
|
311
314
|
|
|
312
|
-
|
|
315
|
+
try:
|
|
316
|
+
return decoded.decode("utf-8")
|
|
317
|
+
except UnicodeDecodeError:
|
|
318
|
+
return decoded
|
|
313
319
|
|
|
314
|
-
|
|
315
|
-
raise OrionisEnvironmentValueException(
|
|
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
|
-
|
|
6
|
+
Utility class for generating Laravel-compatible APP_KEY values.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
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
|
|
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
|
-
#
|
|
25
|
-
|
|
26
|
-
return os.urandom(32).hex()
|
|
46
|
+
# Encode in Base64 and prepend 'base64:'
|
|
47
|
+
return "base64:" + base64.b64encode(key).decode("utf-8")
|