Django-guardian: user.has_perm("perm", obj) ведет себя неожиданно

Созданный на 3 сент. 2011  ·  28Комментарии  ·  Источник: django-guardian/django-guardian

Если я использую стандартный метод user.has_perm("perm") , он вернет только True , если у пользователя есть глобальное разрешение "perm" .
И если используется user.has_perm("perm", obj) , он вернет True , если у пользователя есть разрешение на доступ к этому конкретному объекту.
Но он вернет False , даже если у пользователя есть глобальное разрешение "perm" , что довольно неожиданно для меня, потому что я предполагаю, что наличие глобального разрешения должно давать пользователю доступ ко всем объектам. Я прав?

Enhancement

Самый полезный комментарий

Здравствуйте, не могли бы вы вставить настройку _AUTHENTICATION_BACKENDS_?

Поскольку вы уже читали документацию Django, вы знаете, что порядок указанных бэкэндов важен.

Я полагаю, у вас есть что-то вроде следующего в вашем приложении:

AUTHENTICATION_BACKENDS = (
    'guardian.backends.ObjectPermissionBackend',
    'django.contrib.auth.backends.ModelBackend',
)

Или это:

from django.conf import global_settings
AUTHENTICATION_BACKENDS = (
    'guardian.backends.ObjectPermissionBackend',
) + global_settings.AUTHENTICATION_BACKENDS

Просто убедитесь, что сначала указан бэкэнд по умолчанию.

Вы можете подтвердить, что это проблема? Если нет, добавьте дополнительную информацию (может быть, вы также используете какие-то другие бэкенды или какие-то другие патчи для обезьян приложений, которые используют метод _User.has_perm_?).

Все 28 Комментарий

Я еще немного поковырялся и выяснил, что каждый бэкенд разрешений должен работать независимо, так что ситуации, описанной выше, быть не должно. Но по какой-то причине вызов user.has_perm , предоставляющий экземпляр объекта, проверяет только разрешения на уровне объекта и пропускает глобальную проверку разрешений. Я понятия не имею, в чем причина такого поведения. Я использую Джанго 1.2.5.

Здравствуйте, не могли бы вы вставить настройку _AUTHENTICATION_BACKENDS_?

Поскольку вы уже читали документацию Django, вы знаете, что порядок указанных бэкэндов важен.

Я полагаю, у вас есть что-то вроде следующего в вашем приложении:

AUTHENTICATION_BACKENDS = (
    'guardian.backends.ObjectPermissionBackend',
    'django.contrib.auth.backends.ModelBackend',
)

Или это:

from django.conf import global_settings
AUTHENTICATION_BACKENDS = (
    'guardian.backends.ObjectPermissionBackend',
) + global_settings.AUTHENTICATION_BACKENDS

Просто убедитесь, что сначала указан бэкэнд по умолчанию.

Вы можете подтвердить, что это проблема? Если нет, добавьте дополнительную информацию (может быть, вы также используете какие-то другие бэкенды или какие-то другие патчи для обезьян приложений, которые используют метод _User.has_perm_?).

Ладно, кажется, я слишком устал. Порядок не должен влиять на результат _has_perm_. Пожалуйста, добавьте больше информации об используемых вами настройках приложения. Кроме того, вы можете убедиться, что Guardian работает правильно, запустив набор тестов (_python manage.py test guardian_).

О, хорошо, я прочитал ваш вопрос еще раз. По умолчанию _auth.ModelBackend_ НЕ поддерживает _supports_object_permissions_ (этот атрибут имеет значение _False_). Согласно документам Django, они добавят эту поддержку бэкенда по умолчанию из версии 1.4.

Итак, для вашей ситуации поведение абсолютно правильное и ожидаемое. Бэкэнд по умолчанию просто опущен.

Вы должны проверить глобальные разрешения, прежде чем проверять разрешения на уровне объекта в своем приложении. Это самое простое решение, которое я могу придумать.

Закрытие как _invalid_, однако, если вы хотите снова открыть его с новым комментарием - не стесняйтесь делать это!

Хорошо, похоже, ты прав. Но отсутствие проверки глобальных разрешений создает для меня серьезные трудности. Например в guardian.decorators.permission_required , что, как я предполагал, должно расширять функциональность обычного django.contrib.auth.decorators.permission_required с дополнительной проверкой разрешений на уровне объекта.
Проблема в том, что у меня есть работающее приложение, которое использует глобальные разрешения, и я хотел добавить к нему некоторые дополнительные разрешения на уровне объекта, поэтому я изменил декораторы permission_required по умолчанию на уровень объекта из django-guardian, но затем пользователи потеряли свои права доступа на основе глобальных разрешений.

Для меня это выглядит неожиданным поведением, не могли бы вы хотя бы добавить этот случай в документацию, потому что это неочевидно без изучения кода.

Что касается меня, это недостаток дизайна, приводящий к дополнительной проверке всего кода, где мне нужно проверить разрешение как глобально, так и для каждого объекта.

@Коагулянт : было бы не очень гибко на самом деле _расширять_ декораторы из авторизации Джанго. Это приложение намерено реализовать _разрешения объекта_, а не расширять исходное. То есть, что, если вы хотите использовать какое-то другое приложение, которое использует только разрешения уровня опекуна и объекта? Что, если вы хотите использовать глобальные разрешения только для администратора и разрешения на уровне объекта в приложениях, предоставленные обычным пользователям? Что, если приложению потребуется как глобальное разрешение, так и разрешение на уровне объекта, чтобы пользователь мог выполнять какие-либо действия с объектом? Случаев много, опекун не охватил бы их все.

С другой стороны, я могу признать, что этот конкретный вариант использования может быть более распространенным, чем другие. Для всех, кто заинтересован в этом - пожалуйста, отправьте другой вопрос с указанием требований. Я считаю, что это может быть легко достигнуто без несовместимости обратных слов - т.е. с новой настройкой конфигурации.

Мне было бы полезно, если бы был добавлен еще один декоратор permission_required, который проверяет как разрешения уровня объекта, так и глобальные. Это покроет мою проблему.

@Dzejkob , не могли бы вы проверить последний коммит и сказать, будет ли этого достаточно (я добавил флаг для принятия глобальных разрешений). Кроме того, дайте мне знать, достаточно ли расширения строки документации в самом декораторе, или мне следует добавить больше примеров/более описательных документов?

Примечание: коммит находится в новой ветке: _feature/add-accept_global_perms-flag-for-decorators_

@lukaszb Да, я установил новую ветку Guardian в свой проект, добавил параметр в декораторы $ permission_required accept_global_perms=True , и, похоже, он работает как надо. Так что спасибо за создание этой функции. Я думаю, что это хорошая идея, чтобы слить его в багажник.
Насчет документов они мне вполне понятны, так что думаю хватит.

Это было исправлено давно.

Здравствуйте, я обнаружил эту проблему, когда пробовал jango-guardian, и обнаружил, что это поведение ошибочно/очень запутанно.

Я добавил глобальное разрешение (назовем его view_user ) пользователю (назовем его joe ), а затем проверил, может ли Джо просматривать определенного пользователя (назовем его other_user ) и неожиданно он вернул False .

joe = User.objects.get(username="joe")
other_user = User.objects.get(username="other_user")
assign_perm("myapp.view_user", joe)
joe.has_perm("myapp.view_user") # True as expected
joe.has_perm("myapp.view_user", other_user) # False, whaaaat?

Теперь я не использую декоратор permission_required явно, поскольку мои наборы представлений «автоматически» проверяют разрешения из-за моих REST_FRAMEWORK/DEFAULT_PERMISSION_CLASSES и AUTHENTICATION_BACKENDS в settings.py . Как мне сказать опекуну, чтобы он вел себя так, как ожидалось?

(Извините, если я пропустил что-то действительно глупое, это своего рода день 2/3 в djangoland)

Извините за шум сегодня, еще более интересно/запутанно то, что guardian.shortcuts.get_objects_for_user() по умолчанию соблюдает авторизационные/глобальные разрешения ( accept_global_perms ).

accept_global_perms: if True takes global permissions into account. 
[...]
Default is True.

Хотя этот вопрос был закрыт в пользу другого, здесь есть много инсайтов, поэтому я пишу сюда, чтобы возобновить разговор. Дзен Питона говорит:

Должен быть один — и желательно только один — очевидный способ сделать это.

И для меня это _откат к глобальному, когда локальный (уровень объекта) не указан_. Порядок не меняет результат (начиная с Django returns False if obj is not None ), но может изменить производительность.

Я согласен с тем, что двойные проверки по всему коду — это нехороший дизайн, и в некотором смысле это противоречит принципу DRY. Тем не менее, функция реализации Guardian, доступная в бэкэнде Django по умолчанию, также не является DRY. То, что Django допускает использование нескольких бэкэндов и проверяет их по порядку, указывает на то, что бэкенды должны работать вместе, а не заменять друг друга. Если это правильно, то Django отказывается проверять глобальные переменные, когда obj is not None _неправильно_. Если бы Django проигнорировал obj , это могло бы стать глобальным запасным вариантом для средств проверки объектов.

Я думаю, мы должны открыть тикет с Django, спросив, взаимодействуют ли серверы авторизации друг с другом, и если да, то как, а затем исходить из этого.

При этом я думаю, что изменение поведения Django маловероятно (хотя я все еще думаю, что мы должны спросить об этом), поэтому статус-кво указывает на то, что Guardian должен развиваться, чтобы быть в состоянии обеспечить и то, и другое в зависимости от того, какой из них желателен (какой). может быть установлен либо через настройки, либо через аргумент функции). Но поведение по умолчанию, я думаю, должно быть _local резервным к глобальному, когда false_ по всем направлениям.

Мне бы очень понравилось, если бы Django предпочел систему разрешений с тремя состояниями: True, False и None. В этом случае локальные чекеры могут _переопределить_ глобальные переменные через False ; и через None каждый бэкенд мог сказать: «Я этого не знаю, спросите следующего в очереди». В этом случае Django прекратит проверку после получения True or False в одном из бэкендов и перестанет тратить вычислительную мощность впустую.

Это дало бы каждому бэкенду больше возможностей: он мог бы дать окончательный ответ или сослаться на других.

@doganmeh Django реализует систему разрешений с тремя состояниями (по крайней мере, с версии 1.10). Возможные варианты: True, None и повышение PermissionDenied. Выполнение последнего приведет к тому, что Django прекратит проверку и вернет False.

Что касается рассматриваемой проблемы, я считаю, что это проблема Contrib.Auth. Это серверная часть, которая имеет дело с «глобальными разрешениями». Проблема в том, что они установили косвенное соглашение, согласно которому has_perm с obj=None проверяет только «разрешения таблицы», а has_perm с obj проверяет только «разрешения строк». Они вряд ли изменят это, но _должны_ быть открыты для расширения своего API, чтобы поддерживать проверку обоих.

(По крайней мере, я надеюсь, что они будут готовы добавить поведение для проверки обоих. Это кажется явным недостатком в их системе.)

Каковы ваши предложения по API «проверить оба»? Лучшее, о чем я могу думать, это kwarg.

Я говорил о явной системе с тремя состояниями, где в таблице разрешений будет NullBooleanField. Правда, если вы принимаете отсутствие True (или отсутствие разрешения) как None , это можно рассматривать как три состояния. В этом случае разрешения, не указанные опекуном, должны быть приняты как None , а решение должно быть делегировано глобалу. Если бы у меня был выбор, я бы выбрал явный дизайн.

@airstandley Я думаю, вы имеете в виду добавление kwarg к user.has_perm , например:

def has_perm(self, perm, obj=None, fallback=False)

Я думаю, это поможет. В случае, когда fallback=True , опекун вызовет бэкэнд Django и вернет файл global. Однако, если бы у меня был выбор, я бы предпочел, чтобы откат к Django естественным образом обрабатывался фреймворком, а не взламывал его внутренности.

@doganmeh Извините, я оговорился. Это должно было читаться «True, False и повышение PermissionDenied». Нет:

Возможные варианты: True, None и поднятие PermissionDenied.

Позже я думаю о возврате в своей голове...

Я думаю, что я, возможно, также неправильно понял, что вы имели в виду под

Я бы предпочел, чтобы Джанго предпочитал систему разрешений с тремя состояниями.

Чтобы уточнить, вы говорили о реализации модели разрешений конкретно, а не о системе в целом?

Моя точка зрения заключалась в том, что бэкэнд-система аутентификации допускает точное сокращение, которое вы описали (по крайней мере, для has_perm , has_module_perms API). Это явно, если не прямо:
Любой сервер может либо сам принять решение (вернуть True или PermissionDenied), либо делегировать решение следующему серверу в цепочке (вернуть False). Это решение зависит от реализации, а не от качества самой системы.

Явный ObjectPermissionBackend был бы проблемой для моего проекта, поэтому я бы предпочел не указывать его явно. («Явность» делает интеграцию с другими механизмами проверки разрешений громоздкой.) Я подозреваю, что, как и вы, другие предпочли бы, чтобы она была явной. Поэтому наличие «явности» в качестве настройки имеет для меня смысл.

@доганмех
Итак, о кварге.

Во-первых, я продолжаю беспокоиться, что мы не согласны с тем, как должно работать поведение серверной системы аутентификации. Итак, чтобы было ясно:
Насколько я понимаю, намерение состояло в том, чтобы серверные части работали вместе. Если приложение хочет использовать систему разрешений авторизации (то есть «глобальные разрешения», хотя технически это будут «разрешения для таблиц», но картошка, картошка), оно установит auth ModelBackend в AUTH_BACKENDS. Если бы это приложение хотело использовать систему ObjectPermission опекуна (т. е. «разрешение на строку»), оно не использовало бы ObjectPermissionBackend опекуна в AUTH_BACKENDS.
ModelBackend будет обрабатывать глобальное разрешение.
ObjectPermissionBackend будет обрабатывать разрешения объекта.
если бы у пользователя было только разрешение на объект, ModelBackend никогда бы не вернул True для has_perm . Если бы у пользователя было только глобальное разрешение, ObjectPermissionBackend никогда бы не вернул True для has_perm . В обоих случаях вызов user.has_perm(perm, obj) должен по-прежнему возвращать значение true, поскольку у пользователя есть разрешение для _one_ установленных бэкендов. (Это мы столкнулись с проблемой из-за сбоя ModelBackend в этой учетной записи)

Хорошо, учитывая все это.

В идеальном мире, где совместимость не является проблемой, я бы тоже предпочел простое изменение ModelBackend contrib.auth. ModelBackend.has_perm(user_obj, perm, obj=None) должен возвращать True, если у пользователя есть «глобальное разрешение», указанное в perm; независимо от того, является ли obj None или нет.

Однако совместимость является проблемой, поэтому я спрашиваю, есть ли у вас решение этой проблемы.

Единственные _обратно совместимые_ решения, которые я могу придумать, это либо добавить kwarg в API, либо добавить параметр AUTH.

Итак, варги:
Давайте назовем это obj_shortcut , потому что это лучшее, что я могу придумать. object_shortcut по умолчанию будет иметь значение True. Если object_shortcut равно True, то бэкенды должны вести себя так же, как сейчас делает ModelBackend: они должны _только_ проверять разрешения "таблица/глобальные", если obj имеет значение None, в противном случае они должны _только_ проверять разрешения "строка/объект". Однако, если object_shortcut имеет значение False, серверные части должны вести себя так, как мы оба предпочли бы: они будут проверять _и глобальные, и объектные разрешения, когда объект не равен None. Тогда надстройки, такие как Guardian, всегда могут предоставить миксин с методом has_perm с object_shortcut=False по умолчанию. user.has_perm(perm, obj, object_shortcut=False) будет правильно возвращать True, если Разрешение, представленное perm, было назначено Пользователю, в то время как устаревшие вызовы user.has_perm(perm, obj) будут по-прежнему возвращать False.

Настройка будет иметь тот же результат, но в основном приведет к переключению в ModelBackend._get_permissions .

if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
    return set()

станет чем-то вроде:

if not user_obj.is_active or user_obj.is_anonymous or (obj is not None and legacy_behaviour_setting_is _on):
    return set()

Не уверен, что лучше. Я думаю, что настройка чище, но я уверен, что разработчики Django будут более квалифицированы, чтобы определить это.

Дело в том, что проблему можно исправить, и я твердо верю, что проблема связана с ошибкой Django, а не с Guardian.

@airstandley Вы правы, False или None не имеет значения. Они оба означают , что я не знаю . В конце концов, если никто не знает, разрешение не дается.

Я, как правило, на одной странице с вами, хотя django, заставляющий бэкэнды вести себя так или иначе, похоже, ухудшает слабосвязанность. Я думаю, что у него должны быть механизмы, позволяющие каждому бэкэнду получать информацию, необходимую им для выполнения своей работы. Я с вами по аргументу(ам) ключевого слова, но не просто форсирую так или иначе.

Этот разговор кажется пустой тратой времени, но он помог мне уяснить себе, что отсутствие разрешения у опекуна — это не отказ, а просто отсутствие информации.

В любом случае, я сделал запрос на включение # 546. Пожалуйста, проверьте это и дайте мне знать, что вы думаете.

Я создал тикет с помощью django: https://code.djangoproject.com/ticket/29012 , и мне интересно, что они скажут.

Кажется, были и другие билеты с django: https://code.djangoproject.com/ticket/20218

Я закрыл свой.

@доганмех
Мне нравится направление, в котором вы идете с # 546. Если это поможет, у меня будет время просмотреть остальные модули и написать несколько тестов для этих запасных вариантов в эти выходные.

По касательной. Ваш комментарий о том, что «Django заставляет бэкэнды вести себя определенным образом», смущает меня, но также заставил меня задуматься. Бэкенды могут вести себя так, как им хочется; мое первоначальное беспокойство по поводу ObjectPermissionBackend от Guardian связано с документацией, предполагающей, что он был разработан для работы вместе с ModelBackend от Auth. Guardian может предложить несколько бэкендов, один из которых предназначен для работы с ModelBackend, а другой — для работы в одиночку. (IE, который просто проверяет таблицы UserObjectPermission/GroupObjectPermission Guardian, другой проверяет обе таблицы UserObjectPermission/GroupObjectPermission и таблицу разрешений Auth.)
Лично я предпочитаю текущий подход с настройкой и kwargs. Я думаю, что основным недостатком подхода с несколькими серверными частями будет то, что становится неясным, как должны вести себя ярлыки и удобные функции.

Видел сообщение в списке рассылки разработчиков Django. Я надеюсь, что они примут это или согласятся изменить поведение несовместимым образом. Текущее ограничение на API просто неуклюже.

Можете ли вы поддержать его там? 😊

Отправлено из почты для Windows 10

Откуда: Airstandley
Отправлено: пятница, 12 января 2018 г., 12:06.
Кому: джанго-хранитель/джанго-хранитель
Копия: Мехмет Доган; Упомянуть
Тема: Re: [django-guardian/django-guardian] user.has_perm("perm", obj) ведет себя неожиданно (#49)

Видел сообщение в списке рассылки разработчиков Django. Я надеюсь, что они примут это или согласятся изменить поведение несовместимым образом. Текущее ограничение на API просто неуклюже.

Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub или отключите ветку.

Поработав над этой проблемой около недели, я пришел к более твердому убеждению, что каждый бэкэнд должен делать только одну вещь, а именно права доступа к объектам для Guardian. Для этого Django должен обращаться с obj более аккуратно.

Для этого я отправил Django патч: https://github.com/django/django/pull/9581 (пожалуйста, прокомментируйте, если можете). Те, которые это сделают, мы можем очистить получение разрешений модели, где бы они ни находились в Guardian, и просто сделать вызов к серверной части по умолчанию.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги