Encryptors 2.43__tar.gz → 2.46__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {encryptors-2.43 → encryptors-2.46}/PKG-INFO +1 -1
- {encryptors-2.43 → encryptors-2.46}/setup.py +1 -1
- {encryptors-2.43 → encryptors-2.46}/src/Encryptors.egg-info/PKG-INFO +1 -1
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Decorators/SecureResolver.py +7 -9
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Graphql/Extensions/AuditExtension.py +31 -33
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Graphql/_Helpers/_AuditHelper.py +5 -5
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Helpers/AuditDispatcher.py +4 -4
- {encryptors-2.43 → encryptors-2.46}/README.md +0 -0
- {encryptors-2.43 → encryptors-2.46}/setup.cfg +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Encryptors.egg-info/SOURCES.txt +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Encryptors.egg-info/dependency_links.txt +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Encryptors.egg-info/entry_points.txt +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Encryptors.egg-info/requires.txt +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Encryptors.egg-info/top_level.txt +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Cli/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Database/BaseRepository.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Database/Connection.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Database/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Decorators/Grpc.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Decorators/PublicResolver.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Decorators/Retry.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Decorators/SqlDataNormalizer.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Decorators/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Encryptor/Aes.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Encryptor/Argon2.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Encryptor/Bcrypt.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Encryptor/Jwt.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Encryptor/Rsa.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Encryptor/Sha512.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Encryptor/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Exception/ControlledException.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Exception/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Graphql/Extensions/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Graphql/Models/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Graphql/_Exceptions/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Graphql/_Helpers/_TenantPolicy.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Graphql/_Helpers/_TokenService.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Graphql/_Helpers/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Graphql/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Helpers/KeyVaultService.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Helpers/ResponseDecryptor.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Helpers/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Http/APIClient.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Http/_Exceptions.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Http/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Messaging/AzureServiceBus.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Messaging/Kafka.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Messaging/RabbitMQ.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Messaging/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Models/AuditConfig.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Models/Response.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Models/Token.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Models/_Audit.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Models/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/RedisCache/Redis.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/RedisCache/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Rest/Context/RequestContext.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Rest/Context/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Rest/Middlewares/RequestContextMiddleware.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Rest/Middlewares/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Rest/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Enums/Code.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Enums/Constant.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Enums/FileType.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Enums/GrahpqlOperation.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Enums/Message.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Enums/Profile.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Enums/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Logger.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/CaseConverter.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/CodeGenerator.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/DataNormalizer.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/DataUtils.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/DateUtils.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/FileMetaData.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/HashValidator.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/Mapper.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/PasswordGenerator.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/QueryGenerator.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/RsaUtils.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/TextProcessor.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/Utils/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Shared/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Storage/S3Storage.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/Storage/__init__.py +0 -0
- {encryptors-2.43 → encryptors-2.46}/src/Osdental/__init__.py +0 -0
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
# ANDERSON ESTO YA SE SUBIO Y ESTA ESTABLE, AUN TE QUEDA PENDIENTE LA AUDITORIA CON RSA Y AES DE ACCESSTOKEN
|
|
3
3
|
setup(
|
|
4
4
|
name="Encryptors",
|
|
5
|
-
version="2.
|
|
5
|
+
version="2.46",
|
|
6
6
|
author="OSDental LLC",
|
|
7
7
|
author_email="support@osdental.ai",
|
|
8
8
|
description="End-to-end algorithm library",
|
|
@@ -2,27 +2,27 @@ from functools import wraps
|
|
|
2
2
|
from typing import Callable
|
|
3
3
|
from Osdental.Models.Response import Response
|
|
4
4
|
from Osdental.Shared.Enums.Profile import Profile
|
|
5
|
-
from Osdental.Exception.ControlledException import OSDException, AccessDeniedException
|
|
5
|
+
from Osdental.Exception.ControlledException import OSDException, AccessDeniedException, UnauthorizedException
|
|
6
6
|
from Osdental.Shared.Logger import logger
|
|
7
7
|
|
|
8
8
|
def resolver(public: bool = False, action=None):
|
|
9
9
|
|
|
10
10
|
def decorator(func: Callable):
|
|
11
11
|
|
|
12
|
+
func._is_public = public
|
|
13
|
+
|
|
12
14
|
@wraps(func)
|
|
13
15
|
async def wrapper(obj, info, **kwargs):
|
|
14
16
|
try:
|
|
15
17
|
context = info.context
|
|
16
18
|
token = getattr(context, "token", None)
|
|
17
19
|
|
|
18
|
-
# 🔐 AUTH
|
|
19
20
|
if not public:
|
|
20
21
|
if not token:
|
|
21
|
-
raise
|
|
22
|
+
raise UnauthorizedException(
|
|
22
23
|
error="Authorization required"
|
|
23
24
|
)
|
|
24
25
|
|
|
25
|
-
# 🎯 AUTHORIZATION (roles)
|
|
26
26
|
if action:
|
|
27
27
|
if Profile(token.abbreviation) not in action.allowed_roles:
|
|
28
28
|
raise AccessDeniedException(
|
|
@@ -42,8 +42,8 @@ def resolver(public: bool = False, action=None):
|
|
|
42
42
|
return Response(
|
|
43
43
|
status=e.status_code,
|
|
44
44
|
message=e.message,
|
|
45
|
-
error=
|
|
46
|
-
)
|
|
45
|
+
error=e.error
|
|
46
|
+
)
|
|
47
47
|
|
|
48
48
|
except Exception as e:
|
|
49
49
|
logger.exception(f"Unexpected error: {str(e)}")
|
|
@@ -52,9 +52,7 @@ def resolver(public: bool = False, action=None):
|
|
|
52
52
|
status="DB_ERROR_UNEXPECTED",
|
|
53
53
|
message="Could not process request.",
|
|
54
54
|
error=str(e)
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
wrapper._is_public = public
|
|
55
|
+
)
|
|
58
56
|
|
|
59
57
|
return wrapper
|
|
60
58
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import inspect
|
|
1
2
|
import json
|
|
2
3
|
from graphql.pyutils import is_awaitable
|
|
3
4
|
from ariadne.types import Extension
|
|
@@ -39,7 +40,6 @@ class AuditExtension(Extension):
|
|
|
39
40
|
|
|
40
41
|
# cache body
|
|
41
42
|
context._cached_body = getattr(context, "_cached_body", None) or await request.json()
|
|
42
|
-
|
|
43
43
|
body = context._cached_body
|
|
44
44
|
|
|
45
45
|
# skip introspection
|
|
@@ -55,7 +55,7 @@ class AuditExtension(Extension):
|
|
|
55
55
|
if is_root:
|
|
56
56
|
|
|
57
57
|
# identificar resolver público
|
|
58
|
-
resolver_fn =
|
|
58
|
+
resolver_fn = inspect.unwrap(next_)
|
|
59
59
|
is_public = getattr(resolver_fn, "_is_public", False)
|
|
60
60
|
|
|
61
61
|
# inicializar auth UNA SOLA VEZ
|
|
@@ -66,27 +66,20 @@ class AuditExtension(Extension):
|
|
|
66
66
|
container = context.container
|
|
67
67
|
token_service = container.token_service
|
|
68
68
|
|
|
69
|
-
aes_auth = request.app.state.aes_auth
|
|
70
|
-
aes_user = request.app.state.aes_user
|
|
71
|
-
|
|
72
|
-
if not aes_auth or not aes_user:
|
|
73
|
-
aes_auth = None
|
|
74
|
-
aes_user = None
|
|
75
|
-
|
|
76
69
|
original_token = None
|
|
70
|
+
context.aes_auth = None
|
|
77
71
|
if not is_public and headers.get("authorization"):
|
|
72
|
+
aes_auth = request.app.state.aes_auth
|
|
73
|
+
aes_user = request.app.state.aes_user
|
|
74
|
+
|
|
78
75
|
try:
|
|
79
76
|
original_token = await token_service.authenticate(headers, aes_user)
|
|
80
77
|
except Exception:
|
|
81
78
|
original_token = None
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
context.token = original_token
|
|
85
|
-
context.aes_auth = aes_auth
|
|
80
|
+
context.aes_auth = aes_auth
|
|
86
81
|
|
|
87
82
|
# VALIDACIÓN SOLO ROOT
|
|
88
|
-
original_token = getattr(context, "_original_token", None)
|
|
89
|
-
|
|
90
83
|
if not is_public and not original_token:
|
|
91
84
|
raise ValueError("Authorization required")
|
|
92
85
|
|
|
@@ -100,30 +93,37 @@ class AuditExtension(Extension):
|
|
|
100
93
|
|
|
101
94
|
decrypted_payload = None
|
|
102
95
|
|
|
103
|
-
if encrypted_payload is not None:
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
if not is_public and encrypted_payload is not None:
|
|
97
|
+
|
|
98
|
+
if not isinstance(encrypted_payload, str):
|
|
99
|
+
raise ValueError("Encrypted payload must be a string")
|
|
100
|
+
|
|
101
|
+
if not context.aes_auth:
|
|
102
|
+
raise ValueError("Missing AES configuration")
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
decrypted = AES.decrypt(context.aes_auth, encrypted_payload)
|
|
106
|
+
except Exception:
|
|
107
|
+
raise ValueError("Invalid encrypted payload")
|
|
108
|
+
|
|
109
|
+
if isinstance(decrypted, dict):
|
|
110
|
+
decrypted_payload = decrypted
|
|
106
111
|
|
|
107
|
-
elif isinstance(
|
|
112
|
+
elif isinstance(decrypted, str):
|
|
108
113
|
try:
|
|
109
|
-
decrypted_payload = json.loads(
|
|
114
|
+
decrypted_payload = json.loads(decrypted)
|
|
110
115
|
except Exception:
|
|
111
|
-
|
|
112
|
-
try:
|
|
113
|
-
decrypted_payload = AES.decrypt(context.aes_auth, encrypted_payload)
|
|
114
|
-
try:
|
|
115
|
-
decrypted_payload = json.loads(decrypted_payload)
|
|
116
|
-
except Exception:
|
|
117
|
-
pass
|
|
118
|
-
except Exception:
|
|
119
|
-
decrypted_payload = None
|
|
116
|
+
raise ValueError("Decrypted payload is not valid JSON")
|
|
120
117
|
|
|
118
|
+
else:
|
|
119
|
+
raise ValueError("Unsupported decrypted payload type")
|
|
120
|
+
|
|
121
121
|
if decrypted_payload is not None:
|
|
122
122
|
kwargs["data"] = decrypted_payload
|
|
123
123
|
|
|
124
124
|
if not is_public:
|
|
125
125
|
token = TenantPolicy.resolve(
|
|
126
|
-
token=
|
|
126
|
+
token=original_token,
|
|
127
127
|
headers=request.headers,
|
|
128
128
|
decrypted_payload=decrypted_payload,
|
|
129
129
|
operation_type=info.operation.operation.value
|
|
@@ -171,12 +171,10 @@ class AuditExtension(Extension):
|
|
|
171
171
|
if self.result is None:
|
|
172
172
|
return
|
|
173
173
|
|
|
174
|
-
decrypted_key =
|
|
174
|
+
decrypted_key = context.aes_auth
|
|
175
175
|
|
|
176
176
|
if isinstance(self.result, Response):
|
|
177
|
-
decrypted_key = self.result.key or
|
|
178
|
-
else:
|
|
179
|
-
decrypted_key = context.aes_auth
|
|
177
|
+
decrypted_key = self.result.key or decrypted_key
|
|
180
178
|
|
|
181
179
|
dispatcher = context.request.app.state.audit_dispatcher
|
|
182
180
|
|
|
@@ -5,7 +5,7 @@ from Osdental.Models._Audit import Audit
|
|
|
5
5
|
class AuditHelper:
|
|
6
6
|
|
|
7
7
|
@staticmethod
|
|
8
|
-
async def build_request_payload(audit: Audit) -> Dict:
|
|
8
|
+
async def build_request_payload(audit: Audit) -> Dict[str, Any]:
|
|
9
9
|
request = audit.request
|
|
10
10
|
audit_config = audit.audit_config
|
|
11
11
|
payload = audit.payload
|
|
@@ -14,7 +14,7 @@ class AuditHelper:
|
|
|
14
14
|
|
|
15
15
|
user_ip = request.headers.get("X-Forwarded-For")
|
|
16
16
|
if user_ip:
|
|
17
|
-
user_ip = user_ip.split(
|
|
17
|
+
user_ip = user_ip.split(",")[0]
|
|
18
18
|
else:
|
|
19
19
|
user_ip = getattr(request.client, "host", "*")
|
|
20
20
|
|
|
@@ -49,12 +49,12 @@ class AuditHelper:
|
|
|
49
49
|
def build_final_payload(
|
|
50
50
|
_type: Literal["RESPONSE", "ERROR"],
|
|
51
51
|
status_code: Any,
|
|
52
|
-
result: Optional[Any] =
|
|
52
|
+
result: Optional[Any] = None,
|
|
53
53
|
error: Optional[Any] = "*"
|
|
54
|
-
):
|
|
54
|
+
) -> Dict[str, Any]:
|
|
55
55
|
return {
|
|
56
56
|
"type": _type,
|
|
57
57
|
"httpResponseCode": status_code,
|
|
58
|
-
"messageOut": json.dumps(result) if isinstance(result, dict) else
|
|
58
|
+
"messageOut": json.dumps(result) if isinstance(result, dict) else "*",
|
|
59
59
|
"errorProducer": error
|
|
60
60
|
}
|
|
@@ -74,7 +74,7 @@ class AuditDispatcher:
|
|
|
74
74
|
self._queue.task_done()
|
|
75
75
|
|
|
76
76
|
# Logica
|
|
77
|
-
async def _process(self, payload: Dict[str, Any]):
|
|
77
|
+
async def _process(self, payload: Dict[str, Any]) -> None:
|
|
78
78
|
request = payload["request"]
|
|
79
79
|
request_payload = payload["request_payload"]
|
|
80
80
|
result: Response = payload["result"]
|
|
@@ -103,7 +103,7 @@ class AuditDispatcher:
|
|
|
103
103
|
|
|
104
104
|
# Obtencion de campos adicionales cuando es otro tipo de encriptacion o clave
|
|
105
105
|
encryption_type = result.encryption_type
|
|
106
|
-
decrypted_key = metadata
|
|
106
|
+
decrypted_key = metadata.get("decrypted_key")
|
|
107
107
|
|
|
108
108
|
if audit_type == Constant.MESSAGE_LOG_INTERNAL:
|
|
109
109
|
|
|
@@ -128,7 +128,7 @@ class AuditDispatcher:
|
|
|
128
128
|
audit_message = request_audit_payload | payload
|
|
129
129
|
|
|
130
130
|
else:
|
|
131
|
-
if encryption_type in VALID_TYPES and decrypted_key:
|
|
131
|
+
if encryption_type in VALID_TYPES and decrypted_key and data:
|
|
132
132
|
data = decryptor_data(encryption_type, decrypted_key, data)
|
|
133
133
|
|
|
134
134
|
payload = AuditHelper.build_final_payload(
|
|
@@ -139,4 +139,4 @@ class AuditDispatcher:
|
|
|
139
139
|
|
|
140
140
|
audit_message = request_audit_payload | payload
|
|
141
141
|
|
|
142
|
-
await self._messaging.send_message(audit_message)
|
|
142
|
+
await self._messaging.send_message(audit_message)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{encryptors-2.43 → encryptors-2.46}/src/Osdental/Rest/Middlewares/RequestContextMiddleware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|