Проверка безопасности в графеновом резольвере
Я использую Django и Graphene для обслуживания конечной точки graphql и столкнулся с небольшой проблемой, которую никак не могу решить.
У меня есть следующий резольвер:
class Query(ObjectType):
trainingSession = Field(TrainingSessionType, id=graphene.ID())
trainingSessions = DjangoFilterConnectionField(TrainingSessionType)
@staticmethod
def checked_trainingsession(trainingsession,info):
# returns the trainingsession if a certain logic is fulfilled
# else None
def resolve_trainingSessions(root, info,**kwargs):
ids= kwargs.get('discipline__id')
all = TrainingSession.objects.all()
result = []
for trainingSession in all:
trainingSession = Query.checked_trainingsession(trainingSession,info)
if trainingSession != None:
result.append(trainingSession)
return result
вместе с типами объектов и фильтрами:
class TrainingSessionFilter(FilterSet):
discipline__id = GlobalIDMultipleChoiceFilter()
class Meta:
model = TrainingSession
fields = ["discipline__id"]
class TrainingSessionType(DjangoObjectType):
class Meta:
model=TrainingSession
fields="__all__"
filterset_class = TrainingSessionFilter
interfaces = (CustomNode,)
class CustomNode(graphene.Node):
"""
For fetching object id instead of Node id
"""
class Meta:
name = 'Node'
@staticmethod
def to_global_id(type, id):
return id
однако когда я пытаюсь выполнить запрос
query Sessions{
trainingSessions(discipline_Id:[2,3]){
edges{
node{
dateTime,
discipline{
id
}
}
}
}
}
Я получаю ошибку:
Traceback (most recent call last):
File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\promise\promise.py", line 489, in _resolve_from_executor
executor(resolve, reject)
File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\promise\promise.py", line 756, in executor
return resolve(f(*args, **kwargs))
File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\graphql\execution\middleware.py", line 75, in make_it_promise
return next(*args, **kwargs)
File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\graphene_django\fields.py", line 176, in connection_resolver
iterable = queryset_resolver(connection, iterable, info, args)
File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\graphene_django\filter\fields.py", line 62, in resolve_queryset
return filterset_class(data=filter_kwargs, queryset=qs, request=info.context).qs
File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\django_filters\filterset.py", line 193, in __init__
model = queryset.model
graphql.error.located_error.GraphQLLocatedError: 'list' object has no attribute 'model'
Я знаю, что должен возвращать набор запросов из resolve_trainingSessions. Однако я не знаю, как затем применить проверку разрешений к отдельным результатам. Логика не очень сложная, но я не могу обернуть ее в стандартный фильтр модели Django или объект Q.
Спасибо за любую помощь или подсказки.
Ok Мне удалось решить свою проблему, следуя этой идее: https://docs.graphene-python.org/projects/django/en/latest/authorization/#user-based-queryset-filtering
if user.is_anonymous:
return TrainingSession.objects.none()
if user.is_superuser:
return TrainingSession.objects.filter(filter)
....
Не супер элегантный, но он делает свою работу и не слишком плох.
Итак, как вы уже догадались, причина, по которой запросы с типами DjangoFilterConnectionField должны возвращать queryset, а не list, заключается в том, чтобы правильно работала пагинация, которая поставляется из коробки. К сожалению, для пользователей, которым важна только фильтрация, но не пагинация, отказаться от возврата queryset не представляется возможным. Поэтому у вас есть три варианта. (PS Я внес некоторые незначительные изменения в ваши фрагменты кода, (например, использование @classmethod)
- Вы не используете
DjangoFilterConnectionField
class Query(ObjectType):
trainingSession = Field(TrainingSessionType, id=graphene.ID())
trainingSessions = graphene.List(TrainingSessionType, discipline__id=grapene.List(graphene.ID)
@staticmethod
def checked_trainingsession(trainingsession,info):
# returns the trainingsession if a certain logic is fulfilled
# else None
@classmethod
def resolve_trainingSessions(cls, info, discipline__id):
return [ts for ts in TrainingSession.objects.filter(id__in=discipline_id) if cls.checked_trainingsession(ts, info)]
Преимущество этого метода в том, что в случае, если в вашем кверисете есть элементы, которые вам разрешено видеть, то вы можете вернуть их, не возвращая объект кверисета (вы можете выполнять фильтрацию без базы данных).
Вы очень стараетесь написать свой checked_trainigsession как фильтр queryset - это может показаться невозможным, но после достаточного количества пота вы сможете это сделать... я был там.
Как вы сделали здесь, вы жертвуете возможностью возвращать частичные данные, если пользователю разрешено видеть только некоторые элементы, и просто выдаете ошибку, когда этого не происходит. Способ, которым вы возвращаете
queryset.objects.none(), хорош, но вы можете так же легко выдать ошибку (что может быть элегантнее?)
from graphql_jwt.decorators import superuser_required
class Query(ObjectType):
trainingSession = Field(TrainingSessionType, id=graphene.ID())
trainingSessions = DjangoFilterConnectionField(TrainingSessionType)
@superuser_required
def resolve_trainingSessions(root, info,**filters):
return TrainingSessionFilter(filters).qs
Вы также можете поменять superuser_required на login_required или staff_member_required. Удачи!