nlbone 0.7.15__py3-none-any.whl → 0.7.17__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.
- nlbone/adapters/auth/auth_service.py +43 -9
- nlbone/adapters/auth/token_provider.py +1 -1
- nlbone/config/settings.py +3 -1
- nlbone/container.py +3 -1
- nlbone/interfaces/api/dependencies/client_credential.py +44 -0
- nlbone/interfaces/api/middleware/authentication.py +1 -1
- nlbone/utils/cache.py +8 -2
- nlbone/utils/cache_registry.py +0 -2
- {nlbone-0.7.15.dist-info → nlbone-0.7.17.dist-info}/METADATA +1 -1
- {nlbone-0.7.15.dist-info → nlbone-0.7.17.dist-info}/RECORD +13 -12
- {nlbone-0.7.15.dist-info → nlbone-0.7.17.dist-info}/WHEEL +0 -0
- {nlbone-0.7.15.dist-info → nlbone-0.7.17.dist-info}/entry_points.txt +0 -0
- {nlbone-0.7.15.dist-info → nlbone-0.7.17.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,23 +3,57 @@ import requests
|
|
|
3
3
|
from nlbone.config.settings import get_settings
|
|
4
4
|
from nlbone.core.ports.auth import AuthService as BaseAuthService
|
|
5
5
|
from nlbone.utils.http import normalize_https_base
|
|
6
|
+
from nlbone.utils.cache import cached
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class AuthService(BaseAuthService):
|
|
9
10
|
def __init__(self):
|
|
10
11
|
s = get_settings()
|
|
11
|
-
self.
|
|
12
|
+
self.client_id = s.KEYCLOAK_CLIENT_ID
|
|
13
|
+
self.client_secret = s.CLIENT_SECRET.get_secret_value().strip()
|
|
14
|
+
self._base_url = normalize_https_base(s.AUTH_SERVICE_URL.unicode_string(), enforce_https=False)
|
|
12
15
|
self._timeout = float(s.HTTP_TIMEOUT_SECONDS)
|
|
13
|
-
self._client =
|
|
16
|
+
self._client = requests.session()
|
|
14
17
|
|
|
15
|
-
def has_access(self, token: str, permissions: list[str]) -> bool:
|
|
16
|
-
|
|
18
|
+
def has_access(self, token: str, permissions: list[str]) -> bool:
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
@cached(ttl=15 * 60)
|
|
22
|
+
def verify_token(self, token: str) -> dict:
|
|
17
23
|
url = f"{self._base_url}/introspect"
|
|
18
24
|
result = self._client.post(url, data={
|
|
19
25
|
"token": token
|
|
20
26
|
})
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def
|
|
27
|
+
if result.status_code == 200:
|
|
28
|
+
return result.json()
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
def get_client_id(self, token: str):
|
|
32
|
+
data = self.verify_token(token)
|
|
33
|
+
if data:
|
|
34
|
+
return data["sub"] if data["sub"].startswith('service-account') else None
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
def get_client_token(self) -> dict | None:
|
|
38
|
+
url = f"{self._base_url}/introspect"
|
|
39
|
+
result = self._client.post(url, data={
|
|
40
|
+
"client_id": self.client_id,
|
|
41
|
+
"client_secret": self.client_secret,
|
|
42
|
+
"grant_type": "client_credentials"
|
|
43
|
+
})
|
|
44
|
+
if result.status_code == 200:
|
|
45
|
+
return result.json()
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
def is_client_token(self, token: str, allowed_clients: set[str] | None = None) -> bool:
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
def client_has_access(self, token: str, permissions: list[str], allowed_clients: set[str] | None = None) -> bool:
|
|
52
|
+
data = self.verify_token(token)
|
|
53
|
+
if not data:
|
|
54
|
+
return False
|
|
55
|
+
has_access = [perm in data.get("allowed_permissions", []) for perm in permissions]
|
|
56
|
+
return all(has_access)
|
|
57
|
+
|
|
58
|
+
def get_permissions(self, token: str) -> list[str]:
|
|
59
|
+
...
|
|
@@ -31,7 +31,7 @@ class ClientTokenProvider:
|
|
|
31
31
|
access_token = data.get("access_token")
|
|
32
32
|
if not access_token:
|
|
33
33
|
raise RuntimeError("Keycloak: missing access_token")
|
|
34
|
-
expires_in = int(data.get("expires_in", 60))
|
|
34
|
+
expires_in = int(data.get("expires_in", 15 * 60))
|
|
35
35
|
self._token = access_token
|
|
36
36
|
self._expires_at = time.time() + max(1, expires_in)
|
|
37
37
|
return self._token
|
nlbone/config/settings.py
CHANGED
|
@@ -3,7 +3,7 @@ from functools import lru_cache
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Literal
|
|
5
5
|
|
|
6
|
-
from pydantic import AnyHttpUrl, Field, SecretStr
|
|
6
|
+
from pydantic import AnyHttpUrl, Field, SecretStr, AliasChoices
|
|
7
7
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
8
8
|
|
|
9
9
|
|
|
@@ -63,6 +63,8 @@ class Settings(BaseSettings):
|
|
|
63
63
|
PRICING_API_SECRET: str = ''
|
|
64
64
|
|
|
65
65
|
AUTH_SERVICE_URL: AnyHttpUrl = Field(default="https://auth.numberland.ir")
|
|
66
|
+
CLIENT_ID: str = Field(default="", validation_alias=AliasChoices("KEYCLOAK_CLIENT_ID"))
|
|
67
|
+
CLIENT_SECRET: SecretStr = Field(default="", validation_alias=AliasChoices("KEYCLOAK_CLIENT_SECRET"))
|
|
66
68
|
|
|
67
69
|
# ---------------------------
|
|
68
70
|
# Database
|
nlbone/container.py
CHANGED
|
@@ -5,6 +5,8 @@ from typing import Dict, Optional
|
|
|
5
5
|
from dependency_injector import containers, providers
|
|
6
6
|
from pydantic_settings import BaseSettings
|
|
7
7
|
|
|
8
|
+
from nlbone.adapters.auth.auth_service import AuthService as AuthService_IMP
|
|
9
|
+
from nlbone.core.ports.auth import AuthService
|
|
8
10
|
from nlbone.adapters.auth.keycloak import KeycloakAuthService
|
|
9
11
|
from nlbone.adapters.auth.token_provider import ClientTokenProvider
|
|
10
12
|
from nlbone.adapters.cache.async_redis import AsyncRedisCache
|
|
@@ -28,7 +30,7 @@ class Container(containers.DeclarativeContainer):
|
|
|
28
30
|
# event_bus: providers.Singleton[EventBusPort] = providers.Singleton(InMemoryEventBus)
|
|
29
31
|
|
|
30
32
|
# --- Services ---
|
|
31
|
-
auth: providers.Singleton[
|
|
33
|
+
auth: providers.Singleton[AuthService] = providers.Singleton(AuthService_IMP)
|
|
32
34
|
token_provider = providers.Singleton(ClientTokenProvider, auth=auth, skew_seconds=30)
|
|
33
35
|
file_service: providers.Singleton[FileServicePort] = providers.Singleton(
|
|
34
36
|
UploadchiClient, token_provider=token_provider
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import functools
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
from makefun import wraps as mf_wraps
|
|
6
|
+
|
|
7
|
+
from nlbone.adapters.auth.auth_service import AuthService
|
|
8
|
+
from nlbone.interfaces.api.exceptions import UnauthorizedException, ForbiddenException
|
|
9
|
+
from nlbone.utils.context import current_request
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def current_client_id() -> str:
|
|
13
|
+
request = current_request()
|
|
14
|
+
if client_id := AuthService().get_client_id(request.state.token):
|
|
15
|
+
return str(client_id)
|
|
16
|
+
raise UnauthorizedException()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def client_has_access_func(*, permissions=None):
|
|
20
|
+
request = current_request()
|
|
21
|
+
if not AuthService().client_has_access(request.state.token, permissions=permissions):
|
|
22
|
+
raise ForbiddenException(f"Forbidden {permissions}")
|
|
23
|
+
return True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def client_has_access(*, permissions=None):
|
|
27
|
+
def deco(func: Callable):
|
|
28
|
+
is_async_func = asyncio.iscoroutinefunction(func)
|
|
29
|
+
|
|
30
|
+
if is_async_func:
|
|
31
|
+
@mf_wraps(func)
|
|
32
|
+
async def aw(*args, **kwargs):
|
|
33
|
+
client_has_access_func(permissions=permissions)
|
|
34
|
+
return await func(*args, **kwargs)
|
|
35
|
+
|
|
36
|
+
return aw
|
|
37
|
+
|
|
38
|
+
@mf_wraps(func)
|
|
39
|
+
def sw(*args, **kwargs):
|
|
40
|
+
client_has_access_func(permissions=permissions)
|
|
41
|
+
return func(*args, **kwargs)
|
|
42
|
+
|
|
43
|
+
return sw
|
|
44
|
+
return deco
|
|
@@ -75,7 +75,7 @@ class AuthenticationMiddleware(BaseHTTPMiddleware):
|
|
|
75
75
|
request.state.client_id = None
|
|
76
76
|
request.state.user_id = None
|
|
77
77
|
request.state.token = None
|
|
78
|
-
if request.headers.get('X-Client-Id') and request.headers.get("X-
|
|
78
|
+
if request.headers.get('X-Client-Id') and request.headers.get("X-Api-Key"):
|
|
79
79
|
if request.headers.get("X-Api_Key") == get_settings().PRICING_API_SECRET:
|
|
80
80
|
request.state.client_id = request.headers.get("X-Client-Id")
|
|
81
81
|
return await call_next(request)
|
nlbone/utils/cache.py
CHANGED
|
@@ -5,8 +5,7 @@ from typing import Any, Callable, Iterable, Optional, Mapping
|
|
|
5
5
|
import hashlib
|
|
6
6
|
from makefun import wraps as mf_wraps
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
from nlbone.interfaces.api.pagination import PaginateRequest
|
|
8
|
+
|
|
10
9
|
from nlbone.utils.cache_registry import get_cache
|
|
11
10
|
|
|
12
11
|
try:
|
|
@@ -31,6 +30,9 @@ PRIMITIVES = (str, int, float, bool, type(None))
|
|
|
31
30
|
|
|
32
31
|
|
|
33
32
|
def to_jsonable(obj):
|
|
33
|
+
from nlbone.interfaces.api.additional_filed import AdditionalFieldsRequest
|
|
34
|
+
from nlbone.interfaces.api.pagination import PaginateRequest
|
|
35
|
+
|
|
34
36
|
if isinstance(obj, PRIMITIVES):
|
|
35
37
|
return obj
|
|
36
38
|
|
|
@@ -147,6 +149,8 @@ def cached(
|
|
|
147
149
|
@mf_wraps(func)
|
|
148
150
|
async def aw(*args, **kwargs):
|
|
149
151
|
cache = (cache_resolver or get_cache)()
|
|
152
|
+
if not cache:
|
|
153
|
+
return await func(*args, **kwargs)
|
|
150
154
|
k = _key_from_template(key, func, args, kwargs)
|
|
151
155
|
tg = _format_tags(tags, func, args, kwargs)
|
|
152
156
|
|
|
@@ -177,6 +181,8 @@ def cached(
|
|
|
177
181
|
@mf_wraps(func)
|
|
178
182
|
def sw(*args, **kwargs):
|
|
179
183
|
cache = (cache_resolver or get_cache)()
|
|
184
|
+
if not cache:
|
|
185
|
+
return func(*args, **kwargs)
|
|
180
186
|
k = _key_from_template(key, func, args, kwargs)
|
|
181
187
|
tg = _format_tags(tags, func, args, kwargs)
|
|
182
188
|
|
nlbone/utils/cache_registry.py
CHANGED
|
@@ -21,6 +21,4 @@ def set_context_cache_resolver(fn: Optional[Callable[[], T]]) -> None:
|
|
|
21
21
|
|
|
22
22
|
def get_cache() -> T:
|
|
23
23
|
fn = _ctx_resolver.get() or _global_resolver
|
|
24
|
-
if fn is None:
|
|
25
|
-
raise RuntimeError("Cache resolver not configured. Call set_cache_resolver(...) first.")
|
|
26
24
|
return fn()
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
nlbone/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
nlbone/container.py,sha256=
|
|
2
|
+
nlbone/container.py,sha256=2UQ1YDA6Y24u6FlOPiMRlZSBcj1hcCPrjqyLc7_0IQ0,2810
|
|
3
3
|
nlbone/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
nlbone/adapters/__init__.py,sha256=NzUmk4XPyp3GJOw7VSE86xkQMZLtG3MrOoXLeoB551M,41
|
|
5
5
|
nlbone/adapters/snowflake.py,sha256=lmq7vi6HdX9hEuTW6BlTEAy91wmrA4Bx3tvGoagHTW4,2315
|
|
6
6
|
nlbone/adapters/auth/__init__.py,sha256=hkDHvsFhw_UiOHG9ZSMqjiAhK4wumEforitveSZswVw,42
|
|
7
|
-
nlbone/adapters/auth/auth_service.py,sha256=
|
|
7
|
+
nlbone/adapters/auth/auth_service.py,sha256=kwHyp2trsXeToyE5pb2CBLfyFLjwR_E8CsGgrOlrPT8,2090
|
|
8
8
|
nlbone/adapters/auth/keycloak.py,sha256=IhEriaFl5mjIGT6ZUCU9qROd678ARchvWgd4UJ6zH7s,4925
|
|
9
|
-
nlbone/adapters/auth/token_provider.py,sha256=
|
|
9
|
+
nlbone/adapters/auth/token_provider.py,sha256=EcZ7nSXxPZJZGaWnyo3QDvrEbGdeXXWnhHnP1-kMniY,1438
|
|
10
10
|
nlbone/adapters/cache/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
nlbone/adapters/cache/async_redis.py,sha256=vvu5w4ANx0BVRHL95RAMGsD8CcaC-tSBMbCius2cuNc,6212
|
|
12
12
|
nlbone/adapters/cache/memory.py,sha256=y8M4erHQXApiSMAqG6Qk4pxEb60hRdu1szPv6iqvO9c,3738
|
|
@@ -43,7 +43,7 @@ nlbone/adapters/ticketing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
43
43
|
nlbone/adapters/ticketing/client.py,sha256=GAr0_4DVqVCDV7tT2gkxRyKIsvLwJRlT-xx8fsfOVJE,1303
|
|
44
44
|
nlbone/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
45
|
nlbone/config/logging.py,sha256=Ot6Ctf7EQZlW8YNB-uBdleqI6wixn5fH0Eo6QRgNkQk,4358
|
|
46
|
-
nlbone/config/settings.py,sha256=
|
|
46
|
+
nlbone/config/settings.py,sha256=iP7ZtZUGNFwj5A8qGuqa0k6RSM8Z_sIpWI07zi3gIRM,4752
|
|
47
47
|
nlbone/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
48
|
nlbone/core/application/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
49
|
nlbone/core/application/base_worker.py,sha256=5brIToSd-vi6tw0ukhHnUZGZhOLq1SQ-NRRy-kp6D24,1193
|
|
@@ -78,12 +78,13 @@ nlbone/interfaces/api/additional_filed/default_field_rules/image_field_rules.py,
|
|
|
78
78
|
nlbone/interfaces/api/dependencies/__init__.py,sha256=rnYRrFVZCfICQrp_PVFlzNg3BeC57yM08wn2DbOHCfk,359
|
|
79
79
|
nlbone/interfaces/api/dependencies/async_auth.py,sha256=FBa73JUE2D82k2P1k4daN4IdvZbrbCwREqPJKfYc-4Q,1710
|
|
80
80
|
nlbone/interfaces/api/dependencies/auth.py,sha256=dKq3TIJN4CqKZcRIlMV3rz3v8sFgh9Rz_slIP2VNcvs,3083
|
|
81
|
+
nlbone/interfaces/api/dependencies/client_credential.py,sha256=kRmkWgqRoybXm-Mreiu-c0ve1oz7PlPzcsRTdyxjG94,1308
|
|
81
82
|
nlbone/interfaces/api/dependencies/db.py,sha256=-UD39J_86UU7ZJs2ZncpdND0yhAG0NeeeALrgSDuuFw,466
|
|
82
83
|
nlbone/interfaces/api/dependencies/uow.py,sha256=QfLEvLYLNWZJQN1k-0q0hBVtUld3D75P4j39q_RjcnE,1181
|
|
83
84
|
nlbone/interfaces/api/middleware/__init__.py,sha256=zbX2vaEAfxRMIYwO2MVY_2O6bqG5H9o7HqGpX14U3Is,158
|
|
84
85
|
nlbone/interfaces/api/middleware/access_log.py,sha256=vIkxxxfy2HcjqqKb8XCfGCcSrivAC8u6ie75FMq5x-U,1032
|
|
85
86
|
nlbone/interfaces/api/middleware/add_request_context.py,sha256=av-qs0biOYuF9R6RJOo2eYsFqDL9WRYWcjVakFhbt-w,1834
|
|
86
|
-
nlbone/interfaces/api/middleware/authentication.py,sha256=
|
|
87
|
+
nlbone/interfaces/api/middleware/authentication.py,sha256=sW1j4L7n73pGpIQVAzH8FYnaVUR-NfQj-9yU8wk7eWQ,3112
|
|
87
88
|
nlbone/interfaces/api/pagination/__init__.py,sha256=pA1uC4rK6eqDI5IkLVxmgO2B6lExnOm8Pje2-hifJZw,431
|
|
88
89
|
nlbone/interfaces/api/pagination/offset_base.py,sha256=AwuHLQELAKut58fQSL2hk-QhfwsG1coJWz-Jkh2gnmg,4113
|
|
89
90
|
nlbone/interfaces/api/schema/__init__.py,sha256=LAqgynfupeqOQ6u0I5ucrcYnojRMZUg9yW8IjKSQTNI,119
|
|
@@ -98,17 +99,17 @@ nlbone/interfaces/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
|
98
99
|
nlbone/interfaces/jobs/dispatch_outbox.py,sha256=yLZSC3nvkgxT2LL4Pq_DYzCyf_tZB-FknrjjgN89GFg,809
|
|
99
100
|
nlbone/interfaces/jobs/sync_tokens.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
100
101
|
nlbone/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
101
|
-
nlbone/utils/cache.py,sha256=
|
|
102
|
+
nlbone/utils/cache.py,sha256=BJoWbDicwTS1CF9m8sBKIEgfG-OszoQEEsMqrCHwNL0,7204
|
|
102
103
|
nlbone/utils/cache_keys.py,sha256=Y2YSellHTbUOcoaNbl1jaD4r485VU_e4KXsfBWhYTBo,1075
|
|
103
|
-
nlbone/utils/cache_registry.py,sha256=
|
|
104
|
+
nlbone/utils/cache_registry.py,sha256=3FWYyhujW8oPBiVUPzk1CqJ3jJfxs9729Sbb1pQ5Fag,707
|
|
104
105
|
nlbone/utils/context.py,sha256=MmclJ24BG2uvSTg1IK7J-Da9BhVFDQ5ag4Ggs2FF1_w,1600
|
|
105
106
|
nlbone/utils/crypto.py,sha256=PX0Tlf2nqXcGbuv16J26MoUPzo2c4xcD4sZBXxhBXgQ,746
|
|
106
107
|
nlbone/utils/http.py,sha256=MPDEyaC16AKsL0YH6sWCPp8NC2TgzEHpWERYK5HcaYQ,1001
|
|
107
108
|
nlbone/utils/normalize_mobile.py,sha256=sGH4tV9gX-6eVKozviNWJhm1DN1J28Nj-ERldCYkS_E,732
|
|
108
109
|
nlbone/utils/redactor.py,sha256=-V4HrHmHwPi3Kez587Ek1uJlgK35qGSrwBOvcbw8Jas,1279
|
|
109
110
|
nlbone/utils/time.py,sha256=DjjyQ9GLsfXoT6NK8RDW2rOlJg3e6sF04Jw6PBUrSvg,1268
|
|
110
|
-
nlbone-0.7.
|
|
111
|
-
nlbone-0.7.
|
|
112
|
-
nlbone-0.7.
|
|
113
|
-
nlbone-0.7.
|
|
114
|
-
nlbone-0.7.
|
|
111
|
+
nlbone-0.7.17.dist-info/METADATA,sha256=gBOXqAKZe48Oon5SVO3rdYM0EkyohsF-WIQwd8r3c7E,2295
|
|
112
|
+
nlbone-0.7.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
113
|
+
nlbone-0.7.17.dist-info/entry_points.txt,sha256=CpIL45t5nbhl1dGQPhfIIDfqqak3teK0SxPGBBr7YCk,59
|
|
114
|
+
nlbone-0.7.17.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
115
|
+
nlbone-0.7.17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|