nlbone 0.6.10__tar.gz → 0.6.12__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 (91) hide show
  1. {nlbone-0.6.10 → nlbone-0.6.12}/PKG-INFO +2 -1
  2. {nlbone-0.6.10 → nlbone-0.6.12}/pyproject.toml +3 -2
  3. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/auth/keycloak.py +72 -2
  4. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/db/postgres/query_builder.py +55 -10
  5. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/http_clients/uploadchi/uploadchi.py +14 -13
  6. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/percolation/connection.py +1 -0
  7. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/ports/auth.py +1 -0
  8. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/dependencies/auth.py +13 -5
  9. {nlbone-0.6.10 → nlbone-0.6.12}/.gitignore +0 -0
  10. {nlbone-0.6.10 → nlbone-0.6.12}/LICENSE +0 -0
  11. {nlbone-0.6.10 → nlbone-0.6.12}/README.md +0 -0
  12. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/__init__.py +0 -0
  13. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/__init__.py +0 -0
  14. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/auth/__init__.py +0 -0
  15. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/auth/token_provider.py +0 -0
  16. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/cache/__init__.py +0 -0
  17. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/cache/async_redis.py +0 -0
  18. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/cache/memory.py +0 -0
  19. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/cache/pubsub_listener.py +0 -0
  20. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/cache/redis.py +0 -0
  21. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/db/__init__.py +0 -0
  22. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/db/postgres/__init__.py +0 -0
  23. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/db/postgres/audit.py +0 -0
  24. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/db/postgres/base.py +0 -0
  25. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/db/postgres/engine.py +0 -0
  26. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/db/postgres/repository.py +0 -0
  27. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/db/postgres/schema.py +0 -0
  28. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/db/postgres/uow.py +0 -0
  29. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/db/redis/__init__.py +0 -0
  30. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/db/redis/client.py +0 -0
  31. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/http_clients/__init__.py +0 -0
  32. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/http_clients/pricing/__init__.py +0 -0
  33. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/http_clients/pricing/pricing_service.py +0 -0
  34. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/http_clients/uploadchi/__init__.py +0 -0
  35. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/http_clients/uploadchi/uploadchi_async.py +0 -0
  36. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/messaging/__init__.py +0 -0
  37. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/messaging/event_bus.py +0 -0
  38. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/messaging/redis.py +0 -0
  39. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/adapters/percolation/__init__.py +0 -0
  40. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/config/__init__.py +0 -0
  41. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/config/logging.py +0 -0
  42. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/config/settings.py +0 -0
  43. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/container.py +0 -0
  44. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/__init__.py +0 -0
  45. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/application/__init__.py +0 -0
  46. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/application/base_worker.py +0 -0
  47. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/application/events.py +0 -0
  48. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/application/services/__init__.py +0 -0
  49. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/application/services.py +0 -0
  50. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/application/use_case.py +0 -0
  51. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/domain/__init__.py +0 -0
  52. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/domain/base.py +0 -0
  53. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/domain/events.py +0 -0
  54. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/domain/models.py +0 -0
  55. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/ports/__init__.py +0 -0
  56. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/ports/cache.py +0 -0
  57. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/ports/event_bus.py +0 -0
  58. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/ports/files.py +0 -0
  59. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/ports/messaging.py +0 -0
  60. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/ports/repo.py +0 -0
  61. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/core/ports/uow.py +0 -0
  62. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/__init__.py +0 -0
  63. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/__init__.py +0 -0
  64. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/dependencies/__init__.py +0 -0
  65. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/dependencies/async_auth.py +0 -0
  66. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/dependencies/db.py +0 -0
  67. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/dependencies/uow.py +0 -0
  68. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/exception_handlers.py +0 -0
  69. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/exceptions.py +0 -0
  70. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/middleware/__init__.py +0 -0
  71. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/middleware/access_log.py +0 -0
  72. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/middleware/add_request_context.py +0 -0
  73. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/middleware/authentication.py +0 -0
  74. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/pagination/__init__.py +0 -0
  75. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/pagination/offset_base.py +0 -0
  76. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/routers.py +0 -0
  77. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/api/schemas.py +0 -0
  78. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/cli/__init__.py +0 -0
  79. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/cli/init_db.py +0 -0
  80. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/cli/main.py +0 -0
  81. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/jobs/__init__.py +0 -0
  82. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/interfaces/jobs/sync_tokens.py +0 -0
  83. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/types.py +0 -0
  84. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/utils/__init__.py +0 -0
  85. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/utils/cache.py +0 -0
  86. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/utils/cache_keys.py +0 -0
  87. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/utils/cache_registry.py +0 -0
  88. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/utils/context.py +0 -0
  89. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/utils/http.py +0 -0
  90. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/utils/redactor.py +0 -0
  91. {nlbone-0.6.10 → nlbone-0.6.12}/src/nlbone/utils/time.py +0 -0
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nlbone
3
- Version: 0.6.10
3
+ Version: 0.6.12
4
4
  Summary: Backbone package for interfaces and infrastructure in Python projects
5
5
  Author-email: Amir Hosein Kahkbazzadeh <a.khakbazzadeh@gmail.com>
6
6
  License: MIT
7
7
  License-File: LICENSE
8
8
  Requires-Python: >=3.10
9
9
  Requires-Dist: anyio>=4.0
10
+ Requires-Dist: cachetools>=6.2.0
10
11
  Requires-Dist: dependency-injector>=4.48.1
11
12
  Requires-Dist: elasticsearch==8.14.0
12
13
  Requires-Dist: fastapi>=0.116
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nlbone"
7
- version = "0.6.10"
7
+ version = "0.6.12"
8
8
  description = "Backbone package for interfaces and infrastructure in Python projects"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -27,7 +27,8 @@ dependencies = [
27
27
  "redis~=6.4.0",
28
28
  "python-dateutil~=2.9.0.post0",
29
29
  "typer>=0.17.4",
30
- "makefun>=1.16.0"
30
+ "makefun>=1.16.0",
31
+ "cachetools>=6.2.0",
31
32
  ]
32
33
 
33
34
  [tool.ruff]
@@ -1,9 +1,31 @@
1
+ import functools
2
+ from datetime import datetime, timezone
3
+ from threading import RLock
4
+
5
+ from cachetools import LRUCache
1
6
  from keycloak import KeycloakOpenID
2
7
  from keycloak.exceptions import KeycloakAuthenticationError
3
8
 
4
- from nlbone.config.settings import Settings, get_settings, is_production_env
9
+ from nlbone.config.settings import Settings, get_settings
5
10
  from nlbone.core.ports.auth import AuthService
6
11
 
12
+ _permissions_cache: LRUCache = LRUCache(maxsize=2048)
13
+ _permissions_lock = RLock()
14
+
15
+
16
+ def _now_ts() -> int:
17
+ return int(datetime.now(timezone.utc).timestamp())
18
+
19
+
20
+ def _ttl_from_decoded(decoded: dict) -> int:
21
+ exp = int(decoded.get("exp", 0))
22
+ ttl = max(1, exp - _now_ts())
23
+ return ttl
24
+
25
+
26
+ def _cache_key(sub: str | None, exp: int | None) -> tuple[str | None, int | None]:
27
+ return sub, exp
28
+
7
29
 
8
30
  class KeycloakAuthService(AuthService):
9
31
  def __init__(self, settings: Settings | None = None):
@@ -14,7 +36,7 @@ class KeycloakAuthService(AuthService):
14
36
  realm_name=s.KEYCLOAK_REALM_NAME,
15
37
  client_secret_key=s.KEYCLOAK_CLIENT_SECRET.get_secret_value().strip(),
16
38
  )
17
- self.bypass = not is_production_env()
39
+ self.bypass = s.ENV != 'prod'
18
40
 
19
41
  def has_access(self, token, permissions):
20
42
  if self.bypass:
@@ -29,6 +51,49 @@ class KeycloakAuthService(AuthService):
29
51
  print(f"Token verification failed: {e}")
30
52
  return False
31
53
 
54
+ def _fetch_permissions_from_keycloak(self, token: str) -> tuple[list[str], dict]:
55
+ permissions = self.keycloak_openid.uma_permissions(token)
56
+ decoded_token = self.keycloak_openid.decode_token(token)
57
+ result: list[str] = []
58
+ for p in permissions or []:
59
+ rsname = p.get("rsname")
60
+ for s in p.get("scopes", []) or []:
61
+ result.append(f"{rsname}#{s}")
62
+ return result, decoded_token
63
+
64
+ def get_permissions(self, token: str) -> list[str]:
65
+ try:
66
+ decoded = self.keycloak_openid.decode_token(token)
67
+ sub = decoded.get("sub")
68
+ exp = decoded.get("exp")
69
+ key = _cache_key(sub, exp)
70
+
71
+ now = _now_ts()
72
+ with _permissions_lock:
73
+ entry = _permissions_cache.get(key)
74
+ if entry is not None:
75
+ perms, exp_ts = entry
76
+ if exp_ts > now:
77
+ return perms
78
+ _permissions_cache.pop(key, None)
79
+
80
+ perms, decoded2 = self._fetch_permissions_from_keycloak(token)
81
+ decoded_final = decoded2 or decoded
82
+ sub_f = decoded_final.get("sub")
83
+ exp_f = int(decoded_final.get("exp") or 0)
84
+ key_f = _cache_key(sub_f, exp_f)
85
+
86
+ with _permissions_lock:
87
+ _permissions_cache[key_f] = (perms, exp_f)
88
+
89
+ return perms
90
+
91
+ except KeycloakAuthenticationError:
92
+ return []
93
+ except Exception as e:
94
+ print(f"Getting permissions failed: {e}")
95
+ return []
96
+
32
97
  def verify_token(self, token: str) -> dict | None:
33
98
  try:
34
99
  result = self.keycloak_openid.introspect(token)
@@ -76,3 +141,8 @@ class KeycloakAuthService(AuthService):
76
141
  if not self.is_client_token(token, allowed_clients):
77
142
  return False
78
143
  return self.has_access(token, permissions)
144
+
145
+
146
+ @functools.lru_cache(maxsize=1)
147
+ def get_auth_service() -> KeycloakAuthService:
148
+ return KeycloakAuthService()
@@ -28,6 +28,50 @@ class _InvalidEnum(Exception):
28
28
  pass
29
29
 
30
30
 
31
+ from sqlalchemy.orm import aliased
32
+ from sqlalchemy.inspection import inspect
33
+ from sqlalchemy.orm.attributes import InstrumentedAttribute
34
+ from sqlalchemy.orm.relationships import RelationshipProperty
35
+
36
+
37
+ def _resolve_column_and_joins(entity, query, field_path: str, join_cache: dict[str, Any]):
38
+ parts = [p for p in field_path.split(".") if p]
39
+ if not parts:
40
+ return None, query
41
+
42
+ current_cls_or_alias = entity
43
+ current_path_key_parts: list[str] = []
44
+
45
+ for i, part in enumerate(parts):
46
+ current_path_key_parts.append(part)
47
+ path_key = ".".join(current_path_key_parts)
48
+
49
+ if not hasattr(current_cls_or_alias, part):
50
+ return None, query
51
+
52
+ attr = getattr(current_cls_or_alias, part)
53
+
54
+ prop = getattr(attr, "property", None)
55
+ if isinstance(prop, RelationshipProperty):
56
+ alias = join_cache.get(path_key)
57
+ if alias is None:
58
+ alias = aliased(prop.mapper.class_)
59
+ query = query.outerjoin(alias, attr)
60
+ join_cache[path_key] = alias
61
+ current_cls_or_alias = alias
62
+ continue
63
+
64
+ if isinstance(attr, InstrumentedAttribute):
65
+ if i == len(parts) - 1:
66
+ return attr, query
67
+ else:
68
+ return None, query
69
+
70
+ return None, query
71
+
72
+ return None, query
73
+
74
+
31
75
  def _apply_order(pagination: PaginateRequest, entity, query):
32
76
  order_clauses = []
33
77
 
@@ -112,6 +156,7 @@ def _apply_filters(pagination, entity, query):
112
156
  return query
113
157
 
114
158
  predicates = []
159
+ join_cache: dict[str, Any] = {}
115
160
 
116
161
  if getattr(pagination, "filters", None):
117
162
  for raw_field, value in pagination.filters.items():
@@ -120,10 +165,10 @@ def _apply_filters(pagination, entity, query):
120
165
 
121
166
  field, op_hint = _parse_field_and_op(raw_field)
122
167
 
123
- if not hasattr(entity, field):
168
+ col, query2 = _resolve_column_and_joins(entity, query, field, join_cache)
169
+ if col is None:
124
170
  continue
125
-
126
- col = getattr(entity, field)
171
+ query = query2
127
172
  coltype = getattr(col, "type", None)
128
173
 
129
174
  def coerce(v):
@@ -259,13 +304,13 @@ def _serialize_item(item: Any, output_cls: OutputType) -> Any:
259
304
 
260
305
 
261
306
  def get_paginated_response(
262
- pagination,
263
- entity,
264
- session: Session,
265
- *,
266
- with_count: bool = True,
267
- output_cls: Optional[Type] = None,
268
- eager_options: Optional[Sequence[LoaderOption]] = None,
307
+ pagination,
308
+ entity,
309
+ session: Session,
310
+ *,
311
+ with_count: bool = True,
312
+ output_cls: Optional[Type] = None,
313
+ eager_options: Optional[Sequence[LoaderOption]] = None,
269
314
  ) -> dict:
270
315
  query = session.query(entity)
271
316
  if eager_options:
@@ -35,11 +35,11 @@ def _filename_from_cd(cd: str | None, fallback: str) -> str:
35
35
 
36
36
  class UploadchiClient(FileServicePort):
37
37
  def __init__(
38
- self,
39
- token_provider: ClientTokenProvider | None = None,
40
- base_url: Optional[str] = None,
41
- timeout_seconds: Optional[float] = None,
42
- client: httpx.Client | None = None,
38
+ self,
39
+ token_provider: ClientTokenProvider | None = None,
40
+ base_url: Optional[str] = None,
41
+ timeout_seconds: Optional[float] = None,
42
+ client: httpx.Client | None = None,
43
43
  ) -> None:
44
44
  s = get_settings()
45
45
  self._base_url = normalize_https_base(base_url or str(s.UPLOADCHI_BASE_URL))
@@ -51,7 +51,7 @@ class UploadchiClient(FileServicePort):
51
51
  self._client.close()
52
52
 
53
53
  def upload_file(
54
- self, file_bytes: bytes, filename: str, params: dict[str, Any] | None = None, token: str | None = None
54
+ self, file_bytes: bytes, filename: str, params: dict[str, Any] | None = None, token: str | None = None
55
55
  ) -> dict:
56
56
  tok = _resolve_token(token)
57
57
  files = {"file": (filename, file_bytes)}
@@ -84,12 +84,12 @@ class UploadchiClient(FileServicePort):
84
84
  raise UploadchiError(r.status_code, r.text)
85
85
 
86
86
  def list_files(
87
- self,
88
- limit: int = 10,
89
- offset: int = 0,
90
- filters: dict[str, Any] | None = None,
91
- sort: list[tuple[str, str]] | None = None,
92
- token: str | None = None,
87
+ self,
88
+ limit: int = 10,
89
+ offset: int = 0,
90
+ filters: dict[str, Any] | None = None,
91
+ sort: list[tuple[str, str]] | None = None,
92
+ token: str | None = None,
93
93
  ) -> dict:
94
94
  tok = _resolve_token(token)
95
95
  q = build_list_query(limit, offset, filters, sort)
@@ -116,6 +116,7 @@ class UploadchiClient(FileServicePort):
116
116
 
117
117
  def delete_file(self, file_id: str, token: str | None = None) -> None:
118
118
  tok = _resolve_token(token)
119
- r = self._client.delete(f"{self._base_url}/{file_id}", headers=auth_headers(tok))
119
+ r = self._client.delete(f"{self._base_url}/{file_id}",
120
+ headers=auth_headers(tok or self._token_provider.get_access_token()))
120
121
  if r.status_code not in (204, 200):
121
122
  raise UploadchiError(r.status_code, r.text)
@@ -9,5 +9,6 @@ def get_es_client():
9
9
  es = Elasticsearch(
10
10
  setting.ELASTIC_PERCOLATE_URL,
11
11
  basic_auth=(setting.ELASTIC_PERCOLATE_USER, setting.ELASTIC_PERCOLATE_PASS.get_secret_value().strip()),
12
+ http_compress=True, max_retries=2, retry_on_timeout=True
12
13
  )
13
14
  return es
@@ -8,3 +8,4 @@ class AuthService(Protocol):
8
8
  def get_client_token(self) -> dict | None: ...
9
9
  def is_client_token(self, token: str, allowed_clients: set[str] | None = None) -> bool: ...
10
10
  def client_has_access(self, token: str, perms: list[str], allowed_clients: set[str] | None = None) -> bool: ...
11
+ def get_permissions(self, token: str) -> list[str]: ...
@@ -1,6 +1,7 @@
1
1
  import functools
2
2
 
3
3
  from nlbone.adapters.auth import KeycloakAuthService
4
+ from nlbone.adapters.auth.keycloak import get_auth_service
4
5
  from nlbone.interfaces.api.exceptions import ForbiddenException, UnauthorizedException
5
6
  from nlbone.utils.context import current_request
6
7
 
@@ -51,8 +52,10 @@ def has_access(*, permissions=None):
51
52
  request = current_request()
52
53
  if not current_user_id():
53
54
  raise UnauthorizedException()
54
- if not KeycloakAuthService().has_access(request.state.token, permissions=permissions):
55
- raise ForbiddenException(f"Forbidden {permissions}")
55
+ user_permissions = get_auth_service().get_permissions(request.state.token)
56
+ for p in permissions or []:
57
+ if p not in user_permissions:
58
+ raise ForbiddenException(f"Forbidden {permissions}")
56
59
 
57
60
  return func(*args, **kwargs)
58
61
 
@@ -79,9 +82,14 @@ def client_or_user_has_access(*, permissions=None, client_permissions=None):
79
82
  else:
80
83
  if not current_user_id():
81
84
  raise UnauthorizedException()
82
- if not auth.has_access(token, permissions=permissions):
83
- raise ForbiddenException(f"Forbidden (user) {permissions}")
85
+
86
+ user_permissions = get_auth_service().get_permissions(request.state.token)
87
+ for p in permissions or []:
88
+ if p not in user_permissions:
89
+ raise ForbiddenException(f"Forbidden {permissions}")
84
90
 
85
91
  return func(*args, **kwargs)
92
+
86
93
  return wrapper
87
- return decorator
94
+
95
+ return decorator
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes