Encryptors 2.62__tar.gz → 2.64__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 (88) hide show
  1. {encryptors-2.62 → encryptors-2.64}/PKG-INFO +2 -2
  2. {encryptors-2.62 → encryptors-2.64}/setup.py +2 -2
  3. {encryptors-2.62 → encryptors-2.64}/src/Encryptors.egg-info/PKG-INFO +2 -2
  4. {encryptors-2.62 → encryptors-2.64}/src/Encryptors.egg-info/requires.txt +1 -1
  5. encryptors-2.64/src/Osdental/Helpers/JwtTokenHelper.py +179 -0
  6. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Services/JwtAuthTokenService.py +25 -0
  7. encryptors-2.62/src/Osdental/Helpers/JwtTokenHelper.py +0 -171
  8. {encryptors-2.62 → encryptors-2.64}/README.md +0 -0
  9. {encryptors-2.62 → encryptors-2.64}/setup.cfg +0 -0
  10. {encryptors-2.62 → encryptors-2.64}/src/Encryptors.egg-info/SOURCES.txt +0 -0
  11. {encryptors-2.62 → encryptors-2.64}/src/Encryptors.egg-info/dependency_links.txt +0 -0
  12. {encryptors-2.62 → encryptors-2.64}/src/Encryptors.egg-info/entry_points.txt +0 -0
  13. {encryptors-2.62 → encryptors-2.64}/src/Encryptors.egg-info/top_level.txt +0 -0
  14. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Cache/Redis.py +0 -0
  15. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Cache/__init__.py +0 -0
  16. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Cli/__init__.py +0 -0
  17. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Constants/Constant.py +0 -0
  18. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Constants/Message.py +0 -0
  19. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Constants/__init__.py +0 -0
  20. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Context/__init__.py +0 -0
  21. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Database/BaseRepository.py +0 -0
  22. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Database/Connection.py +0 -0
  23. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Database/__init__.py +0 -0
  24. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Decorators/Grpc.py +0 -0
  25. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Decorators/Retry.py +0 -0
  26. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Decorators/SecureResolver.py +0 -0
  27. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Decorators/__init__.py +0 -0
  28. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/Aes.py +0 -0
  29. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/Argon2.py +0 -0
  30. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/Bcrypt.py +0 -0
  31. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/Jwt.py +0 -0
  32. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/Rsa.py +0 -0
  33. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/Sha512.py +0 -0
  34. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/__init__.py +0 -0
  35. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/AuditType.py +0 -0
  36. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/ErrorSource.py +0 -0
  37. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/FileType.py +0 -0
  38. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/GrahpqlOperation.py +0 -0
  39. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/Profile.py +0 -0
  40. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/ResultType.py +0 -0
  41. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/StatusCode.py +0 -0
  42. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/__init__.py +0 -0
  43. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Exception/ControlledException.py +0 -0
  44. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Exception/__init__.py +0 -0
  45. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/AuditHelper.py +0 -0
  46. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/AuditQueue.py +0 -0
  47. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/AuthTokenProcessor.py +0 -0
  48. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/AzureClassifier.py +0 -0
  49. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/GrpcConnection.py +0 -0
  50. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/Resilience.py +0 -0
  51. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/ResponseDecryptor.py +0 -0
  52. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/__init__.py +0 -0
  53. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Http/APIClient.py +0 -0
  54. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Http/__init__.py +0 -0
  55. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Messaging/AzureServiceBus.py +0 -0
  56. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Messaging/Kafka.py +0 -0
  57. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Messaging/RabbitMQ.py +0 -0
  58. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Messaging/__init__.py +0 -0
  59. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/ApiResponse.py +0 -0
  60. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/AuditContext.py +0 -0
  61. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/Graphql.py +0 -0
  62. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/Notification.py +0 -0
  63. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/Response.py +0 -0
  64. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/Token.py +0 -0
  65. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/TokenClaims.py +0 -0
  66. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/__init__.py +0 -0
  67. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Secrets/AzureKeyVaultProvider.py +0 -0
  68. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Secrets/__init__.py +0 -0
  69. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Services/ServiceBusAuditEmitter.py +0 -0
  70. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Services/WebsocketClient.py +0 -0
  71. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Services/__init__.py +0 -0
  72. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
  73. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Storage/S3Storage.py +0 -0
  74. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Storage/__init__.py +0 -0
  75. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/CaseConverter.py +0 -0
  76. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/CodeGenerator.py +0 -0
  77. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/DataNormalizer.py +0 -0
  78. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/DataUtils.py +0 -0
  79. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/DateUtils.py +0 -0
  80. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/FileMetaData.py +0 -0
  81. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/HashValidator.py +0 -0
  82. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/Mapper.py +0 -0
  83. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/PasswordGenerator.py +0 -0
  84. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/QueryGenerator.py +0 -0
  85. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/RsaUtils.py +0 -0
  86. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/TextProcessor.py +0 -0
  87. {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/__init__.py +0 -0
  88. {encryptors-2.62 → encryptors-2.64}/src/Osdental/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Encryptors
3
- Version: 2.62
3
+ Version: 2.64
4
4
  Summary: End-to-end algorithm library
5
5
  Author: OSDental LLC
6
6
  Author-email: support@osdental.ai
@@ -57,7 +57,7 @@ Requires-Dist: bcrypt==4.3.0
57
57
  Requires-Dist: azure-monitor-opentelemetry==1.8.1
58
58
  Requires-Dist: uvicorn==0.37.0
59
59
  Requires-Dist: gunicorn==23.0.0
60
- Requires-Dist: jwcrypto==1.5.7
60
+ Requires-Dist: joserfc==1.5.0
61
61
  Dynamic: author
62
62
  Dynamic: author-email
63
63
  Dynamic: classifier
@@ -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.62",
5
+ version="2.64",
6
6
  author="OSDental LLC",
7
7
  author_email="support@osdental.ai",
8
8
  description="End-to-end algorithm library",
@@ -65,7 +65,7 @@ setup(
65
65
  "azure-monitor-opentelemetry==1.8.1",
66
66
  "uvicorn==0.37.0",
67
67
  "gunicorn==23.0.0",
68
- "jwcrypto==1.5.7",
68
+ "joserfc==1.5.0",
69
69
  ],
70
70
  entry_points={
71
71
  'console_scripts': [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Encryptors
3
- Version: 2.62
3
+ Version: 2.64
4
4
  Summary: End-to-end algorithm library
5
5
  Author: OSDental LLC
6
6
  Author-email: support@osdental.ai
@@ -57,7 +57,7 @@ Requires-Dist: bcrypt==4.3.0
57
57
  Requires-Dist: azure-monitor-opentelemetry==1.8.1
58
58
  Requires-Dist: uvicorn==0.37.0
59
59
  Requires-Dist: gunicorn==23.0.0
60
- Requires-Dist: jwcrypto==1.5.7
60
+ Requires-Dist: joserfc==1.5.0
61
61
  Dynamic: author
62
62
  Dynamic: author-email
63
63
  Dynamic: classifier
@@ -46,4 +46,4 @@ bcrypt==4.3.0
46
46
  azure-monitor-opentelemetry==1.8.1
47
47
  uvicorn==0.37.0
48
48
  gunicorn==23.0.0
49
- jwcrypto==1.5.7
49
+ joserfc==1.5.0
@@ -0,0 +1,179 @@
1
+ import json
2
+ from datetime import datetime, timedelta, timezone
3
+ from joserfc import jwe, jwt
4
+ from joserfc.jwk import RSAKey
5
+ from Osdental.Models.TokenClaims import TokenValidationOptions
6
+ from Osdental.Exception.ControlledException import UnauthorizedException
7
+ from Osdental.Enums.StatusCode import StatusCode
8
+
9
+ class JwtTokenHelper:
10
+
11
+ def __init__(
12
+ self,
13
+ private_key_pem: str,
14
+ public_key_pem: str,
15
+ ):
16
+ self._private_key = RSAKey.import_key(
17
+ private_key_pem.encode()
18
+ )
19
+ self._public_key = RSAKey.import_key(
20
+ public_key_pem.encode()
21
+ )
22
+
23
+ self._jwe_algorithms = ["RSA-OAEP", "A256CBC-HS512"]
24
+
25
+ def generate_token(
26
+ self,
27
+ claims: dict,
28
+ expiration_minutes: int,
29
+ ) -> str:
30
+
31
+ now = datetime.now(timezone.utc)
32
+
33
+ payload = {
34
+ **claims,
35
+ "iat": int(now.timestamp()),
36
+ "nbf": int(now.timestamp()),
37
+ "exp": int(
38
+ (
39
+ now
40
+ + timedelta(
41
+ minutes=expiration_minutes
42
+ )
43
+ ).timestamp()
44
+ ),
45
+ }
46
+
47
+ # 1. Firmar → JWS (inner JWT)
48
+ signed: str = jwt.encode(
49
+ header={"alg": "RS256", "typ": "JWT"},
50
+ claims=payload,
51
+ key=self._private_key,
52
+ )
53
+
54
+ # 2. Encriptar → JWE (outer token)
55
+ encrypted: str = jwe.encrypt_compact(
56
+ protected={"alg": "RSA-OAEP", "enc": "A256CBC-HS512", "cty": "JWT"},
57
+ plaintext=signed.encode(), # el JWT firmado es el payload del JWE
58
+ public_key=self._public_key,
59
+ )
60
+
61
+ return encrypted
62
+
63
+ def get_claims_ignore_expiration(
64
+ self,
65
+ token: str,
66
+ ) -> dict:
67
+ """
68
+ Desencripta y verifica FIRMA únicamente.
69
+
70
+ NO valida: exp, nbf, iat, iss, aud.
71
+
72
+ Usar exclusivamente en el flujo de refresh token,
73
+ donde el token puede estar vencido pero la firma debe ser válida.
74
+ """
75
+ return self._decode(token, validate_exp=False)
76
+
77
+
78
+ def validate_token(
79
+ self,
80
+ token: str,
81
+ options: TokenValidationOptions | None = None,
82
+ ) -> dict:
83
+ """
84
+ Desencripta, verifica firma, valida exp y,
85
+ opcionalmente, iss y aud según el TokenValidationOptions recibido.
86
+ """
87
+ options = options or TokenValidationOptions()
88
+
89
+ claims = self._decode(token, validate_exp=True)
90
+
91
+ self._validate_registered_claims(claims, options)
92
+
93
+ return claims
94
+
95
+
96
+ def _decode(
97
+ self,
98
+ token: str,
99
+ validate_exp: bool,
100
+ ) -> dict:
101
+ """
102
+ Núcleo del proceso:
103
+ 1. Desencripta el JWE → valida criptografía outer
104
+ 2. Verifica la firma del JWT inner → valida criptografía inner
105
+ 3. Valida exp solo si validate_exp=True
106
+ """
107
+ try:
108
+ # --- Paso 1: desencriptar JWE ---
109
+ decrypted = jwe.decrypt_compact(
110
+ value=token.encode(),
111
+ private_key=self._private_key,
112
+ algorithms=self._jwe_algorithms,
113
+ )
114
+
115
+ inner_token: str = decrypted.plaintext.decode()
116
+
117
+ # --- Paso 2: verificar firma (decode NO valida claims) ---
118
+ token_obj = jwt.decode(
119
+ value=inner_token,
120
+ key=self._public_key,
121
+ )
122
+
123
+ claims: dict = token_obj.claims
124
+
125
+ # --- Paso 3: validar exp solo si se pide ---
126
+ if validate_exp:
127
+ now = datetime.now(timezone.utc)
128
+ exp = claims.get("exp")
129
+
130
+ if exp is None:
131
+ raise UnauthorizedException(
132
+ message="Token missing exp claim",
133
+ status_code=StatusCode.UNAUTHORIZED,
134
+ )
135
+
136
+ if int(now.timestamp()) > exp:
137
+ raise UnauthorizedException(
138
+ message="Token expired",
139
+ status_code=StatusCode.UNAUTHORIZED,
140
+ )
141
+
142
+ return claims
143
+
144
+ except UnauthorizedException:
145
+ raise
146
+
147
+ except Exception as exc:
148
+ raise UnauthorizedException(
149
+ message="Invalid token",
150
+ status_code=StatusCode.UNAUTHORIZED,
151
+ ) from exc
152
+
153
+ def _validate_registered_claims(
154
+ self,
155
+ claims: dict,
156
+ options: TokenValidationOptions,
157
+ ) -> None:
158
+
159
+ if options.validate_issuer:
160
+
161
+ if not options.issuer:
162
+ raise ValueError("issuer is required in options")
163
+
164
+ if claims.get("iss") != options.issuer:
165
+ raise UnauthorizedException(
166
+ message="Invalid issuer",
167
+ status_code=StatusCode.UNAUTHORIZED,
168
+ )
169
+
170
+ if options.validate_audience:
171
+
172
+ if not options.audience:
173
+ raise ValueError("audience is required in options")
174
+
175
+ if claims.get("aud") != options.audience:
176
+ raise UnauthorizedException(
177
+ message="Invalid audience",
178
+ status_code=StatusCode.UNAUTHORIZED,
179
+ )
@@ -130,6 +130,31 @@ class JwtAuthTokenService(IAuthTokenService):
130
130
  )
131
131
  )
132
132
 
133
+ return UserTokenClaims(
134
+ id_legacy=claims["id_legacy"],
135
+ id_enterprise=claims["id_enterprise"],
136
+ id_token=claims["id_token"],
137
+ id_user=claims["id_user"],
138
+ id_external_enterprise=claims["id_external_enterprise"],
139
+ id_profile=claims["id_profile"],
140
+ user_full_name=claims["user_full_name"],
141
+ abbreviation=claims["abbreviation"],
142
+ aes_key_auth=claims["aes_key_auth"],
143
+ )
144
+
145
+
146
+ async def validate_token_for_refresh(
147
+ self,
148
+ token: str,
149
+ ) -> UserTokenClaims:
150
+ """
151
+ Valida firma pero ignora exp.
152
+ Usar exclusivamente para emitir un nuevo token cuando el anterior venció.
153
+ """
154
+ await self.__get_token_signatures()
155
+
156
+ claims = self._jwt.get_claims_ignore_expiration(token)
157
+
133
158
  return UserTokenClaims(
134
159
  id_legacy=claims["id_legacy"],
135
160
  id_enterprise=claims["id_enterprise"],
@@ -1,171 +0,0 @@
1
- import json
2
- from datetime import datetime, timedelta, timezone
3
- from jwcrypto import jwk
4
- from jwcrypto import jwt
5
- from jwcrypto import jwe
6
- from jwcrypto.jwt import JWTExpired
7
- from Osdental.Models.TokenClaims import TokenValidationOptions
8
- from Osdental.Exception.ControlledException import UnauthorizedException
9
- from Osdental.Enums.StatusCode import StatusCode
10
-
11
- class JwtTokenHelper:
12
-
13
- def __init__(
14
- self,
15
- private_key_pem: str,
16
- public_key_pem: str,
17
- ):
18
- self._private_key = jwk.JWK.from_pem(
19
- private_key_pem.encode()
20
- )
21
-
22
- self._public_key = jwk.JWK.from_pem(
23
- public_key_pem.encode()
24
- )
25
-
26
- def generate_token(
27
- self,
28
- claims: dict,
29
- expiration_minutes: int,
30
- ) -> str:
31
-
32
- now = datetime.now(timezone.utc)
33
-
34
- payload = {
35
- **claims,
36
- "iat": int(now.timestamp()),
37
- "nbf": int(now.timestamp()),
38
- "exp": int(
39
- (
40
- now
41
- + timedelta(
42
- minutes=expiration_minutes
43
- )
44
- ).timestamp()
45
- ),
46
- }
47
-
48
- signed_token = jwt.JWT(
49
- header={
50
- "alg": "RS256",
51
- "typ": "JWT",
52
- },
53
- claims=payload,
54
- )
55
-
56
- signed_token.make_signed_token(
57
- self._private_key
58
- )
59
-
60
- encrypted_token = jwt.JWT(
61
- header={
62
- "alg": "RSA-OAEP",
63
- "enc": "A256CBC-HS512",
64
- "cty": "JWT",
65
- },
66
- claims=signed_token.serialize(),
67
- )
68
-
69
- encrypted_token.make_encrypted_token(
70
- self._public_key
71
- )
72
-
73
- return encrypted_token.serialize()
74
-
75
- def get_claims(
76
- self,
77
- token: str,
78
- ) -> dict:
79
- """
80
- Desencripta y verifica firma.
81
-
82
- NO valida:
83
- - issuer
84
- - audience
85
-
86
- Retorna claims incluso si el token está expirado.
87
- """
88
-
89
- try:
90
- encrypted = jwe.JWE()
91
-
92
- encrypted.deserialize(
93
- token
94
- )
95
-
96
- encrypted.decrypt(
97
- self._private_key
98
- )
99
-
100
- inner_token = (
101
- encrypted.payload.decode()
102
- )
103
-
104
- verified = jwt.JWT()
105
-
106
- verified.deserialize(
107
- jwt=inner_token,
108
- key=self._public_key,
109
- )
110
-
111
- return json.loads(
112
- verified.claims
113
- )
114
-
115
- except JWTExpired:
116
- raise UnauthorizedException(
117
- message="Token expired",
118
- status_code=StatusCode.UNAUTHORIZED
119
- )
120
-
121
- def validate_claims(
122
- self,
123
- claims: dict,
124
- options: TokenValidationOptions,
125
- ) -> None:
126
-
127
-
128
- if options.validate_issuer:
129
-
130
- if not options.issuer:
131
- raise ValueError(
132
- "issuer is required"
133
- )
134
-
135
- if claims.get("iss") != options.issuer:
136
- raise ValueError(
137
- "Invalid issuer"
138
- )
139
-
140
-
141
- if options.validate_audience:
142
-
143
- if not options.audience:
144
- raise ValueError(
145
- "audience is required"
146
- )
147
-
148
- if claims.get("aud") != options.audience:
149
- raise ValueError(
150
- "Invalid audience"
151
- )
152
-
153
-
154
- def validate_token(
155
- self,
156
- token: str,
157
- options: TokenValidationOptions | None = None,
158
- ) -> dict:
159
-
160
- options = options or TokenValidationOptions()
161
-
162
- claims = self.get_claims(
163
- token
164
- )
165
-
166
- self.validate_claims(
167
- claims=claims,
168
- options=options,
169
- )
170
-
171
- return claims
File without changes
File without changes