Encryptors 2.31__tar.gz → 2.32__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.31 → encryptors-2.32}/PKG-INFO +1 -1
- {encryptors-2.31 → encryptors-2.32}/setup.py +1 -1
- {encryptors-2.31 → encryptors-2.32}/src/Encryptors.egg-info/PKG-INFO +1 -1
- {encryptors-2.31 → encryptors-2.32}/src/Encryptors.egg-info/SOURCES.txt +11 -26
- encryptors-2.32/src/Osdental/Database/Connection.py +66 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Encryptor/Bcrypt.py +1 -1
- encryptors-2.32/src/Osdental/Exception/ControlledException.py +142 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Graphql/Extensions/AuditExtension.py +7 -3
- encryptors-2.32/src/Osdental/Graphql/_Exceptions/__init__.py +8 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Graphql/_Helpers/_AuditHelper.py +9 -13
- encryptors-2.32/src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py +11 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Graphql/_Helpers/_TenantPolicy.py +10 -2
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Graphql/_Helpers/_TokenService.py +4 -6
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Helpers/AuditDispatcher.py +45 -17
- encryptors-2.32/src/Osdental/Http/APIClient.py +137 -0
- encryptors-2.32/src/Osdental/Http/_Exceptions.py +12 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Messaging/AzureServiceBus.py +12 -3
- encryptors-2.32/src/Osdental/Messaging/Kafka.py +11 -0
- encryptors-2.32/src/Osdental/Messaging/RabbitMQ.py +11 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Messaging/__init__.py +2 -2
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Models/Response.py +3 -3
- encryptors-2.32/src/Osdental/Models/_Audit.py +13 -0
- encryptors-2.32/src/Osdental/Rest/Context/RequestContext.py +19 -0
- encryptors-2.32/src/Osdental/Rest/Middlewares/RequestContextMiddleware.py +22 -0
- encryptors-2.32/src/Osdental/Shared/Enums/Constant.py +10 -0
- encryptors-2.31/src/Osdental/BlobStorage/Storage.py +0 -50
- encryptors-2.31/src/Osdental/Database/Connection.py +0 -218
- encryptors-2.31/src/Osdental/Database/UnitOfWork.py +0 -44
- encryptors-2.31/src/Osdental/Database/UowFactory.py +0 -8
- encryptors-2.31/src/Osdental/Decorators/AuditLog.py +0 -87
- encryptors-2.31/src/Osdental/Decorators/DecryptedData.py +0 -139
- encryptors-2.31/src/Osdental/Exception/ControlledException.py +0 -189
- encryptors-2.31/src/Osdental/ExternalHttp/Client.py +0 -117
- encryptors-2.31/src/Osdental/Grpc/Base/GrpcClientBase.py +0 -61
- encryptors-2.31/src/Osdental/Grpc/Client/PortalClient.py +0 -44
- encryptors-2.31/src/Osdental/Grpc/Generated/Common_pb2.py +0 -40
- encryptors-2.31/src/Osdental/Grpc/Generated/Common_pb2_grpc.py +0 -24
- encryptors-2.31/src/Osdental/Grpc/Generated/Portal_pb2.py +0 -37
- encryptors-2.31/src/Osdental/Grpc/Generated/Portal_pb2_grpc.py +0 -140
- encryptors-2.31/src/Osdental/InternalHttp/Request.py +0 -105
- encryptors-2.31/src/Osdental/InternalHttp/Response.py +0 -39
- encryptors-2.31/src/Osdental/Messaging/Kafka.py +0 -12
- encryptors-2.31/src/Osdental/Messaging/RabbitMQ.py +0 -12
- encryptors-2.31/src/Osdental/Models/CDataIntegration.py +0 -41
- encryptors-2.31/src/Osdental/ServicesBus/ServicesBus.py +0 -26
- encryptors-2.31/src/Osdental/ServicesBus/TaskQueue.py +0 -52
- encryptors-2.31/src/Osdental/Shared/Config/__init__.py +0 -15
- encryptors-2.31/src/Osdental/Shared/Enums/Constant.py +0 -16
- encryptors-2.31/src/Osdental/Shared/Enums/__init__.py +0 -0
- encryptors-2.31/src/Osdental/Shared/Utils/__init__.py +0 -0
- encryptors-2.31/src/Osdental/Shared/__init__.py +0 -0
- encryptors-2.31/src/Osdental/__init__.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/README.md +0 -0
- {encryptors-2.31 → encryptors-2.32}/setup.cfg +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Encryptors.egg-info/dependency_links.txt +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Encryptors.egg-info/entry_points.txt +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Encryptors.egg-info/requires.txt +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Encryptors.egg-info/top_level.txt +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Cli/__init__.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Database/BaseRepository.py +0 -0
- {encryptors-2.31/src/Osdental/BlobStorage → encryptors-2.32/src/Osdental/Database}/__init__.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Decorators/Grpc.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Decorators/Retry.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Decorators/SecureResolver.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Decorators/SqlDataNormalizer.py +0 -0
- {encryptors-2.31/src/Osdental/Database → encryptors-2.32/src/Osdental/Decorators}/__init__.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Encryptor/Aes.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Encryptor/Argon2.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Encryptor/Jwt.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Encryptor/Rsa.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Encryptor/Sha512.py +0 -0
- {encryptors-2.31/src/Osdental/Decorators → encryptors-2.32/src/Osdental/Encryptor}/__init__.py +0 -0
- {encryptors-2.31/src/Osdental/Encryptor → encryptors-2.32/src/Osdental/Exception}/__init__.py +0 -0
- {encryptors-2.31/src/Osdental/Exception → encryptors-2.32/src/Osdental/Graphql/Extensions}/__init__.py +0 -0
- {encryptors-2.31/src/Osdental/ExternalHttp → encryptors-2.32/src/Osdental/Graphql/_Helpers}/__init__.py +0 -0
- {encryptors-2.31/src/Osdental/Graphql/Extensions → encryptors-2.32/src/Osdental/Graphql}/__init__.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Helpers/KeyVaultService.py +0 -0
- {encryptors-2.31/src/Osdental/Graphql/_Helpers → encryptors-2.32/src/Osdental/Helpers}/__init__.py +0 -0
- {encryptors-2.31/src/Osdental/Graphql → encryptors-2.32/src/Osdental/Http}/__init__.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Models/AuditConfig.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Models/Catalog.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Models/Legacy.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Models/Token.py +0 -0
- {encryptors-2.31/src/Osdental/Grpc/Base → encryptors-2.32/src/Osdental/Models}/__init__.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/RedisCache/Redis.py +0 -0
- {encryptors-2.31/src/Osdental/Grpc/Client → encryptors-2.32/src/Osdental/RedisCache}/__init__.py +0 -0
- {encryptors-2.31/src/Osdental/Grpc/Generated → encryptors-2.32/src/Osdental/Rest/Context}/__init__.py +0 -0
- {encryptors-2.31/src/Osdental/Grpc → encryptors-2.32/src/Osdental/Rest/Middlewares}/__init__.py +0 -0
- {encryptors-2.31/src/Osdental/Helpers → encryptors-2.32/src/Osdental/Rest}/__init__.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Enums/Code.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Enums/FileType.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Enums/GrahpqlOperation.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Enums/Message.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Enums/Profile.py +0 -0
- {encryptors-2.31/src/Osdental/InternalHttp → encryptors-2.32/src/Osdental/Shared/Enums}/__init__.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Logger.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Utils/CaseConverter.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Utils/CodeGenerator.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Utils/DataNormalizer.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Utils/DataUtils.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Utils/DateUtils.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Utils/FileMetaData.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Utils/HashValidator.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Utils/Mapper.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Utils/PasswordGenerator.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Utils/QueryGenerator.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Shared/Utils/TextProcessor.py +0 -0
- {encryptors-2.31/src/Osdental/Models → encryptors-2.32/src/Osdental/Shared/Utils}/__init__.py +0 -0
- {encryptors-2.31/src/Osdental/RedisCache → encryptors-2.32/src/Osdental/Shared}/__init__.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Storage/S3Storage.py +0 -0
- {encryptors-2.31 → encryptors-2.32}/src/Osdental/Storage/__init__.py +0 -0
- {encryptors-2.31/src/Osdental/ServicesBus → encryptors-2.32/src/Osdental}/__init__.py +0 -0
|
@@ -7,16 +7,10 @@ src/Encryptors.egg-info/entry_points.txt
|
|
|
7
7
|
src/Encryptors.egg-info/requires.txt
|
|
8
8
|
src/Encryptors.egg-info/top_level.txt
|
|
9
9
|
src/Osdental/__init__.py
|
|
10
|
-
src/Osdental/BlobStorage/Storage.py
|
|
11
|
-
src/Osdental/BlobStorage/__init__.py
|
|
12
10
|
src/Osdental/Cli/__init__.py
|
|
13
11
|
src/Osdental/Database/BaseRepository.py
|
|
14
12
|
src/Osdental/Database/Connection.py
|
|
15
|
-
src/Osdental/Database/UnitOfWork.py
|
|
16
|
-
src/Osdental/Database/UowFactory.py
|
|
17
13
|
src/Osdental/Database/__init__.py
|
|
18
|
-
src/Osdental/Decorators/AuditLog.py
|
|
19
|
-
src/Osdental/Decorators/DecryptedData.py
|
|
20
14
|
src/Osdental/Decorators/Grpc.py
|
|
21
15
|
src/Osdental/Decorators/Retry.py
|
|
22
16
|
src/Osdental/Decorators/SecureResolver.py
|
|
@@ -31,50 +25,41 @@ src/Osdental/Encryptor/Sha512.py
|
|
|
31
25
|
src/Osdental/Encryptor/__init__.py
|
|
32
26
|
src/Osdental/Exception/ControlledException.py
|
|
33
27
|
src/Osdental/Exception/__init__.py
|
|
34
|
-
src/Osdental/ExternalHttp/Client.py
|
|
35
|
-
src/Osdental/ExternalHttp/__init__.py
|
|
36
28
|
src/Osdental/Graphql/__init__.py
|
|
37
29
|
src/Osdental/Graphql/Extensions/AuditExtension.py
|
|
38
30
|
src/Osdental/Graphql/Extensions/__init__.py
|
|
31
|
+
src/Osdental/Graphql/_Exceptions/__init__.py
|
|
39
32
|
src/Osdental/Graphql/_Helpers/_AuditHelper.py
|
|
33
|
+
src/Osdental/Graphql/_Helpers/_ExtractAuthToken.py
|
|
40
34
|
src/Osdental/Graphql/_Helpers/_TenantPolicy.py
|
|
41
35
|
src/Osdental/Graphql/_Helpers/_TokenService.py
|
|
42
36
|
src/Osdental/Graphql/_Helpers/__init__.py
|
|
43
|
-
src/Osdental/Grpc/__init__.py
|
|
44
|
-
src/Osdental/Grpc/Base/GrpcClientBase.py
|
|
45
|
-
src/Osdental/Grpc/Base/__init__.py
|
|
46
|
-
src/Osdental/Grpc/Client/PortalClient.py
|
|
47
|
-
src/Osdental/Grpc/Client/__init__.py
|
|
48
|
-
src/Osdental/Grpc/Generated/Common_pb2.py
|
|
49
|
-
src/Osdental/Grpc/Generated/Common_pb2_grpc.py
|
|
50
|
-
src/Osdental/Grpc/Generated/Portal_pb2.py
|
|
51
|
-
src/Osdental/Grpc/Generated/Portal_pb2_grpc.py
|
|
52
|
-
src/Osdental/Grpc/Generated/__init__.py
|
|
53
37
|
src/Osdental/Helpers/AuditDispatcher.py
|
|
54
38
|
src/Osdental/Helpers/KeyVaultService.py
|
|
55
39
|
src/Osdental/Helpers/__init__.py
|
|
56
|
-
src/Osdental/
|
|
57
|
-
src/Osdental/
|
|
58
|
-
src/Osdental/
|
|
40
|
+
src/Osdental/Http/APIClient.py
|
|
41
|
+
src/Osdental/Http/_Exceptions.py
|
|
42
|
+
src/Osdental/Http/__init__.py
|
|
59
43
|
src/Osdental/Messaging/AzureServiceBus.py
|
|
60
44
|
src/Osdental/Messaging/Kafka.py
|
|
61
45
|
src/Osdental/Messaging/RabbitMQ.py
|
|
62
46
|
src/Osdental/Messaging/__init__.py
|
|
63
47
|
src/Osdental/Models/AuditConfig.py
|
|
64
|
-
src/Osdental/Models/CDataIntegration.py
|
|
65
48
|
src/Osdental/Models/Catalog.py
|
|
66
49
|
src/Osdental/Models/Legacy.py
|
|
67
50
|
src/Osdental/Models/Response.py
|
|
68
51
|
src/Osdental/Models/Token.py
|
|
52
|
+
src/Osdental/Models/_Audit.py
|
|
69
53
|
src/Osdental/Models/__init__.py
|
|
70
54
|
src/Osdental/RedisCache/Redis.py
|
|
71
55
|
src/Osdental/RedisCache/__init__.py
|
|
72
|
-
src/Osdental/
|
|
73
|
-
src/Osdental/
|
|
74
|
-
src/Osdental/
|
|
56
|
+
src/Osdental/Rest/__init__.py
|
|
57
|
+
src/Osdental/Rest/Context/RequestContext.py
|
|
58
|
+
src/Osdental/Rest/Context/__init__.py
|
|
59
|
+
src/Osdental/Rest/Middlewares/RequestContextMiddleware.py
|
|
60
|
+
src/Osdental/Rest/Middlewares/__init__.py
|
|
75
61
|
src/Osdental/Shared/Logger.py
|
|
76
62
|
src/Osdental/Shared/__init__.py
|
|
77
|
-
src/Osdental/Shared/Config/__init__.py
|
|
78
63
|
src/Osdental/Shared/Enums/Code.py
|
|
79
64
|
src/Osdental/Shared/Enums/Constant.py
|
|
80
65
|
src/Osdental/Shared/Enums/FileType.py
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
import urllib
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from azure.identity import DefaultAzureCredential
|
|
5
|
+
from sqlalchemy import event
|
|
6
|
+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
|
7
|
+
from sqlalchemy.orm import sessionmaker
|
|
8
|
+
|
|
9
|
+
SQL_COPT_SS_ACCESS_TOKEN = 1256
|
|
10
|
+
class Connection:
|
|
11
|
+
|
|
12
|
+
_instances: Dict[str, 'Connection'] = {}
|
|
13
|
+
|
|
14
|
+
def __new__(cls, db_url: str):
|
|
15
|
+
if db_url not in cls._instances:
|
|
16
|
+
cls._instances[db_url] = super(Connection, cls).__new__(cls)
|
|
17
|
+
return cls._instances[db_url]
|
|
18
|
+
|
|
19
|
+
def __init__(self, db_url: str):
|
|
20
|
+
if not hasattr(self, 'initialized'):
|
|
21
|
+
self.credential = DefaultAzureCredential()
|
|
22
|
+
raw_odbc_string = db_url
|
|
23
|
+
params = urllib.parse.quote_plus(raw_odbc_string)
|
|
24
|
+
sqlalchemy_url = f"mssql+aioodbc:///?odbc_connect={params}"
|
|
25
|
+
self.engine = create_async_engine(
|
|
26
|
+
sqlalchemy_url,
|
|
27
|
+
pool_size=20,
|
|
28
|
+
max_overflow=40,
|
|
29
|
+
pool_timeout=30,
|
|
30
|
+
pool_recycle=1800,
|
|
31
|
+
pool_pre_ping=True
|
|
32
|
+
)
|
|
33
|
+
self._attach_token_listener()
|
|
34
|
+
self.session_factory = sessionmaker(
|
|
35
|
+
bind=self.engine,
|
|
36
|
+
class_=AsyncSession,
|
|
37
|
+
expire_on_commit=False
|
|
38
|
+
)
|
|
39
|
+
self.initialized = True
|
|
40
|
+
|
|
41
|
+
def _attach_token_listener(self):
|
|
42
|
+
|
|
43
|
+
@event.listens_for(self.engine.sync_engine, "do_connect")
|
|
44
|
+
def provide_token(dialect, conn_rec, cargs, cparams):
|
|
45
|
+
|
|
46
|
+
token = self.credential.get_token(
|
|
47
|
+
"https://database.windows.net/.default"
|
|
48
|
+
).token
|
|
49
|
+
|
|
50
|
+
token_bytes = token.encode("utf-16-le")
|
|
51
|
+
|
|
52
|
+
token_struct = struct.pack(
|
|
53
|
+
f"<I{len(token_bytes)}s",
|
|
54
|
+
len(token_bytes),
|
|
55
|
+
token_bytes
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
cparams["attrs_before"] = {
|
|
59
|
+
SQL_COPT_SS_ACCESS_TOKEN: token_struct
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def close_engine(self) -> None:
|
|
64
|
+
"""Dispose of the engine and remove instance."""
|
|
65
|
+
if self.engine:
|
|
66
|
+
await self.engine.dispose()
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from Osdental.Shared.Enums.Code import Code
|
|
3
|
+
from Osdental.Shared.Enums.Message import Message
|
|
4
|
+
|
|
5
|
+
class OSDException(Exception):
|
|
6
|
+
""" Base class for all custom exceptions. """
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
10
|
+
error: str = None,
|
|
11
|
+
status_code: Any = Code.APP_ERROR_CODE
|
|
12
|
+
):
|
|
13
|
+
super().__init__(error)
|
|
14
|
+
self.message = message
|
|
15
|
+
self.error = error
|
|
16
|
+
self.status_code = status_code
|
|
17
|
+
|
|
18
|
+
class UnauthorizedException(OSDException):
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
message: str = Message.PORTAL_ACCESS_RESTRICTED_MSG,
|
|
22
|
+
error: str = None,
|
|
23
|
+
status_code: str = Code.UNAUTHORIZATED_CODE
|
|
24
|
+
):
|
|
25
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
26
|
+
|
|
27
|
+
class RequestDataException(OSDException):
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
message: str = Message.INVALID_REQUEST_PARAMS_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
|
+
|
|
36
|
+
class DatabaseConnectionException(OSDException):
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
message: str = Message.INVALID_FORMAT_MSG,
|
|
40
|
+
error: str = None,
|
|
41
|
+
status_code: str = Code.DATABASE_CONNECTION_ERROR_CODE
|
|
42
|
+
):
|
|
43
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
44
|
+
|
|
45
|
+
class DatabaseException(OSDException):
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
49
|
+
error: str = None,
|
|
50
|
+
status_code: str = Code.DATABASE_ERROR_CODE
|
|
51
|
+
):
|
|
52
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
53
|
+
|
|
54
|
+
class RSAEncryptException(OSDException):
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
58
|
+
error: str = None,
|
|
59
|
+
status_code: str = Code.RSA_ERROR_CODE
|
|
60
|
+
):
|
|
61
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
62
|
+
|
|
63
|
+
class AESEncryptException(OSDException):
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
67
|
+
error: str = None,
|
|
68
|
+
status_code: str = Code.AES_ERROR_CODE
|
|
69
|
+
):
|
|
70
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
71
|
+
|
|
72
|
+
class JWTokenException(OSDException):
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
76
|
+
error: str = None,
|
|
77
|
+
status_code: str = Code.JWT_ERROR_CODE
|
|
78
|
+
):
|
|
79
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
80
|
+
|
|
81
|
+
class AzureException(OSDException):
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
85
|
+
error: str = None,
|
|
86
|
+
status_code: str = Code.AZURE_ERROR_CODE
|
|
87
|
+
):
|
|
88
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
89
|
+
|
|
90
|
+
class RedisException(OSDException):
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
94
|
+
error: str = None,
|
|
95
|
+
status_code: str = Code.REDIS_ERROR_CODE
|
|
96
|
+
):
|
|
97
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
98
|
+
|
|
99
|
+
class ValidationDataException(OSDException):
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
103
|
+
error: str = None,
|
|
104
|
+
status_code: str = Code.REQUEST_VALIDATION_ERROR_CODE
|
|
105
|
+
):
|
|
106
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
107
|
+
|
|
108
|
+
class UnexpectedException(OSDException):
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
message: str = Message.UNEXPECTED_ERROR_MSG,
|
|
112
|
+
error: str = None,
|
|
113
|
+
status_code: str = Code.UNEXPECTED_ERROR_CODE
|
|
114
|
+
):
|
|
115
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
116
|
+
|
|
117
|
+
class MissingFieldException(OSDException):
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
message: str = Message.MISSING_FIELD_ERROR_MSG,
|
|
121
|
+
error: str = None,
|
|
122
|
+
status_code: str = Code.MISSING_FIELD_ERROR_CODE
|
|
123
|
+
):
|
|
124
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
125
|
+
|
|
126
|
+
class ProfilePermissionDeniedException(OSDException):
|
|
127
|
+
def __init__(
|
|
128
|
+
self,
|
|
129
|
+
message: str = Message.PROFILE_PERMISSION_DENIED_MSG,
|
|
130
|
+
error: str = None,
|
|
131
|
+
status_code: str = Code.PROFILE_PERMISSION_DENIED_CODE
|
|
132
|
+
):
|
|
133
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
134
|
+
|
|
135
|
+
class InvalidFormatException(OSDException):
|
|
136
|
+
def __init__(
|
|
137
|
+
self,
|
|
138
|
+
message: str = Message.INVALID_FORMAT_MSG,
|
|
139
|
+
error: str = None,
|
|
140
|
+
status_code: str = Code.INVALID_FORMAT_CODE
|
|
141
|
+
):
|
|
142
|
+
super().__init__(message=message, error=error, status_code=status_code)
|
|
@@ -3,7 +3,7 @@ from graphql.pyutils import is_awaitable
|
|
|
3
3
|
from ariadne.types import Extension
|
|
4
4
|
from Osdental.Encryptor.Aes import AES
|
|
5
5
|
from Osdental.Graphql._Helpers._TenantPolicy import TenantPolicy
|
|
6
|
-
from Osdental.
|
|
6
|
+
from Osdental.Graphql._Exceptions import AESKeyNotFound
|
|
7
7
|
|
|
8
8
|
class AuditExtension(Extension):
|
|
9
9
|
|
|
@@ -29,7 +29,7 @@ class AuditExtension(Extension):
|
|
|
29
29
|
|
|
30
30
|
aes_key = request.app.state.aes_auth
|
|
31
31
|
if not aes_key:
|
|
32
|
-
raise
|
|
32
|
+
raise AESKeyNotFound("Could not find authorization key Aes.")
|
|
33
33
|
|
|
34
34
|
original_token = await token_service.authenticate(request)
|
|
35
35
|
|
|
@@ -50,7 +50,7 @@ class AuditExtension(Extension):
|
|
|
50
50
|
except Exception:
|
|
51
51
|
decrypted_payload = None
|
|
52
52
|
|
|
53
|
-
token
|
|
53
|
+
token = TenantPolicy.resolve(
|
|
54
54
|
token=original_token,
|
|
55
55
|
headers=request.headers,
|
|
56
56
|
decrypted_payload=decrypted_payload,
|
|
@@ -83,6 +83,10 @@ class AuditExtension(Extension):
|
|
|
83
83
|
self.errors = errors
|
|
84
84
|
|
|
85
85
|
def request_finished(self, context):
|
|
86
|
+
query = self.request_payload.get("query", "")
|
|
87
|
+
if "__schema" in query or "__type" in query: return
|
|
88
|
+
|
|
89
|
+
if not hasattr(context, "audit_plain_response"): return
|
|
86
90
|
|
|
87
91
|
dispatcher = context.request.app.state.audit_dispatcher
|
|
88
92
|
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from typing import Dict, Literal, Any, Optional
|
|
3
|
-
from
|
|
4
|
-
from Osdental.Models.AuditConfig import AuditConfig
|
|
3
|
+
from Osdental.Models._Audit import Audit
|
|
5
4
|
|
|
6
5
|
class AuditHelper:
|
|
7
6
|
|
|
8
7
|
@staticmethod
|
|
9
|
-
async def build_request_payload(
|
|
10
|
-
request
|
|
11
|
-
audit_config
|
|
12
|
-
payload
|
|
13
|
-
|
|
14
|
-
full_name: str = "Joe Doe"
|
|
15
|
-
) -> Dict:
|
|
16
|
-
|
|
8
|
+
async def build_request_payload(audit: Audit) -> Dict:
|
|
9
|
+
request = audit.request
|
|
10
|
+
audit_config = audit.audit_config
|
|
11
|
+
payload = audit.payload
|
|
12
|
+
|
|
17
13
|
default_value = "*"
|
|
18
14
|
|
|
19
15
|
user_ip = request.headers.get("X-Forwarded-For")
|
|
@@ -39,14 +35,14 @@ class AuditHelper:
|
|
|
39
35
|
"microServiceUrl": str(request.url),
|
|
40
36
|
"microServiceName": audit_config.microservice_name,
|
|
41
37
|
"microServiceVersion": audit_config.microservice_version,
|
|
42
|
-
"serviceName": operation_name,
|
|
38
|
+
"serviceName": audit.operation_name,
|
|
43
39
|
"machineNameUser": request.headers.get("Machinenameuser", default_value),
|
|
44
40
|
"ipUser": user_ip or default_value,
|
|
45
|
-
"userName": full_name,
|
|
41
|
+
"userName": audit.full_name,
|
|
46
42
|
"localitation": default_value,
|
|
47
43
|
"httpMethod": request.method,
|
|
48
44
|
"messageIn": json.dumps(payload) if payload else default_value,
|
|
49
|
-
"auditLog":
|
|
45
|
+
"auditLog": audit.audit_type
|
|
50
46
|
}
|
|
51
47
|
|
|
52
48
|
@staticmethod
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from fastapi import Request
|
|
2
|
+
|
|
3
|
+
class ExtractAuthToken:
|
|
4
|
+
|
|
5
|
+
@staticmethod
|
|
6
|
+
async def get_auth_token(request: Request) -> str:
|
|
7
|
+
authorization = request.headers.get("authorization")
|
|
8
|
+
if not authorization or not authorization.startswith("Bearer "):
|
|
9
|
+
raise ValueError("Missing Bearer token")
|
|
10
|
+
|
|
11
|
+
return authorization.split(" ")[1]
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import Dict, Any
|
|
2
2
|
from uuid import UUID
|
|
3
3
|
from graphql import OperationType
|
|
4
|
+
from starlette.datastructures import Headers
|
|
5
|
+
from Osdental.Encryptor.Aes import AES
|
|
6
|
+
from Osdental.Models.Token import AuthToken
|
|
4
7
|
|
|
5
8
|
class TenantPolicy:
|
|
6
9
|
|
|
7
10
|
@staticmethod
|
|
8
|
-
def resolve(
|
|
11
|
+
def resolve(
|
|
12
|
+
token: AuthToken,
|
|
13
|
+
headers: Headers,
|
|
14
|
+
decrypted_payload: Dict[str, Any],
|
|
15
|
+
operation_type: str
|
|
16
|
+
) -> AuthToken:
|
|
9
17
|
aes = AES()
|
|
10
18
|
# Solo aplicamos cambios si es QUERY
|
|
11
19
|
if operation_type != OperationType.QUERY.value:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from fastapi import Request
|
|
1
2
|
from Osdental.Encryptor.Aes import AES
|
|
2
3
|
from Osdental.Encryptor.Jwt import JWT
|
|
3
4
|
from Osdental.Models.Token import AuthToken
|
|
5
|
+
from Osdental.Graphql._Helpers._ExtractAuthToken import ExtractAuthToken
|
|
4
6
|
|
|
5
7
|
class TokenService:
|
|
6
8
|
|
|
@@ -9,12 +11,8 @@ class TokenService:
|
|
|
9
11
|
self.auth_validator = auth_validator
|
|
10
12
|
self.aes = AES()
|
|
11
13
|
|
|
12
|
-
async def authenticate(self, request):
|
|
13
|
-
|
|
14
|
-
if not authorization or not authorization.startswith("Bearer "):
|
|
15
|
-
raise ValueError("Missing Bearer token")
|
|
16
|
-
|
|
17
|
-
encrypted_token = authorization.split(" ")[1]
|
|
14
|
+
async def authenticate(self, request: Request) -> AuthToken:
|
|
15
|
+
encrypted_token = ExtractAuthToken.get_auth_token(request)
|
|
18
16
|
|
|
19
17
|
aes_user = request.app.state.aes_user
|
|
20
18
|
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
from fastapi import Request
|
|
3
4
|
from Osdental.Models.AuditConfig import AuditConfig
|
|
4
5
|
from Osdental.Messaging import IMessageQueue
|
|
5
6
|
from Osdental.Graphql._Helpers._AuditHelper import AuditHelper
|
|
7
|
+
from Osdental.Models._Audit import Audit
|
|
8
|
+
from Osdental.Models.Response import Response
|
|
9
|
+
from Osdental.Shared.Enums.Constant import Constant
|
|
10
|
+
from Osdental.Shared.Logger import logger
|
|
6
11
|
|
|
7
12
|
class AuditDispatcher:
|
|
8
13
|
|
|
@@ -29,19 +34,25 @@ class AuditDispatcher:
|
|
|
29
34
|
await self._worker_task
|
|
30
35
|
|
|
31
36
|
# Ultra rápido, no bloquea
|
|
32
|
-
def dispatch(
|
|
37
|
+
def dispatch(
|
|
38
|
+
self,
|
|
39
|
+
request: Request,
|
|
40
|
+
request_payload: Dict[str, Any],
|
|
41
|
+
result: Response,
|
|
42
|
+
audit_type: str = None
|
|
43
|
+
):
|
|
33
44
|
try:
|
|
34
45
|
payload = {
|
|
35
46
|
"request": request,
|
|
36
47
|
"request_payload": request_payload,
|
|
37
48
|
"result": result,
|
|
38
|
-
"
|
|
49
|
+
"audit_type": audit_type
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
self._queue.put_nowait(payload)
|
|
42
53
|
|
|
43
54
|
except asyncio.QueueFull:
|
|
44
|
-
|
|
55
|
+
logger.error("Audit queue full. Dropping message.")
|
|
45
56
|
|
|
46
57
|
# Worker real
|
|
47
58
|
async def _worker(self):
|
|
@@ -55,39 +66,56 @@ class AuditDispatcher:
|
|
|
55
66
|
try:
|
|
56
67
|
await self._process(payload)
|
|
57
68
|
except Exception:
|
|
58
|
-
|
|
69
|
+
logger.exception("Audit processing failed")
|
|
59
70
|
finally:
|
|
60
71
|
self._queue.task_done()
|
|
61
72
|
|
|
62
73
|
# Logica
|
|
63
|
-
async def _process(self, payload):
|
|
74
|
+
async def _process(self, payload: Dict[str, Any]):
|
|
64
75
|
request = payload["request"]
|
|
65
76
|
request_payload = payload["request_payload"]
|
|
66
77
|
result = payload["result"]
|
|
78
|
+
audit_type = payload["audit_type"]
|
|
67
79
|
|
|
68
80
|
operation_name = request_payload.get("operation_name")
|
|
69
81
|
if operation_name == 'UnknownOperation':
|
|
70
|
-
|
|
82
|
+
logger.info("Skipping audit: no data in GraphQL response")
|
|
71
83
|
return
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
audit = Audit(
|
|
86
|
+
audit_type=audit_type,
|
|
74
87
|
request=request,
|
|
75
88
|
audit_config=self._audit_config,
|
|
76
89
|
operation_name=operation_name,
|
|
77
90
|
full_name=request_payload.get("user"),
|
|
78
91
|
payload=request_payload.get("variables")
|
|
79
92
|
)
|
|
93
|
+
request_audit_payload = await AuditHelper.build_request_payload(audit=audit)
|
|
80
94
|
|
|
81
|
-
|
|
82
|
-
message = result.
|
|
83
|
-
data = result.
|
|
95
|
+
status_code = result.status
|
|
96
|
+
message = result.message
|
|
97
|
+
data = result.data
|
|
98
|
+
error = result.error if result.error else message
|
|
84
99
|
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
if audit_type == Constant.MESSAGE_LOG_INTERNAL:
|
|
101
|
+
|
|
102
|
+
ERROR_PREFIXES = ("DB_ERROR", "DB_WARNING")
|
|
103
|
+
|
|
104
|
+
is_error = status_code.upper().startswith(ERROR_PREFIXES)
|
|
105
|
+
|
|
106
|
+
else:
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
is_error = not (200 <= status_code < 300)
|
|
110
|
+
except ValueError:
|
|
111
|
+
is_error = True
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if is_error:
|
|
87
115
|
payload = AuditHelper.build_final_payload(
|
|
88
116
|
_type="ERROR",
|
|
89
|
-
status_code=
|
|
90
|
-
error=
|
|
117
|
+
status_code=status_code,
|
|
118
|
+
error=error
|
|
91
119
|
)
|
|
92
120
|
|
|
93
121
|
audit_message = request_audit_payload | payload
|
|
@@ -95,10 +123,10 @@ class AuditDispatcher:
|
|
|
95
123
|
else:
|
|
96
124
|
payload = AuditHelper.build_final_payload(
|
|
97
125
|
_type="RESPONSE",
|
|
98
|
-
status_code=
|
|
126
|
+
status_code=status_code,
|
|
99
127
|
result=data
|
|
100
128
|
)
|
|
101
129
|
|
|
102
130
|
audit_message = request_audit_payload | payload
|
|
103
131
|
|
|
104
|
-
await self._messaging.
|
|
132
|
+
await self._messaging.send_message(audit_message)
|