strawberry-graphql 0.229.2__py3-none-any.whl → 0.229.2.dev1715873118__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.
- strawberry/permission.py +156 -24
- strawberry/utils/typing.py +1 -1
- {strawberry_graphql-0.229.2.dist-info → strawberry_graphql-0.229.2.dev1715873118.dist-info}/METADATA +1 -1
- {strawberry_graphql-0.229.2.dist-info → strawberry_graphql-0.229.2.dev1715873118.dist-info}/RECORD +7 -7
- {strawberry_graphql-0.229.2.dist-info → strawberry_graphql-0.229.2.dev1715873118.dist-info}/LICENSE +0 -0
- {strawberry_graphql-0.229.2.dist-info → strawberry_graphql-0.229.2.dev1715873118.dist-info}/WHEEL +0 -0
- {strawberry_graphql-0.229.2.dist-info → strawberry_graphql-0.229.2.dev1715873118.dist-info}/entry_points.txt +0 -0
strawberry/permission.py
CHANGED
@@ -8,12 +8,14 @@ from typing import (
|
|
8
8
|
TYPE_CHECKING,
|
9
9
|
Any,
|
10
10
|
Awaitable,
|
11
|
-
Dict,
|
12
11
|
List,
|
12
|
+
Literal,
|
13
13
|
Optional,
|
14
|
+
Tuple,
|
14
15
|
Type,
|
15
16
|
Union,
|
16
17
|
)
|
18
|
+
from typing_extensions import deprecated
|
17
19
|
|
18
20
|
from strawberry.exceptions import StrawberryGraphQLError
|
19
21
|
from strawberry.exceptions.permission_fail_silently_requires_optional import (
|
@@ -51,17 +53,40 @@ class BasePermission(abc.ABC):
|
|
51
53
|
@abc.abstractmethod
|
52
54
|
def has_permission(
|
53
55
|
self, source: Any, info: Info, **kwargs: Any
|
54
|
-
) -> Union[
|
56
|
+
) -> Union[
|
57
|
+
bool,
|
58
|
+
Awaitable[bool],
|
59
|
+
Tuple[Literal[False], dict],
|
60
|
+
Awaitable[Tuple[Literal[False], dict]],
|
61
|
+
]:
|
62
|
+
"""
|
63
|
+
This method is a required override in the permission class. It checks if the user has the necessary permissions to access a specific field.
|
64
|
+
|
65
|
+
The method should return a boolean value:
|
66
|
+
- True: The user has the necessary permissions.
|
67
|
+
- False: The user does not have the necessary permissions. In this case, the `on_unauthorized` method will be invoked.
|
68
|
+
|
69
|
+
Avoid raising exceptions in this method. Instead, use the `on_unauthorized` method to handle errors and customize the error response.
|
70
|
+
|
71
|
+
If there's a need to pass additional information to the `on_unauthorized` method, return a tuple. The first element should be False, and the second element should be a dictionary containing the additional information.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
source (Any): The source field that the permission check is being performed on.
|
75
|
+
info (Info): The GraphQL resolve info associated with the field.
|
76
|
+
**kwargs (Any): Additional arguments that are typically passed to the field resolver.
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
bool or tuple: Returns True if the user has the necessary permissions. Returns False or a tuple (False, additional_info) if the user does not have the necessary permissions. In the latter case, the `on_unauthorized` method will be invoked.
|
80
|
+
"""
|
55
81
|
raise NotImplementedError(
|
56
82
|
"Permission classes should override has_permission method"
|
57
83
|
)
|
58
84
|
|
59
|
-
def on_unauthorized(self) -> None:
|
85
|
+
def on_unauthorized(self, **kwargs: Any) -> None:
|
60
86
|
"""
|
61
87
|
Default error raising for permissions.
|
62
88
|
This can be overridden to customize the behavior.
|
63
89
|
"""
|
64
|
-
|
65
90
|
# Instantiate error class
|
66
91
|
error = self.error_class(self.message or "")
|
67
92
|
|
@@ -74,6 +99,9 @@ class BasePermission(abc.ABC):
|
|
74
99
|
raise error
|
75
100
|
|
76
101
|
@property
|
102
|
+
@deprecated(
|
103
|
+
"@schema_directive is deprecated and will be disabled by default on 31.12.2024 with future removal planned. Use the new @permissions directive instead."
|
104
|
+
)
|
77
105
|
def schema_directive(self) -> object:
|
78
106
|
if not self._schema_directive:
|
79
107
|
|
@@ -89,6 +117,93 @@ class BasePermission(abc.ABC):
|
|
89
117
|
|
90
118
|
return self._schema_directive
|
91
119
|
|
120
|
+
@cached_property
|
121
|
+
def is_async(self) -> bool:
|
122
|
+
return iscoroutinefunction(self.has_permission)
|
123
|
+
|
124
|
+
def __and__(self, other: BasePermission):
|
125
|
+
return AndPermission([self, other])
|
126
|
+
|
127
|
+
def __or__(self, other: BasePermission):
|
128
|
+
return OrPermission([self, other])
|
129
|
+
|
130
|
+
|
131
|
+
class CompositePermission(BasePermission, abc.ABC):
|
132
|
+
def __init__(self, child_permissions: List[BasePermission]):
|
133
|
+
self.child_permissions = child_permissions
|
134
|
+
|
135
|
+
def on_unauthorized(self, **kwargs: Any) -> Any:
|
136
|
+
failed_permissions = kwargs.get("failed_permissions", [])
|
137
|
+
for permission in failed_permissions:
|
138
|
+
permission.on_unauthorized()
|
139
|
+
|
140
|
+
@cached_property
|
141
|
+
def is_async(self) -> bool:
|
142
|
+
return any(x.is_async for x in self.child_permissions)
|
143
|
+
|
144
|
+
|
145
|
+
class AndPermission(CompositePermission):
|
146
|
+
def has_permission(
|
147
|
+
self, source: Any, info: Info, **kwargs: Any
|
148
|
+
) -> Union[
|
149
|
+
bool,
|
150
|
+
Awaitable[bool],
|
151
|
+
Tuple[Literal[False], dict],
|
152
|
+
Awaitable[Tuple[Literal[False], dict]],
|
153
|
+
]:
|
154
|
+
if self.is_async:
|
155
|
+
return self._has_permission_async(source, info, **kwargs)
|
156
|
+
|
157
|
+
for permission in self.child_permissions:
|
158
|
+
if not permission.has_permission(source, info, **kwargs):
|
159
|
+
return False, {"failed_permissions": [permission]}
|
160
|
+
return True
|
161
|
+
|
162
|
+
async def _has_permission_async(
|
163
|
+
self, source: Any, info: Info, **kwargs: Any
|
164
|
+
) -> Union[bool, Tuple[Literal[False], dict]]:
|
165
|
+
for permission in self.child_permissions:
|
166
|
+
if not await await_maybe(permission.has_permission(source, info, **kwargs)):
|
167
|
+
return False, {"failed_permissions": [permission]}
|
168
|
+
return True
|
169
|
+
|
170
|
+
def __and__(self, other: BasePermission):
|
171
|
+
return AndPermission([*self.child_permissions, other])
|
172
|
+
|
173
|
+
|
174
|
+
class OrPermission(CompositePermission):
|
175
|
+
def has_permission(
|
176
|
+
self, source: Any, info: Info, **kwargs: Any
|
177
|
+
) -> Union[
|
178
|
+
bool,
|
179
|
+
Awaitable[bool],
|
180
|
+
Tuple[Literal[False], dict],
|
181
|
+
Awaitable[Tuple[Literal[False], dict]],
|
182
|
+
]:
|
183
|
+
if self.is_async:
|
184
|
+
return self._has_permission_async(source, info, **kwargs)
|
185
|
+
failed_permissions = []
|
186
|
+
for permission in self.child_permissions:
|
187
|
+
if permission.has_permission(source, info, **kwargs):
|
188
|
+
return True
|
189
|
+
failed_permissions.append(permission)
|
190
|
+
|
191
|
+
return False, {"failed_permissions": failed_permissions}
|
192
|
+
|
193
|
+
async def _has_permission_async(
|
194
|
+
self, source: Any, info: Info, **kwargs: Any
|
195
|
+
) -> Union[bool, Tuple[Literal[False], dict]]:
|
196
|
+
failed_permissions = []
|
197
|
+
for permission in self.child_permissions:
|
198
|
+
if await await_maybe(permission.has_permission(source, info, **kwargs)):
|
199
|
+
return True
|
200
|
+
failed_permissions.append(permission)
|
201
|
+
|
202
|
+
return False, {"failed_permissions": failed_permissions}
|
203
|
+
|
204
|
+
def __or__(self, other: BasePermission):
|
205
|
+
return OrPermission([*self.child_permissions, other])
|
206
|
+
|
92
207
|
|
93
208
|
class PermissionExtension(FieldExtension):
|
94
209
|
"""
|
@@ -100,8 +215,8 @@ class PermissionExtension(FieldExtension):
|
|
100
215
|
|
101
216
|
NOTE:
|
102
217
|
Currently, this is automatically added to the field, when using
|
103
|
-
field.permission_classes
|
104
|
-
|
218
|
+
field.permission_classes. You are free to use whichever method you prefer.
|
219
|
+
Use PermissionExtension if you want additional customization.
|
105
220
|
"""
|
106
221
|
|
107
222
|
def __init__(
|
@@ -117,12 +232,16 @@ class PermissionExtension(FieldExtension):
|
|
117
232
|
|
118
233
|
def apply(self, field: StrawberryField) -> None:
|
119
234
|
"""
|
120
|
-
Applies all
|
235
|
+
Applies all the permission directives to the schema
|
121
236
|
and sets up silent permissions
|
122
237
|
"""
|
123
238
|
if self.use_directives:
|
124
239
|
field.directives.extend(
|
125
|
-
|
240
|
+
[
|
241
|
+
p.schema_directive
|
242
|
+
for p in self.permissions
|
243
|
+
if not isinstance(p, CompositePermission)
|
244
|
+
]
|
126
245
|
)
|
127
246
|
# We can only fail silently if the field is optional or a list
|
128
247
|
if self.fail_silently:
|
@@ -132,28 +251,40 @@ class PermissionExtension(FieldExtension):
|
|
132
251
|
elif isinstance(field.type, StrawberryList):
|
133
252
|
self.return_empty_list = True
|
134
253
|
else:
|
135
|
-
|
136
|
-
raise errror
|
254
|
+
raise PermissionFailSilentlyRequiresOptionalError(field)
|
137
255
|
|
138
|
-
def _on_unauthorized(self, permission: BasePermission) -> Any:
|
256
|
+
def _on_unauthorized(self, permission: BasePermission, **kwargs: Any) -> Any:
|
139
257
|
if self.fail_silently:
|
140
258
|
return [] if self.return_empty_list else None
|
141
|
-
|
259
|
+
|
260
|
+
if kwargs in (None, {}):
|
261
|
+
return permission.on_unauthorized()
|
262
|
+
return permission.on_unauthorized(**kwargs)
|
142
263
|
|
143
264
|
def resolve(
|
144
265
|
self,
|
145
266
|
next_: SyncExtensionResolver,
|
146
267
|
source: Any,
|
147
268
|
info: Info,
|
148
|
-
**kwargs:
|
269
|
+
**kwargs: Any[str, Any],
|
149
270
|
) -> Any:
|
150
271
|
"""
|
151
272
|
Checks if the permission should be accepted and
|
152
273
|
raises an exception if not
|
153
274
|
"""
|
275
|
+
|
154
276
|
for permission in self.permissions:
|
155
|
-
|
156
|
-
|
277
|
+
permission_response = permission.has_permission(source, info, **kwargs)
|
278
|
+
|
279
|
+
context = {}
|
280
|
+
if isinstance(permission_response, tuple):
|
281
|
+
has_permission, context = permission_response
|
282
|
+
else:
|
283
|
+
has_permission = permission_response
|
284
|
+
|
285
|
+
if not has_permission:
|
286
|
+
return self._on_unauthorized(permission, **context)
|
287
|
+
|
157
288
|
return next_(source, info, **kwargs)
|
158
289
|
|
159
290
|
async def resolve_async(
|
@@ -161,15 +292,21 @@ class PermissionExtension(FieldExtension):
|
|
161
292
|
next_: AsyncExtensionResolver,
|
162
293
|
source: Any,
|
163
294
|
info: Info,
|
164
|
-
**kwargs:
|
295
|
+
**kwargs: Any[str, Any],
|
165
296
|
) -> Any:
|
166
297
|
for permission in self.permissions:
|
167
|
-
|
298
|
+
permission_response = await await_maybe(
|
168
299
|
permission.has_permission(source, info, **kwargs)
|
169
300
|
)
|
170
301
|
|
302
|
+
context = {}
|
303
|
+
if isinstance(permission_response, tuple):
|
304
|
+
has_permission, context = permission_response
|
305
|
+
else:
|
306
|
+
has_permission = permission_response
|
307
|
+
|
171
308
|
if not has_permission:
|
172
|
-
return self._on_unauthorized(permission)
|
309
|
+
return self._on_unauthorized(permission, **context)
|
173
310
|
next = next_(source, info, **kwargs)
|
174
311
|
if inspect.isasyncgen(next):
|
175
312
|
return next
|
@@ -179,9 +316,4 @@ class PermissionExtension(FieldExtension):
|
|
179
316
|
def supports_sync(self) -> bool:
|
180
317
|
"""The Permission extension always supports async checking using await_maybe,
|
181
318
|
but only supports sync checking if there are no async permissions"""
|
182
|
-
|
183
|
-
True
|
184
|
-
for permission in self.permissions
|
185
|
-
if iscoroutinefunction(permission.has_permission)
|
186
|
-
]
|
187
|
-
return len(async_permissions) == 0
|
319
|
+
return all(not permission.is_async for permission in self.permissions)
|
strawberry/utils/typing.py
CHANGED
@@ -302,7 +302,7 @@ def _get_namespace_from_ast(
|
|
302
302
|
# here to resolve lazy types by execing the annotated args, resolving the
|
303
303
|
# type directly and then adding it to extra namespace, so that _eval_type
|
304
304
|
# can properly resolve it later
|
305
|
-
type_name = args[0].strip(
|
305
|
+
type_name = args[0].strip()
|
306
306
|
for arg in args[1:]:
|
307
307
|
evaled_arg = eval(arg, globalns, localns) # noqa: PGH001, S307
|
308
308
|
if isinstance(evaled_arg, StrawberryLazyReference):
|
{strawberry_graphql-0.229.2.dist-info → strawberry_graphql-0.229.2.dev1715873118.dist-info}/RECORD
RENAMED
@@ -162,7 +162,7 @@ strawberry/litestar/handlers/graphql_ws_handler.py,sha256=vVpjd5rJOldF8aQWEGjmmN
|
|
162
162
|
strawberry/mutation.py,sha256=NROPvHJU1BBxZB7Wj4Okxw4hDIYM59MCpukAGEmSNYA,255
|
163
163
|
strawberry/object_type.py,sha256=iQL2NqO7I28bS0moWgxPmCDc0w1HewPcXq8Xyh-xAWI,12539
|
164
164
|
strawberry/parent.py,sha256=rmedKjN4zdg4KTnNV8DENrzgNYVL67rXpHjHoBofMS4,825
|
165
|
-
strawberry/permission.py,sha256=
|
165
|
+
strawberry/permission.py,sha256=blMwepQb20KLgnpNXWMcMZKa2soRo3zKjKXizPfIrIw,11238
|
166
166
|
strawberry/printer/__init__.py,sha256=DmepjmgtkdF5RxK_7yC6qUyRWn56U-9qeZMbkztYB9w,62
|
167
167
|
strawberry/printer/ast_from_value.py,sha256=MFIX2V51d9ocRvD0Njemjk8YIzKh2BB1g2iUcX6a3d8,4946
|
168
168
|
strawberry/printer/printer.py,sha256=HUUecFETXWgfQtMzxjx9pNOXP3tDevqlU3sG7TC3AD8,17449
|
@@ -240,9 +240,9 @@ strawberry/utils/inspect.py,sha256=6z-tJpiWWm6E4-O6OUfhu689W9k1uY0m3FDwAfVCiNs,2
|
|
240
240
|
strawberry/utils/logging.py,sha256=flS7hV0JiIOEdXcrIjda4WyIWix86cpHHFNJL8gl1y4,713
|
241
241
|
strawberry/utils/operation.py,sha256=Um-tBCPl3_bVFN2Ph7o1mnrxfxBes4HFCj6T0x4kZxE,1135
|
242
242
|
strawberry/utils/str_converters.py,sha256=avIgPVLg98vZH9mA2lhzVdyyjqzLsK2NdBw9mJQ02Xk,813
|
243
|
-
strawberry/utils/typing.py,sha256=
|
244
|
-
strawberry_graphql-0.229.2.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
|
245
|
-
strawberry_graphql-0.229.2.dist-info/METADATA,sha256=
|
246
|
-
strawberry_graphql-0.229.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
247
|
-
strawberry_graphql-0.229.2.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
|
248
|
-
strawberry_graphql-0.229.2.dist-info/RECORD,,
|
243
|
+
strawberry/utils/typing.py,sha256=SQVOw1nuFZk2Pe3iz0o8ebzpoyvBVoGSQZVZj6-8k7I,13483
|
244
|
+
strawberry_graphql-0.229.2.dev1715873118.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
|
245
|
+
strawberry_graphql-0.229.2.dev1715873118.dist-info/METADATA,sha256=MSkHwXiY0s2dglAReE2EXuSKi9WQgZ9c6MI75-KeglE,7835
|
246
|
+
strawberry_graphql-0.229.2.dev1715873118.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
247
|
+
strawberry_graphql-0.229.2.dev1715873118.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
|
248
|
+
strawberry_graphql-0.229.2.dev1715873118.dist-info/RECORD,,
|
{strawberry_graphql-0.229.2.dist-info → strawberry_graphql-0.229.2.dev1715873118.dist-info}/LICENSE
RENAMED
File without changes
|
{strawberry_graphql-0.229.2.dist-info → strawberry_graphql-0.229.2.dev1715873118.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|