Переход с Django на Next.js: Что является эквивалентом для Django-Guardian?

Переход с Django на Next.js: Что является эквивалентом для Django-Guardian?

Django - это популярный веб-фреймворк на основе Python. Это огромный так называемый “battery-included” фреймворк, охватывающий многие аспекты веб-разработки: аутентификацию, ORM, формы, панели администратора и т.д. Это также фреймворк с сильным мнением, который предлагает шаблоны почти для всего, что вы делаете, что позволяет вам чувствовать себя хорошо управляемым во время разработки.

Однако в последние несколько лет Django, как и большинство не-JS стеков, уступает свои позиции JS-фреймворкам, таким как Next.js, Remix, Nuxt и др.

Переход от одного фреймворка к другому требует тщательного планирования и исполнения, особенно если вы одновременно меняете язык. Популярным и мощным Javascript/Typescript эквивалентным стеком для Django может быть следующая комбинация:

  • Next.js: маршрутизация URL, SSR и создание страниц с помощью ReactJS (слой представления + шаблонов Django).
  • NextAuth: аутентификация (аутентификация Django)
  • Prisma: ORM + миграция баз данных (слой моделей Django).

Эти части очень хорошо сочетаются друг с другом и достаточны для замены большинства возможностей, которые предоставляет Django. Однако одного элемента не хватает. В Django есть встроенная функция разрешений, но она ограничена контролем на уровне модели, т.е. если пользователь или группа имеют доступ X к модели типа Y. Многие пользователи используют популярный пакет django-guardian для реализации разрешений на уровне строк. Он позволяет устанавливать разрешения между пользователями/группами и объектами, управляет базовыми таблицами базы данных разрешений и предоставляет API для настройки и проверки таких разрешений.

К счастью, если вы решите использовать Prisma ORM в своем новом стеке, вы можете использовать ZenStack для достижения аналогичных функций с меньшими усилиями. ZenStack - это набор инструментов, созданный как расширение возможностей Prisma ORM, и одним из его основных направлений является контроль доступа. В этом посте мы кратко сравним, как django-guardian и ZenStack решают проблему разрешений на уровне строк, соответственно.

Назначение разрешений

Предположим, мы создаем сайт для ведения блогов и имеем модель Post. В Django уже есть встроенные модели User и Group и предопределенные CRUD разрешения для каждой модели. С помощью django-guardian вы можете использовать API assign_perm для назначения разрешений:

from django.contrib.auth.models import User, Group
from guardian.shortcuts import assign_perm

# establishing permission between a user and a post
user1 = User.objects.create(username='user1')
post1 = Post.object.create(title='My Post', slug='post1')
assign_perm('view_post', user1, post1)
assign_perm('change_post', user1, post1)

# establishing permission between a group and a post
group1 = Group.objects.create(name='group1')
user1.groups.add(group1)
assign_perm('view_post', group1, post1)
assign_perm('change_post', group1, post1)

В отличие от Django, Next.js + Prisma + ZenStack является неориентированным фреймворком и не имеет встроенных моделей для User и Group. Вам необходимо явно смоделировать их с помощью схемы ZenStack:

model User {
  id Int @id @default(autoincrement())
  username String
  groups Group[]
}

model Group {
  id Int @id @default(autoincrement())
  name String
  users User[]
}

Схема не только моделирует типы данных и отношения, но и позволяет выразить в ней полномочия. Давайте посмотрим, как смоделировать разрешения пользователя и группы на пост:

model User {
  id Int @id @default(autoincrement())
  username String
  groups Group[]
  posts Post[]
}

model Group {
  id Int @id @default(autoincrement())
  name String
  users User[]
  posts Post[]
}

model Post {
  id Int @id @default(autoincrement())
  title String
  slug String @unique
  groups Group[]
  users User[]

  // if the current user is in the user list of the post, update is allowed
  @@allow('read,update', users?[id == auth().id])

  // if the current user is in any group of the group list of the post, 
  // update is allowed
  @@allow('read,update', groups?[users?[id == auth().id]])

  // ... other permissions
}

Некоторые разъяснения:

  • Синтаксис @@allow добавляет метаданные контроля доступа к модели. Действие разрешено, если любое из правил @@allow имеет значение true.
  • auth() представляет текущего пользователя для входа в систему. Вскоре вы увидите, как он подключается.
  • Синтаксис model?[expression] представляет собой предикат над отношением to-many. users?[id == auth().id] читается как "имеет ли любой элемент в коллекции users id, равный id текущего пользователя".

Как вы можете видеть, подход к моделированию разрешений совершенно разный для django-guardian и ZenStack. Django-guardian использует императивный код для управления разрешениями в коде приложения, в то время как ZenStack предпочитает декларативный стиль в схеме данных. Кроме того, в django-guardian установка и проверка разрешений (показанные в следующем разделе) разделены, в то время как в ZenStack вы моделируете данные о разрешениях и правила в одном месте.

Проверка разрешений

Как и в случае с назначением разрешений, в django-guardian проверка разрешений также выполняется явно в коде приложения, в основном одним из двух способов:

1. Используя императивный код

user1 = User.objects.get(username='user1')
post1 = Post.objects.get(slug='post1')

from guardian.core import ObjectPermissionChecker
checker = ObjectPermissionChecker(user1)
if checker.has_perm('change_post', post1):
  # update logic here

2. Использование декораторов

Вы также можете использовать декораторы для включения автоматической проверки разрешений в представлениях:

from guardian.decorators import permission_required_or_403

@permission_required_or_403('change_post', (Post, 'slug', 'post_slug'))
def edit_post(request, post_slug):
  # update logic here

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

В ZenStack проверка разрешений намного проще, поскольку правила выражены на уровне ORM, поэтому они автоматически применяются при вызове уровня данных:

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

Единственная необходимая настройка - создать клиентскую обертку Prisma с контролем доступа и текущим пользователем в качестве контекста:

// update-post.ts: function for updating a post
import { prisma } from './db';
import { getSessionUser } from './auth';

export function updatePost(request: Request, slug: string, data: PostUpdateInput) {
  const user = await getSessionUser(req);

  // get an access-control enabled Prisma wrapper
  // the "user" context value supports the `auth()` 
  // function in the permission rules
  const db = withPresets(prisma, { user });

  // error will be thrown if the current user doesn't
  // have permission
    return db.post.update({ where: { slug }, data });
}

Подведение итогов

Как видите, и django-guardian, и ZenStack решают проблемы с разрешениями на уровне строк, хотя и в совершенно разных парадигмах. Django-guardian полагается на императивный код, и ответственность за то, чтобы обеспечить надлежащую логику проверки там, где это необходимо, в большей степени лежит на разработчике.

С другой стороны, ZenStack отдает предпочтение декларативному моделированию. Поскольку правила выражаются на уровне ORM, они автоматически применяются ко всему коду приложения, использующему уровень данных, что повышает согласованность и надежность.

Будучи относительно новым инструментарием, ZenStack не лишен своих ограничений. Например, по сравнению с django-guardian, в нем отсутствуют две основные функции:

Пользовательские разрешения

ZenStack моделирует фиксированный набор разрешений: CRUD, в то время как django-guardian позволяет вам определять пользовательские разрешения. Хотя все разрешения в конечном итоге сводятся к CRUD, пользовательские разрешения могут выражать тонкий контроль разрешений на уровне полей. Это пока не поддерживается ZenStack.

API для явной проверки разрешений

Проверка разрешений в ZenStack внедрена в CRUD API ORM. Однако иногда бывает удобно явно проверить, есть ли у пользователя разрешение на определенный объект, и использовать его, например, для динамического отображения пользовательского интерфейса.

Я надеюсь, что эта статья поможет вам на пути перехода вашего стека python в мир Javascript, и удачи вам в этом начинании!