Система прав доступа¶
Примечание
Этот материал находится в состоянии наполнения информацией.
Первое разворачивание¶
При первом создании базы данных создаётся суперпользователь admin@virtual.ko с паролем 12345678.
Программное разрешение прав¶
В качестве основы используется система прав Django. Поэтому для основных моделей в метаклассе должны быть определены параметры default_permissions (см. default_permissions) или permissions (см. permissions).
На текущий момент default_permissions определяют доступность прямого редактирования через админку Django, а права через API определяются через permissions.
Соглашение о наименовании прав в permissions¶
Для единообразия стиля примим следующее правило для формирования именования прав.
Именование права должно быть на языке эсперанто и начинаться с фразы povas (может - рус.). Далее следует фраза описания права, где вместо пробелов между словами используем символ _.
Например:
povas_vidi_kategorion - может видеть категорию; povas_krei_kategorion - может создать категорию; povas_forigi_kategorion - может удалить категорию; povas_shanghi_kategorion - может изменить категорию.
Отсюда можно выделить 4 подтипа прав по умолчанию для действий видеть, создать, удалить, изменить соответственно: povas_vidi_*, povas_krei_*, povas_forigi_*, povas_shanghi_*. Настоятельно рекомендуем для типовых дейсвий использовать именование прав по вышеуказанным шаблонам.
Ниже пример кода задания списка прав для модели konferencoj.KonferencojKategorio:
# дополнительные настройки для модели
class Meta:
# название таблицы в базе данных для этой модели
db_table = 'konferencoj_kategorioj'
# читабельное название модели, в единственном числе
verbose_name = _('Kategorio')
# читабельное название модели, во множественном числе
verbose_name_plural = _('Kategorioj')
# права
permissions = (
('povas_vidi_kategorion', _('Povas vidi kategorion')),
('povas_krei_kategorion', _('Povas krei kategorion')),
('povas_forigi_kategorion', _('Povas forigu kategorion')),
('povas_shanghi_kategorion', _('Povas ŝanĝi kategorion')),
)
Разрешение прав на модель¶
Поскольку видимость объектов в GraphQL API в случае с моделями Django определяется результатом простого запроса QuerySet, то перед выборкой объектов для выдачи клиенту, класс-коннектор SiriusoFilterConnectionField проверяет в модели наличие статического метода класса с именем _get_perm_cond. Данному методу в качестве аргумента передается объект пользователя текущей сессии (main.Uzanto или auth.AnonymousUser в случае не авторизованного пользователя). Если такой метод в классе-модели присутсвует, то он используется для получения дополнительных условий выборки, т.е. данный метод _get_perm_cond должен вернуть объект или логическое объединение объектов `класса-выражения Q https://docs.djangoproject.com/en/2.2/ref/models/querysets/#q-objects`_. Таким образом при применении данного дополнительного условия выборки из модели, должен получиться доступный для текущего пользователя список объектов. Если необходимо закрыть доступ пользователя вообще к объектам данной модели, то можно через класс-выражение Q заведомо невыполнимое условие, например, выбрать только те записи в которых первичный ключ является NULL (а такого быть не может).
Пример кода из konferencoj.KonferencojKategorio:
[@staticmethod](profile/staticmethod)
def _get_perm_cond(user_obj):
if user_obj.is_authenticated:
# Для авторизированного пользователя
if (perms.has_registrita_perm('konferencoj.povas_vidi_kategorion')
or user_obj.has_perm('konferencoj.povas_vidi_kategorion')):
# Если есть право просмотра у зарегистрированного или есть право Django у пользователя
cond = Q()
else:
# Если права нет, то задаем заведомо невыполнимое условие, например, первичный ключ равен NULL
cond = Q(uuid__isnull=True)
if perms.has_komunumo_adm_perms('konferencoj.povas_vidi_kategorion'):
# Однако, если есть родительские сообщества, где пользователь является администратором,
# получаем список uuid для этих сообществ
adms = KomunumoMembro.objects.filter(
autoro=user_obj, forigo=False, tipo__kodo__in=('administranto', 'komunumano-adm')
).values_list('posedanto_id', flat=True)
# Добавляем условие через логическое ИЛИ (|=)
cond |= Q(posedanto_id__in=adms)
if perms.has_komunumo_mod_perms('konferencoj.povas_vidi_kategorion'):
# Или сообщества, где пользователь является модератором,
# получаем список uuid для этих сообществ
mods = KomunumoMembro.objects.filter(
autoro=user_obj, forigo=False, tipo__kodo__in=('moderiganto', 'komunumano-mod')
).values_list('posedanto_id', flat=True)
# Добавляем условие через логическое ИЛИ (|=)
cond |= Q(posedanto_id__in=mods)
else:
# Для неавторизированных пользователей
if perms.has_neregistrita_perm('konferencoj.povas_vidi_kategorion'):
# Если есть права на просмотр
cond = Q()
else:
# Если права нет, то задаем заведомо невыполнимое условие, например, первичный ключ равен NULL
cond = Q(uuid__isnull=True)
return cond
Разрешение прав на объект модели¶
Для определения прав на конкретный объект модели Django используются методы класса-модели с именами _get_user_permissions (права пользователя на объект) и _get_group_permissions (права групп пользователя на объект). Доступ к этим методам производится автоматически через системный миксин пользователя при проверке наличия прав стандартным для Django способом: user_obj.has_perm(„app_name.perm_name“, obj) (см. тут). Заметьте, обращение к методам произойдет только если в метод объекта пользователя has_perm после имени права передать конкретный объект, наличие права к которому проверяется. В случае, если данные методы (_get_user_permissions и _get_group_permissions) в классе-модели не определены, то проверка прав на конкретный объект всегда будет отричательной, т.е. никаких прав на него любой пользовател иметь не будет. Подразумевается наличие данных методов только в тех моделях, для обхектов которых будет необходимо индивидуальная проверка прав доступа по пользователю. В качестве результата данные методы должны возвращать QuerySet с объектами auth.Permission, т.е. стандартные права из класса-модели прав Django. Со стороны GraphQL API, если в запросе необходимо выдать в качестве результата ещё и список прав доступа пользователя текущей сессии, то в обпределении узла нужно в список родительских классов указать миксин SiriusoPermissions (находится в siriuso.api.mixins). Данный миксин добавляет поле rajtoj (тип List(String) - список строковых объектов) и метод его разрешения resolve_rajtoj, который автоматически получаеи весь список прав на объект для текущего пользователя.
Так как в нашем случае в Siriuso всегда определяются индивидуальные права пользователя на объект, то в основном используется метод _get_user_permissions
Пример кода реализации методов _get_user_permissions и _get_group_permissions из konferencoj.KonferencojKategorio:
def _get_user_permissions(self, user_obj):
# Объявляем пустой список прав
user_perms = Permission.objects.none()
if user_obj.is_authenticated:
# Права зарегистриованных для приложения конференций
all_perms = set(perms.user_registrita_perms(apps=('konferencoj',)))
# Определяем тип членства для пользователя в родительских собществах
membro = KomunumoMembro.objects.select_related('tipo').get(
posedanto_id=self.posedanto_id, autoro=user_obj, forigo=False
)
if membro.tipo.kodo in ('administranto', 'komunumano-adm'):
# Если есть права администратора или администратора сообщества
all_perms |= set(perms.user_komunumo_adm_perms(apps=('konferencoj',)))
elif membro.tipo.kodo in ('moderiganto', 'komunumano-mod'):
# Если есть права модератора или модератора сообщества
all_perms |= set(perms.user_komunumo_mod_perms(apps=('konferencoj',)))
# Преобразуем список прав к имени права без приложения, отфильтровывая только нужные для текущей модели
# и одно право на создание для дочерней модели
all_perms = set(perm.split('.')[-1] for perm in all_perms if perm in (
'konferencoj.povas_vidi_kategorion', 'konferencoj.povas_krei_kategorion',
'konferencoj.povas_forigi_kategorion', 'konferencoj.povas_shanghi_kategorion',
'konferencoj.povas_krei_teman_tipon'
))
# Делаем выборку прав из модели прав Django
user_perms = Permission.objects.filter(content_type__app_label='konferencoj', codename__in=all_perms)
return user_perms
def _get_group_permissions(self, user_obj):
return Permission.objects.none()
Уровни прав доступа в Siriuso¶
В Siriuso предусмотрено 3 уровня правдоступа:
системные права (системные группы и права пользователя Django) права специальных групп права пользователя на кокретный объект.
В основе прав всех уровней используюется стандартная система прав Django django.auth.models.Permission. Системные права - это верхний уровеь, т.е. права, которые получает пользователь через стандартные групп и индивидуальные назначения в админке Django должны быть исключительными. Они должны игнорировать авторство, где это приминимо, и позволять получать соответствующий доступ ко всем объектам моделей; они как бы дают админиские привелегии к опрделённым объектам моделей. Права специальных групп - это права, которые перечисленных в специальных группах (модель main.models.SpecialajGrupoj). По сути в этой модели идет связь специальной группы и групп Django (сделано, чтобы можно было получать права по специальному коду группы). На текущий момент есть следующие спец. группы:
komunumano-adm - права для администратора Сообществ komunumano-mod - права для модератора Сообщетсв neregistrita - права незарегистрированных пользователей registrita - права зарегистрированных пользователей uzanta_pagho - права пользователя на собсвенной странице (стене) Все спец группы определяются программно, т.е. прораммист сам должен определить, какую спец группу спец. группу нужно посмотреть на наличие прав доступа к объекту. Права пользователя на конкретный объект - это права текущего пользователя к конкретному объекту модели. Как было описано выше, для проверки прав на конкретный объект нужно использовать стандартную функцию пользователя Django (в случае с Siriuso - это модель main.models.Uzanto) has_perm(perm_name, object), передав ей второй аргумент - сам объект. В данном случае при программировании методов модели _get_user_permissions и _get_group_permissions как раз следует использовать соответствующие спец группы (например, registrita) для проверки наличия права спец группы пользователя, и скомбинировать наличие соответствующих прав с признаками, например, авторства пользователя к объекту.
В практике программирования проверки прав в мутациях следует руководствоваться следующим принципом:
сперва проверяем наличие системных прав (при помощи метода has_perm без передачи конкретного объекта) если системных прав нет, проверять наличие прав на объект при помощи has_perm уже с передачей ему кокретного объекта.
Так же слудет понимать, что есть 2 операции для объекта в мутациях - создание и изменение (редактирование, удаление). При создании объекта совершенно ясно, что объект ещё не создан, следовательно проверить права на конкретный объект невозможно. В этом случае можно пользоваться такой хитростью, как наличи права на создание объекта у его родительского объекта. Например, в случае с конференциями есть соответствующая иерархия данных: Категория -> Тема -> Комментарий в теме. Тогда получается, что для Комментария темы мы должны проверить наличие права на создание у родительского объекта, т.е. в Теме. Для Темы, соответственно в Категории, а вот для категории нет родительского объекта (хотя на самом деле он есть - Сообщество, но мы пока опустим это), следовательно права на создание корневого объекта должны быть системными или определяться соответствующей спец. группой. В принице, логично, что создание корневых объектов является ситемгым действием и дается только администраторам и модераторам. А вот при изменении объекта, уже надо проверять наличие сначала системных прав, потом прав спец. групп (в случае наличия спец. групп модераторов и администраторов - сначала проверяем пользователя на принадлежность к ним по признакам в объекте модели, а потом уже проверяем права в спец. группе зарегистрированных пользователей, соответственно проверяя авторство объекта).
Для упрощения получения списка прав спец. групп, существует ряд функций. они расположены в модуле siriuso.utils.perms. Там есть 2 типа функций:
функции на получение списка всех прав спец. группы:
# Получаем список прав для не зарегистрированных пользователей
user_neregistrita_perms = functools.partial(get_special_perms, 'neregistrita')
# Получаем список прав зарегистрированного пользователя
user_registrita_perms = functools.partial(get_special_perms, 'registrita')
# Получаем список прав для страницы пользователя
user_page_perms = functools.partial(get_special_perms, 'uzanta_pagho')
# Получаем список прав для админа сообществ
user_komunumo_adm_perms = functools.partial(get_special_perms, 'komunumano-adm')
# Получаем список прав для модератора сообществ
user_komunumo_mod_perms = functools.partial(get_special_perms, 'komunumano-mode')
функции, проверяющие наличие права конкретного в спец. группе:
def has_registrita_perm(perm_name):
return perm_name in user_registrita_perms()
def has_user_page_perm(perm_name):
return perm_name in user_page_perms()
def has_komunumo_adm_perms(perm_name):
return perm_name in user_komunumo_adm_perms()
def has_komunumo_mod_perms(perm_name):
return perm_name in user_komunumo_mod_perms()
def has_neregistrita_perm(perm_name):
return perm_name in user_neregistrita_perms()
Обращаю внимание, что второй тип функция просто проверяет наличие права в спец. группе, а не вычисляет наличие права для объекта. Вычислений прав на конкретный объект - это задача программиста. А вышеуказанный набор функций просто облегчает написание кода. При необходимости создания новыйх спец. групп нужно дополнять соответствующий модуль своими функциями, оформляя их имена схожим образом с уже существующими.