-
-
Notifications
You must be signed in to change notification settings - Fork 541
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Prevent a possible security issue when resolving a relay node wi…
…th multiple possibilities (#3749)
- Loading branch information
1 parent
fc854f1
commit 526eb82
Showing
8 changed files
with
203 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
Release type: minor | ||
|
||
The common `node: Node` used to resolve relay nodes means we will be relying on | ||
is_type_of to check if the returned object is in fact a subclass of the Node | ||
interface. | ||
|
||
However, integrations such as Django, SQLAlchemy and Pydantic will not return | ||
the type itself, but instead an alike object that is later resolved to the | ||
expected type. | ||
|
||
In case there are more than one possible type defined for that model that is | ||
being returned, the first one that replies True to `is_type_of` check would be | ||
used in the resolution, meaning that when asking for `"PublicUser:123"`, | ||
strawberry could end up returning `"User:123"`, which can lead to security | ||
issues (such as data leakage). | ||
|
||
In here we are introducing a new `strawberry.cast`, which will be used to mark | ||
an object with the already known type by us, and when asking for is_type_of that | ||
mark will be used to check instead, ensuring we will return the correct type. | ||
|
||
That `cast` is already in place for the relay node resolution and pydantic. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Any, TypeVar, overload | ||
|
||
_T = TypeVar("_T", bound=object) | ||
|
||
TYPE_CAST_ATTRIBUTE = "__as_strawberry_type__" | ||
|
||
|
||
@overload | ||
def cast(type_: type, obj: None) -> None: ... | ||
|
||
|
||
@overload | ||
def cast(type_: type, obj: _T) -> _T: ... | ||
|
||
|
||
def cast(type_: type, obj: _T | None) -> _T | None: | ||
"""Cast an object to given type. | ||
This is used to mark an object as a cast object, so that the type can be | ||
picked up when resolving unions/interfaces in case of ambiguity, which can | ||
happen when returning an alike object instead of an instance of the type | ||
(e.g. returning a Django, Pydantic or SQLAlchemy object) | ||
""" | ||
if obj is None: | ||
return None | ||
|
||
setattr(obj, TYPE_CAST_ATTRIBUTE, type_) | ||
return obj | ||
|
||
|
||
def get_strawberry_type_cast(obj: Any) -> type | None: | ||
"""Get the type of a cast object.""" | ||
return getattr(obj, TYPE_CAST_ATTRIBUTE, None) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import strawberry | ||
from strawberry.types.cast import get_strawberry_type_cast | ||
|
||
|
||
def test_cast(): | ||
@strawberry.type | ||
class SomeType: ... | ||
|
||
class OtherType: ... | ||
|
||
obj = OtherType | ||
assert get_strawberry_type_cast(obj) is None | ||
|
||
cast_obj = strawberry.cast(SomeType, obj) | ||
assert cast_obj is obj | ||
assert get_strawberry_type_cast(cast_obj) is SomeType | ||
|
||
|
||
def test_cast_none_obj(): | ||
@strawberry.type | ||
class SomeType: ... | ||
|
||
obj = None | ||
assert get_strawberry_type_cast(obj) is None | ||
|
||
cast_obj = strawberry.cast(SomeType, obj) | ||
assert cast_obj is None | ||
assert get_strawberry_type_cast(obj) is None |