maleo-foundation 0.1.36__py3-none-any.whl → 0.1.37__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.
- maleo_foundation/managers/service.py +3 -3
- maleo_foundation/middlewares/authentication.py +2 -2
- maleo_foundation/middlewares/base.py +2 -19
- maleo_foundation/utils/__init__.py +4 -2
- maleo_foundation/utils/extractor.py +16 -14
- maleo_foundation/utils/keyloader.py +52 -50
- {maleo_foundation-0.1.36.dist-info → maleo_foundation-0.1.37.dist-info}/METADATA +1 -1
- {maleo_foundation-0.1.36.dist-info → maleo_foundation-0.1.37.dist-info}/RECORD +10 -10
- {maleo_foundation-0.1.36.dist-info → maleo_foundation-0.1.37.dist-info}/WHEEL +0 -0
- {maleo_foundation-0.1.36.dist-info → maleo_foundation-0.1.37.dist-info}/top_level.txt +0 -0
@@ -20,7 +20,7 @@ from maleo_foundation.middlewares.base import RequestProcessor
|
|
20
20
|
from maleo_foundation.services.token import BaseTokenService
|
21
21
|
from maleo_foundation.types import BaseTypes
|
22
22
|
from maleo_foundation.utils.exceptions import BaseExceptions
|
23
|
-
from maleo_foundation.utils.keyloader import
|
23
|
+
from maleo_foundation.utils.keyloader import BaseKeyLoaders
|
24
24
|
from maleo_foundation.utils.logging import GoogleCloudLogging, ServiceLogger, MiddlewareLogger
|
25
25
|
|
26
26
|
class LogConfig(BaseModel):
|
@@ -294,14 +294,14 @@ class ServiceManager:
|
|
294
294
|
def _parse_keys(self) -> None:
|
295
295
|
#* Parse private key
|
296
296
|
key_type = BaseEnums.KeyType.PRIVATE
|
297
|
-
private =
|
297
|
+
private = BaseKeyLoaders.load_rsa(
|
298
298
|
type=key_type,
|
299
299
|
path=self._settings.PRIVATE_KEY_PATH,
|
300
300
|
password=self._settings.KEY_PASSWORD
|
301
301
|
)
|
302
302
|
#* Parse public key
|
303
303
|
key_type = BaseEnums.KeyType.PUBLIC
|
304
|
-
public =
|
304
|
+
public = BaseKeyLoaders.load_rsa(
|
305
305
|
type=key_type,
|
306
306
|
path=self._settings.PUBLIC_KEY_PATH
|
307
307
|
)
|
@@ -6,7 +6,7 @@ from typing import Tuple
|
|
6
6
|
from maleo_foundation.authentication import Credentials, User
|
7
7
|
from maleo_foundation.models.transfers.parameters.token import BaseTokenParametersTransfers
|
8
8
|
from maleo_foundation.services.token import BaseTokenService
|
9
|
-
from maleo_foundation.utils.extractor import
|
9
|
+
from maleo_foundation.utils.extractor import BaseExtractors
|
10
10
|
from maleo_foundation.utils.logging import MiddlewareLogger
|
11
11
|
|
12
12
|
class Backend(AuthenticationBackend):
|
@@ -16,7 +16,7 @@ class Backend(AuthenticationBackend):
|
|
16
16
|
self._key = key
|
17
17
|
|
18
18
|
async def authenticate(self, conn:HTTPConnection) -> Tuple[Credentials, User]:
|
19
|
-
client_ip = extract_client_ip(conn)
|
19
|
+
client_ip = BaseExtractors.extract_client_ip(conn)
|
20
20
|
if "Authorization" not in conn.headers:
|
21
21
|
self._logger.info(f"Request | IP: {client_ip} | URL: {conn.url.path} - Result | General: Header did not contain authorization")
|
22
22
|
return Credentials(), User(authenticated=False)
|
@@ -9,7 +9,7 @@ from fastapi.responses import JSONResponse
|
|
9
9
|
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
10
10
|
from typing import Awaitable, Callable, Optional, Sequence
|
11
11
|
from maleo_foundation.models.responses import BaseResponses
|
12
|
-
from maleo_foundation.utils.extractor import
|
12
|
+
from maleo_foundation.utils.extractor import BaseExtractors
|
13
13
|
from maleo_foundation.utils.logging import MiddlewareLogger
|
14
14
|
|
15
15
|
RequestProcessor = Callable[[Request], Awaitable[Optional[Response]]]
|
@@ -80,23 +80,6 @@ class BaseMiddleware(BaseHTTPMiddleware):
|
|
80
80
|
self.last_cleanup = now
|
81
81
|
self.logger.debug(f"Cleaned up request cache. Removed {len(inactive_ips)} inactive IPs. Current tracked IPs: {len(self.requests)}")
|
82
82
|
|
83
|
-
def _extract_client_ip(self, request:Request) -> str:
|
84
|
-
"""Extract client IP with more robust handling of proxies"""
|
85
|
-
#* Check for X-Forwarded-For header (common when behind proxy/load balancer)
|
86
|
-
x_forwarded_for = request.headers.get("X-Forwarded-For")
|
87
|
-
if x_forwarded_for:
|
88
|
-
#* The client's IP is the first one in the list
|
89
|
-
ips = [ip.strip() for ip in x_forwarded_for.split(",")]
|
90
|
-
return ips[0]
|
91
|
-
|
92
|
-
#* Check for X-Real-IP header (used by some proxies)
|
93
|
-
x_real_ip = request.headers.get("X-Real-IP")
|
94
|
-
if x_real_ip:
|
95
|
-
return x_real_ip
|
96
|
-
|
97
|
-
#* Fall back to direct client connection
|
98
|
-
return request.client.host if request.client else "unknown"
|
99
|
-
|
100
83
|
def _check_rate_limit(self, client_ip:str) -> bool:
|
101
84
|
"""Check if the client has exceeded their rate limit"""
|
102
85
|
with self._lock:
|
@@ -179,7 +162,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
|
|
179
162
|
self._cleanup_old_data() #* Run periodic cleanup
|
180
163
|
request_timestamp = datetime.now(tz=timezone.utc) #* Record the request timestamp
|
181
164
|
start_time = time.perf_counter() #* Record the start time
|
182
|
-
client_ip = extract_client_ip(request) #* Get request IP with improved extraction
|
165
|
+
client_ip = BaseExtractors.extract_client_ip(request) #* Get request IP with improved extraction
|
183
166
|
|
184
167
|
try:
|
185
168
|
#* 1. Rate limit check
|
@@ -1,13 +1,15 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from .formatter import BaseFormatter
|
3
|
-
from .logger import BaseLogger
|
4
3
|
from .exceptions import BaseExceptions
|
4
|
+
from .extractor import BaseExtractors
|
5
|
+
from .keyloader import BaseKeyLoaders
|
5
6
|
from .controller import BaseControllerUtils
|
6
7
|
from .query import BaseQueryUtils
|
7
8
|
|
8
9
|
class BaseUtils:
|
9
10
|
Formatter = BaseFormatter
|
10
|
-
Logger = BaseLogger
|
11
11
|
Exceptions = BaseExceptions
|
12
|
+
Extractors = BaseExtractors
|
13
|
+
KeyLoader = BaseKeyLoaders
|
12
14
|
Controller = BaseControllerUtils
|
13
15
|
Query = BaseQueryUtils
|
@@ -1,18 +1,20 @@
|
|
1
1
|
from starlette.requests import HTTPConnection
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
class BaseExtractors:
|
4
|
+
@staticmethod
|
5
|
+
def extract_client_ip(conn:HTTPConnection) -> str:
|
6
|
+
"""Extract client IP with more robust handling of proxies"""
|
7
|
+
#* Check for X-Forwarded-For header (common when behind proxy/load balancer)
|
8
|
+
x_forwarded_for = conn.headers.get("X-Forwarded-For")
|
9
|
+
if x_forwarded_for:
|
10
|
+
#* The client's IP is the first one in the list
|
11
|
+
ips = [ip.strip() for ip in x_forwarded_for.split(",")]
|
12
|
+
return ips[0]
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
#* Check for X-Real-IP header (used by some proxies)
|
15
|
+
x_real_ip = conn.headers.get("X-Real-IP")
|
16
|
+
if x_real_ip:
|
17
|
+
return x_real_ip
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
#* Fall back to direct client connection
|
20
|
+
return conn.client.host if conn.client else "unknown"
|
@@ -3,63 +3,65 @@ from cryptography.hazmat.primitives import serialization
|
|
3
3
|
from typing import Optional, Union
|
4
4
|
from maleo_foundation.enums import BaseEnums
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
class BaseKeyLoaders:
|
7
|
+
@staticmethod
|
8
|
+
def load_rsa(
|
9
|
+
type:BaseEnums.KeyType,
|
10
|
+
path: Union[str, pathlib.Path],
|
11
|
+
password:Optional[Union[str, bytes]] = None,
|
12
|
+
format:BaseEnums.KeyFormatType = BaseEnums.KeyFormatType.STRING,
|
13
|
+
) -> Union[bytes, str]:
|
14
|
+
"""
|
15
|
+
Load an RSA private or public key strictly from a file.
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
Args:
|
18
|
+
path (str | pathlib.Path): Path to the PEM file.
|
19
|
+
password (str | bytes | None): Password for encrypted private keys (optional).
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
Returns:
|
22
|
+
rsa.RSAPrivateKey | rsa.RSAPublicKey
|
23
|
+
"""
|
24
|
+
if not isinstance(type, BaseEnums.KeyType):
|
25
|
+
raise TypeError("Invalid key type")
|
24
26
|
|
25
|
-
|
27
|
+
file_path = pathlib.Path(path)
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
+
if not file_path.is_file():
|
30
|
+
raise FileNotFoundError(f"Key file not found: {file_path}")
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
+
if password is not None and not isinstance(password, (str, bytes)):
|
33
|
+
raise TypeError("Invalid passsword type")
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
+
if not isinstance(format, BaseEnums.KeyFormatType):
|
36
|
+
raise TypeError("Invalid key format type")
|
35
37
|
|
36
|
-
|
38
|
+
key_data = file_path.read_bytes()
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
40
|
+
if type == BaseEnums.KeyType.PRIVATE:
|
41
|
+
private_key = serialization.load_pem_private_key(
|
42
|
+
key_data,
|
43
|
+
password=password.encode() if isinstance(password, str) else password,
|
44
|
+
)
|
45
|
+
private_key_bytes = private_key.private_bytes(
|
46
|
+
encoding=serialization.Encoding.PEM,
|
47
|
+
format=serialization.PrivateFormat.PKCS8,
|
48
|
+
encryption_algorithm=serialization.NoEncryption()
|
49
|
+
)
|
50
|
+
if format == BaseEnums.KeyFormatType.BYTES:
|
51
|
+
return private_key_bytes
|
52
|
+
elif format == BaseEnums.KeyFormatType.STRING:
|
53
|
+
return private_key_bytes.decode()
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
55
|
+
elif type == BaseEnums.KeyType.PUBLIC:
|
56
|
+
public_key = serialization.load_pem_public_key(key_data)
|
57
|
+
public_key_bytes = public_key.public_bytes(
|
58
|
+
encoding=serialization.Encoding.PEM,
|
59
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
60
|
+
)
|
61
|
+
if format == BaseEnums.KeyFormatType.BYTES:
|
62
|
+
return public_key_bytes
|
63
|
+
elif format == BaseEnums.KeyFormatType.STRING:
|
64
|
+
return public_key_bytes.decode()
|
63
65
|
|
64
|
-
|
65
|
-
|
66
|
+
else:
|
67
|
+
raise ValueError(f"Unsupported key type: {type}")
|
@@ -13,7 +13,7 @@ maleo_foundation/expanded_types/token.py,sha256=4fRTJw6W5MYq71NksNrWNi7qYHQ4_lQw
|
|
13
13
|
maleo_foundation/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
14
|
maleo_foundation/managers/db.py,sha256=ZN0b43OgqQtk2WHKMJQ0E2TeaSEyVJ0-l4FEkrSG0Qo,4645
|
15
15
|
maleo_foundation/managers/middleware.py,sha256=7CDXPMb28AR7J72TWOeKFxOlMypKezEtO9mr53a88B0,4032
|
16
|
-
maleo_foundation/managers/service.py,sha256
|
16
|
+
maleo_foundation/managers/service.py,sha256=-xsghwPtqV95YcktytV7Zba_E38JkmwUPlHXcLppfkQ,20413
|
17
17
|
maleo_foundation/managers/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
18
|
maleo_foundation/managers/client/base.py,sha256=lYREmEoTLShlPPXOKKiAopjefJ8nIWHCi7IvWkXXKeY,1465
|
19
19
|
maleo_foundation/managers/client/maleo.py,sha256=NibYGdiN3EXUw5nx-tL48QAZym14GcA4BDZihGJNQ-g,4254
|
@@ -22,8 +22,8 @@ maleo_foundation/managers/client/google/base.py,sha256=7jCnzkBN-F8xJEfP5eopuyfRB
|
|
22
22
|
maleo_foundation/managers/client/google/secret.py,sha256=E2SthMUHScIo9Cz5ICbCK7xBg16hnzKANk7--B82828,3481
|
23
23
|
maleo_foundation/managers/client/google/storage.py,sha256=CZmbKBS4p7OCYb5MKWmGB56-Dl7gi7xXteoqpJpVH08,2748
|
24
24
|
maleo_foundation/middlewares/__init__.py,sha256=bqE2EIFC3rWcR2AwFPR0fk2kSFfeTRzgA24GbnuT5RA,3697
|
25
|
-
maleo_foundation/middlewares/authentication.py,sha256=
|
26
|
-
maleo_foundation/middlewares/base.py,sha256=
|
25
|
+
maleo_foundation/middlewares/authentication.py,sha256=mpJ4WJ25zw4SGvgpeJE9eSV3-AtK5IJtN2U8Dh9rmMk,3132
|
26
|
+
maleo_foundation/middlewares/base.py,sha256=iGc_4JZYxE0k47Dty4aFOvpgXjQY_W_4tgqYndhq3V8,10991
|
27
27
|
maleo_foundation/middlewares/cors.py,sha256=9uvBvY2N6Vxa9RP_YtESxcWo6Doi6uS0lzAG9iLY7Uc,2288
|
28
28
|
maleo_foundation/models/__init__.py,sha256=AaKehO7c1HyKhoTGRmNHDddSeBXkW-_YNrpOGBu8Ms8,246
|
29
29
|
maleo_foundation/models/responses.py,sha256=Ka9Peb1fVuQKaxyy11FVkUPtGtzyEm_9Xfe8Vla3ml0,4764
|
@@ -54,16 +54,16 @@ maleo_foundation/models/transfers/results/service/controllers/__init__.py,sha256
|
|
54
54
|
maleo_foundation/models/transfers/results/service/controllers/rest.py,sha256=wCuFyOTQkuBs2cqjPsWnPy0XIsCfMqGByhrSy57qp7Y,1107
|
55
55
|
maleo_foundation/services/__init__.py,sha256=Ho5zJSA89xdGFKIwOdzjmd8sm23cIuwrqYAxCEBBTIU,120
|
56
56
|
maleo_foundation/services/token.py,sha256=ZqRqOdGUnaSIam6-JHVdAW1UST-2EDtcVN0fpbPmXY4,1638
|
57
|
-
maleo_foundation/utils/__init__.py,sha256=
|
57
|
+
maleo_foundation/utils/__init__.py,sha256=eEnPPeA6la0mu7535EFEKUB02C1IO_R8JgYcohvf1qU,471
|
58
58
|
maleo_foundation/utils/controller.py,sha256=ECzPzpw36zBAjKcWcDbUAhIJGbc6UpeypdUUX6ipXBg,6396
|
59
59
|
maleo_foundation/utils/exceptions.py,sha256=LPPcU-6_3NbRIBZg2Nr2Ac5HF1qZJbHbMVnwfIfZg6g,3702
|
60
|
-
maleo_foundation/utils/extractor.py,sha256=
|
61
|
-
maleo_foundation/utils/keyloader.py,sha256=
|
60
|
+
maleo_foundation/utils/extractor.py,sha256=SZXVYDHWGaA-Dd1BUydwF2HHdZqexEielS4CjL0Ceng,814
|
61
|
+
maleo_foundation/utils/keyloader.py,sha256=TOKgKLqvUV27MYkej9d_MQB9O8bOJ_WzaXlqHorkmpo,2581
|
62
62
|
maleo_foundation/utils/logging.py,sha256=MwvZmZSA8SIdfq-knEvpYIgqnSpHcyHrZY9TVHWVHJA,9023
|
63
63
|
maleo_foundation/utils/query.py,sha256=ODQ3adOYQNj5E2cRW9ytbjBz56nEDcnfq8mQ6YZbCCM,4375
|
64
64
|
maleo_foundation/utils/formatter/__init__.py,sha256=iKf5YCbEdg1qKnFHyKqqcQbqAqEeRUf8mhI3v3dQoj8,78
|
65
65
|
maleo_foundation/utils/formatter/case.py,sha256=TmvvlfzGdC_omMTB5vAa40TZBxQ3hnr-SYeo0M52Rlg,1352
|
66
|
-
maleo_foundation-0.1.
|
67
|
-
maleo_foundation-0.1.
|
68
|
-
maleo_foundation-0.1.
|
69
|
-
maleo_foundation-0.1.
|
66
|
+
maleo_foundation-0.1.37.dist-info/METADATA,sha256=G4M-2SvG4uuTt1hiIP7g0UnQmI4-SQUWbxb_OGm5tjc,3190
|
67
|
+
maleo_foundation-0.1.37.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
68
|
+
maleo_foundation-0.1.37.dist-info/top_level.txt,sha256=_iBos3F_bhEOdjOnzeiEYSrCucasc810xXtLBXI8cQc,17
|
69
|
+
maleo_foundation-0.1.37.dist-info/RECORD,,
|
File without changes
|
File without changes
|