Рубрики
Без рубрики

Авторизация GraphQL с графеном, SQLALCHEMY и OSO

GraphQL видел быстрый апартик в принятии. Это позволяет более выразительный интерфейс между F … с меченым с GraphQL, авторизацией, SQLALCHEMY, PYTHON.

GraphQL видел быстрый апартик в принятии. Он позволяет более выразительный интерфейс между кодом FrontEnd и Backend, позволяя разработчикам точно указать, какие данные требуются для рендера данной страницы. Хотя есть многочисленные победы для инженеров Frontend, команда Backend требует другого мышления. Вместо того, чтобы написать обработчики маршрута, которые возвращают фиксированный набор данных (или, может быть, несколько вариаций), команда Backeng должна писать Функции Resolver которые возвращают части набора данных индивидуально.

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

Этот подход легко начать, но обычно не осуществимо в GraphQL. Существует один маршрут в приложении GraphQl, а потенциальный набор данных возвращается в высокой переменной. Существует много более комбинаций полей в запросе графиков, чем количество маршрутов в типичном приложении для отдыха.

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

Где вы должны выполнять проверки проверки и авторизации? Ответ: внутри выделенного бизнес-логического слоя. Ваш бизнес логический слой должен выступать в качестве единственного источника истины для обеспечения применения Бизнес Домена Правила [акцент добавлен].

https://graphql.org/learn/thinking-in-graphs/#business-logic-layer.

Большой. Легкий. Но как это на самом деле работает? Как мы принудим правило бизнес-домена? Есть ли простая абстракция для указания их?

В этом посте мы посмотрим на использование OSO – библиотеку авторизации открытых источников – для обеспечения применения правил авторизации. Посмотрим, как декларативно указать правила авторизации в Polar, языке политики ОСО и как интегрировать OSO в приложение GraphQL только в нескольких линиях кода.

Мы будем использовать SQLalchemy как нашу ORM и Graphene – популярную библиотеку Python GraphQL. SQLALCHEMY-OSO библиотека обеспечит клей между OSO, GraphQL и SQLALCHEMY, что позволяет нам обеспечить обеспечение согласованности авторизации.

Мы начнем с приложения Basic Flask, представляющих приложение управления расходами. Следовать, клонировать проект на Github Отказ Мы пройдем по частям этого приложения, относящегося к авторизации, но полный код доступен в проекте. Мы предположим некоторое знакомство с колбой, SQLALCHEMY и GRAPHQL. Каждый раздел этого поста основан на фиксации в репозитории, и мы свяжем со соответствующим фиксацией в начале раздела.

Если вы планируете следуете и пробовать примеры, как вы читаете, обязательно следите за Readme Чтобы установить зависимости, чтобы вы могли запустить код. Этот раздел охватывает Первоначальный коммит Отказ

Наше приложение имеет несколько моделей:

  • Расходы: Расход, созданный пользователем.
  • Пользователь: Пользователь доступа к приложению.

Мы начнем с этого, и добавьте еще позже. Вот наши начальные модели от Models.py Отказ Это обычные модели SQLALCHEMY. Мы использовали FLASK_SQLALCHEMY Библиотека для интеграции с колбой и облегчить нашу настройку.

from flask_sqlalchemy import SQLAlchemy

from sqlalchemy.orm import relationship

db = SQLAlchemy()

class Expense(db.Model):
    __tablename__ = 'expenses'

    id = db.Column(db.Integer, primary_key=True)
    amount = db.Column(db.Integer)

    created_by_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    created_by = relationship("User")

    description = db.Column(db.Text)

class User(db.Model):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(256))

Расход создан пользователем, доступным в EXCENSE.CREATED_BY имущество.

У нас также есть схема GraphQL, определенная с использованием графена над этими моделями в Приложение/Schema.py :

import graphene
from graphene import relay
from graphene_sqlalchemy import SQLAlchemyConnectionField, SQLAlchemyObjectType

from flask import g

from . import models

class Expense(SQLAlchemyObjectType):
    class Meta:
        model = models.Expense
        interfaces = (relay.Node,)

class User(SQLAlchemyObjectType):
    class Meta:
        model = models.User
        interfaces = (relay.Node,)

class Query(graphene.ObjectType):
    expenses = SQLAlchemyConnectionField(Expense.connection)
    user = graphene.Field(User)
    node = graphene.relay.Node.Field()

    def resolve_user(parent, info):
        return (g.current_user
                if isinstance(g.current_user, models.User)
                else None)

# ... snip ...

schema = graphene.Schema(query=Query)

Эта схема использует Графена-SQLALCHEMY Библиотека Создание объектов графена схемы для наших моделей SQLALCHEMY. Это Стандартный подход Для использования Sqlalchemy с графеном.

Мы можем сделать простой запрос (я использую потрясающие График Инструмент для этого, но вы также можете просто пойти в http://localhost: 5000/graphql В вашем браузере) и увидите, что наше приложение работает:

Добавление авторизации

На данный момент у нас есть приложение Basic GraphQL без разрешения. Давайте установим это. Вот …| совершать на Github Отказ

Наше первоначальное правило авторизации будет основано на концепции владельцев данных: Пользователи могут просматривать свои собственные расходы.

Для реализации этого правила нам нужно будет выполнять фильтрацию на уровне SQLachemy. Что значит:

  1. Добавление пользовательского Resolver для расходов
  2. Проверка всех других мест Схема GraphQL может получить доступ к расходам
  3. Реализация пользовательского запроса SQLALCHEMY
  4. Повторяя для каждой другой модели и проверки авторизации.

Это правило авторизации основано на каждой записи базы данных – иногда называют разрешениями уровня объекта. Это может быть сложно абстрактно как промежуточное ПО, и, безусловно, является более сложным, чем просто добавление простого декоратора, который мы обсуждали ранее.

Вместо этого мы будем использовать SQLALCHEMY-OSO библиотека. Нам нужно сделать два изменения, чтобы установить это:

  1. Добавьте экземпляр ОСО к нашей заявке.
  2. Напишите политику, содержащую наши правила авторизации.

Чтобы добавить ОСО, мы изменим create_app Функция:

from oso import Oso
from sqlalchemy_oso import register_models

def create_app():
    # snip ...

    # Create oso instance which will hold our policy.
    oso = Oso()  
    # Make SQLAlchemy models available in the policy.
    register_models(oso, db.Model)  
    # Load policy file into oso.
    oso.load_file(Path(__file__).parent / "policy.polar")   

    # ...

Тогда мы поменяем SQLalchemy Объект в нашем приложении с АвторизацияКлальчемия Отказ В Models.py :

from sqlalchemy_oso.flask import AuthorizedSQLAlchemy

db = AuthorizedSQLAlchemy(
    get_oso=lambda: current_app.oso,
    get_user=lambda: getattr(g, "current_user", None),
    get_action=lambda: "read"
)

Мы используем этот объект вместо flask_sqlalchemy. SQLalchemy Отказ Это обеспечивает SQLalchemy Sessionous Object Это фильтрует запросы только для возврата объектов, которые уполномочены для текущего пользователя и действий.

Наш политический файл, Приложение/Политика.Polar пока пуст.

Теперь давайте проверим наш запрос результатов:

Ничего не вернулось больше. Почему? Файл политики пуст. Полярное отрицает по умолчанию, поэтому нам нужно добавить некоторые правила. Прежде чем мы сделаем это, давайте посмотрим, как это работает. Наше приложение имеет конечную точку, которая будет регистрировать SQL, используемый для выполнения нашего последнего запроса. Если вы следуете, запустите приложение с:

$ FLASK_RUN_EXTRA_FILES=app/policy.polar FLASK_DEBUG=1 flask run

Запустите запрос в graphiql. Ниже приведена хорошая отправная точка.

{
  expenses {
    edges {
      node {
        id
        description
        createdBy {
          email
        }
      }
    }
  }
}

Теперь посетите, http://localhost: 5000/sql Отказ

Здесь мы видим:

SELECT count(*) AS count_1
  FROM (
        SELECT expenses.id AS expenses_id,
               expenses.amount AS expenses_amount,
               expenses.created_by_id AS expenses_created_by_id,
               expenses.description AS expenses_description
          FROM expenses
         WHERE 0 = 1
         ORDER BY expenses.id ASC
       ) AS anon_1

Обратите внимание на нашу пункт, где: 0 Отказ Откуда это происходит? SQLALCHEMY-OSO Обеспечить нашу полярную политику путем перевода правил из политики в фильтры SQL. 0 всегда ложно, что не вызывает возврата записей из базы данных.

Добавление некоторых правил

Теперь, когда у нас интегрировано ОСО, давайте закончим реализацию нашего правила авторизации. Держите приложение запущено, Flask_run_extra_files Переменная среды Мы устанавливаем, гарантируют, что колба перезагружает приложение при изменении политики.

Давайте попробуем следующее (в App/Policy.polar ):

allow(_actor, _action, _resource);

Эта политика позволяет пользователю получить доступ к любому объекту. В полярном порядке каждое утверждение в политике называется правилом. Разрешить Правило – это специальное правило, которое используется в качестве въездной точки для политик. У него есть три аргумента, Актер (кто делает запрос), действие (Что будет делать запрос), а Ресурс (Что за запрос работает над). Наша политика имеет одно правило. Каждый параметр начинается с _ который указывает на анонимную переменную (тот, который что-то совпадает, потому что на нем нет ограничений).

Rerun запрос и проверить SQL (просто обновить http://localhost: 5000/sql . Вы увидите несколько заявлений там, я выбрал основной запрос SQL, используемый для разрешения запроса GraphQL):

SELECT expenses.id AS expenses_id,
       expenses.amount AS expenses_amount,
       expenses.created_by_id AS expenses_created_by_id,
       expenses.description AS expenses_description
  FROM expenses
 WHERE 1 = 1
 ORDER BY expenses.id ASC
 LIMIT 4
OFFSET 0

Теперь мы видим Где Отказ Это всегда верно! В результате все было возвращено.

Давайте пойдем немного дальше:

allow(_: User, "read", _: Expense);

Эта политика заявляет, что все пользователи могут прочитать все расходы. Опять же _ обозначает анонимную или игнорируемую переменную, но : Пользователь Синтаксис проверяет, что входные данные совпадают с определенным типом. В этом случае пользователь и модели расходов из наших Models.py файл.

Вот возвращенные данные:

Обратите внимание, что Созданиебий поле каждого расхода сейчас null Отказ Так как у нас нет правила для Пользователь ресурс, пользователь не может просматривать любые расходы. Добавьте правило для Пользователь Теперь:

allow(_: User, "read", _: Expense);
allow(_: User, "read", _: User);

Выше политика в это совершить на GitHub.

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

allow(user: User, "read", expense: Expense) if
    expense.created_by = user;

allow(_: User, "read", _: User);

Давайте посмотрим на SQL:

SELECT expenses.id AS expenses_id,
       expenses.amount AS expenses_amount,
       expenses.created_by_id AS expenses_created_by_id,
       expenses.description AS expenses_description
  FROM expenses
 WHERE 1 = expenses.created_by_id
 ORDER BY expenses.id ASC
 LIMIT 2
OFFSET 0

Теперь мы видим Где.created_by_id Отказ 1 это идентификатор текущего пользователя. Наша политика переведена на SQL! Проверьте это совершить, чтобы запустить его Действительно

Идти дальше

До сих пор мы показали небольшой пример политики. ОСО дает нам последовательную абстракцию для выражения правил авторизации, даже если мы не можем просто использовать простые декораторы с GraphQL. Для того, чтобы осуществить это, мы не должны изменить наши арбитры, или записывать новые. Полярные и GraphQL являются оба декларативными подходами к их проблемным доменам: Polar предоставляет абстракцию для объявления правил авторизации, в то время как GraphQL обеспечивает декларативную абстракцию для получения данных с бэкэнда.

Если вы хотите увидеть немного больше, мы сделали эту политику немного далее в примере приложения здесь Отказ Эта политика показывает:

  • Новое правило авторизации, которое использует отношения: пользователи могут просматривать затраты на проекты, которые они находятся в. Полярные поддерживает выразительные правила, связанные с отношениями, которые чрезвычайно распространены в политике авторизации (отношения часто группировать пользователь и ресурсы вместе способах, которые отражают бизнес-домен).
  • Обобщение концепций авторизации (например, создание) с использованием правил. Запись новых правил позволяет вам абстрактным функциональным возможностям и сохранить вашу политику сухого эквивалента для подъема распространенных функций в функцию в остальной части вашего кода (Polar’s просто код в конце концов!).

Чтобы узнать больше о том, что мы прошли через:

Оригинал: “https://dev.to/dhatch/graphql-authorization-with-graphene-sqlalchemy-and-oso-5fhi”