ОС CTSS (Compatible Time-Sharing System) Массачусетского технологического института была разработана в 1961 году для обеспечения независимого доступа нескольких пользователей к большому компьютеру. Вскоре разработчики обнаружили, что существует огромная потребность в предоставлении друг другу общего доступа к программам и данным. Это способствовало возникновению первых разговоров о компьютерной безопасности и привело к тому, что защита стала основной целью разработки ОС Multics. Спустя годы после выпуска Multics, Зальтцер (Saltzer) и Шредер (Schroeder) опубликовали книгу "Защита информации в компьютерных системах", в которой были учтены уроки, полученные при ее разработке и реальном использовании. Их работа является одной из самых цитируемых в истории работ по безопасности и первой, где были использованы многие термины, которые мы употребляем сегодня, включая понятие "Наименьшая привилегия".

В этой статье я представлю три метода управления доступом, начиная со списка контроля доступа (ACL) - механизма, реализованного в Multics - затем рассмотрю доступ на основе ролей (RBAC), а затем доступ на основе атрибутов (ABAC). В одном из следующих постов я рассмотрю управление доступом на основе целей (Purpose Based Access Control, PBAC), которое было представлено в 2008 году, когда индустрия стала уделять приоритетное внимание конфиденциальности в интернете. PBAC предоставляет способ сопоставления данных по намерениям доступа с целевыми данными, определенными в политике конфиденциальности, согласованной с пользователями. Он опирается на механизмы, предоставляемые RBAC и ABAC, поэтому, чтобы понять PBAC, я бы хотел сначала познакомиться с принципами работы RBAC и ABAC. Зачастую, при реализации чего-либо в коде, мне удается полностью понять, как это работает. Я попытаюсь закрепить некоторые из этих концепций управления доступом в сознании читателя, написав несколько примеров на языке Python.

Мы перейдем к RBAC, сначала изучив ACL, поскольку RBAC был спроектирован с учетом недостатков ACL. Давайте начнем с настройки кода в поддержку наших примеров.

from types import NoneType
from typing import Callable, TypeVar, Generic, Optional
from dataclasses import dataclass
from enum import Enum

PrincipalId = int

# Represents an individual seeking access (e.g. an engineer responding to an
# incident)
@dataclass
class Principal:
    id: PrincipalId
    name: str


# Different access control methods require different types of metadata 
# attached to records (e.g. ACLs, opt-in information)
RecordMetadata = TypeVar("RecordMetadata")
RecordId = int


# Single patient record
@dataclass
class Record(Generic[RecordMetadata]):
    id: RecordId
    patient_name: str
    dob: int
    metadata: Optional[RecordMetadata] = None


# An action the principal would take on a `Record` or `Record`s
class Action(Enum):
    READ = 1
    WRITE = 2

Компоненты, которые здесь определены, будут использоваться в каждом из трех следующих примеров управления доступом.

  1. Принципал (Principal) - лицо, пытающееся получить доступ. Примером может быть пользователь AWS (Amazon Web Services) IAM (Identity and Access Management) для инженера из вашей команды, которому нужно попасть в продакшн.

  2. Запись (Record) - объект, к которому пытается получить доступ Principal. В наших примерах мы будем моделировать простые истории болезни, но на практике это может быть файл или папка в файловой системе, облачный ресурс, такой как S3 или EC2 от AWS, или строка в базе данных. Роль, которую играет Record в нашей модели, упоминается как object или resource во многих программных системах или исследовательских работах по безопасности. Я выбрал Record, потому что он соответствует конкретному примеру из истории болезни.

  3. Действие (Action) - то, что Principal хочет сделать с Record.

Используя эти термины, мы можем сформулировать цель любой системы управления доступом как ответ на вопрос: "Имеет ли данный Principal право выполнять такое Action с этой Record?". ACL, RBAC и ABAC имеют разные возможности и характеристики, связанные с масштабируемостью и гибкостью, но каждая из них поспособствует нам в получении ответа.

Давайте добавим еще один класс. Нам нужно реализовать System (система), которая может выполнять Action над нашими Record.

Authorizer = Callable[[Principal, Action, Record], bool]

class System:
    def __init__(self, records: list[Record], authorizer: Authorizer):
        self.records = records
        self.is_authorized = authorizer

    def get(
        self,
        record_id: RecordId,
        principal: Principal
        ) -> Optional[Record]:
        """Return a record if the Principal has Action.READ access to it and
        return None if not
        """
        for record in self.records:
            if record_id == record.id and self.is_authorized(
                principal, Action.READ, record
            ):
                return record
        return None

    def update(
        self,
        record_id: RecordId,
        principal: Principal,
        updates: dict
        ):
        """Update the Record with id equal to record_id only if the
        Principal has Action.WRITE access. Otherwise do nothing.
        """
        for record in self.records:
            if record.id == record_id and self.is_authorized(
                principal, Action.WRITE, record
            ):
                for (k, v) in updates.items():
                    setattr(record, k, v)

Обратите внимание, что для создания новой System передаются record и Authorizer (авторизатор). Именно здесь мы имплементируем специфику каждой схемы управления доступом. В ACL и ABAC мы будем прикреплять различные типы метаданных к нашим записям, а наша реализация RBAC не будет видеть никаких метаданных Record. Но самая большая разница будет существовать в логике и контексте, привносимом функцией authorizer и ее замыканием.

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

import pytest

# Members of your engineering team.
@pytest.fixture
def principals() -> tuple[Principal, Principal]:
    return (Principal(1, "Alice"), Principal(2, "Bob"))

# Patient records we need to protect.
@pytest.fixture
def records() -> list[Record[NoneType]]:
    return [
        Record[NoneType](1, "Alyssa", 1965),
        Record[NoneType](2, "Ben", 1974),
    ]

# In some cases, we are going to want metadata on our records. For example,
# ACLs will be attached to records and ABAC will allow us to use any
# attribute we want to attach to any record
def records_with_metadata(
    metadata: tuple[RecordMetadata, RecordMetadata]
    ) -> list[Record[RecordMetadata]]:
    
    return [
        Record[RecordMetadata](1, "Alyssa", 1965, metadata[0]),
        Record[RecordMetadata](2, "Ben", 1974, metadata[1]),
    ]

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

def authorizer_tests(authorized: Authorizer, records: list[Record]):
    """Asserts that:
    1. Alice gets READ and WRITE access to both records.
    2. Bob only gets READ access to Ben's record.
    """
    system = System(records, authorized)

    assert records[0] == system.get(records[0].id, ALICE)
    assert not system.get(records[0].id, BOB)
    assert records[1] == system.get(records[1].id, BOB)

    system.update(records[0].id, ALICE, {"dob": 1994})
    assert 1994 == system.get(records[0].id, ALICE).dob

    system.update(records[1].id, BOB, {"dob": 2006})
    assert 1974 == system.get(records[1].id, BOB).dob

Мы можем использовать эти утверждения для проверки каждой System, которую собираемся создать. Одно замечание: мы не поддерживаем явные отказы, которые являются общепринятой возможностью в современном управлении доступом. Стоит узнать, как явные запреты могут влиять на взаимодействие ролей RBAC и правил ABAC, но это не относится к концепциям, которые мы рассмотрим сегодня.Все разрешения, которые мы определим ниже, будут следствием допуска предоставленного Action.

Списки управления доступом (ACL)

ACL были впервые применены для защиты файловой системы Multics в 1965 году. Сегодня они широко используются в файловых системах, сетевой безопасности и облачных сервисах. Первые средства управления доступом для S3 на самом деле были ACL.

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

По этой причине мы будем прикреплять наши ACL непосредственно к Record, а не включать ссылку (например, record_id: RecordId) в качестве свойства ACL. Хотя в отдельных случаях реализация ACL может быть иной, данный подход помогает понять, как логически структурированы ACL с точки зрения системного администратора. Это позволит лучше разобраться в сильных и слабых сторонах ACL и выявить их отличия от других механизмов управления доступом.

def test_acl(principals: tuple[Principal, Principal]):
    @dataclass
    class AccessControl:
        principal: Principal
        actions: set[Action]

    (alice, bob) = principals
    alice_rw = AccessControl(alice, {Action.READ, Action.WRITE})
    bob_r = AccessControl(bob, {Action.READ})

    # Create new records with ACLs directly attached
    records: list[Record[list[AccessControl]]] = records_with_metadata(
        ([alice_rw], [alice_rw, bob_r])
    )

    def acl_authorizer(
        principal: Principal,
        action: Action,
        record: Record[list[AccessControl]],
    ) -> bool:
        from collections.abc import Iterable

        if isinstance(record.metadata, Iterable):
            for acl in record.metadata:
                if acl.principal.id == principal.id and action in acl.actions:
                    return True
        return False

    # main.py::test_acl PASSED
    authorizer_tests(acl_authorizer, records, principals)

Отлично! Это успешно проходит наши тесты и помогает нам понять, как может работать простая система на основе ACL. System использует нашу имплементацию Authorizer для проверки списка правил AccessControl, прикрепленных к Record, чтобы определить, может ли Principal выполнять Action над этой Record.

Это также помогает нам понять, что масштабирование команды и количества записей с помощью такой простой реализации может быть затруднено. Некоторые обновления политики могут потребовать от вас обновления каждой отдельной Record. В современных реализациях ACL есть две фичи, которые улучшают управляемость:

  1. Ссылки на группы Principal, а не на отдельных Principal

  2. Ссылки на группы или иерархии Record, а не на отдельные Record.

Если заменить ссылку на Principal ссылкой на новый объект Group, содержащий список Principal (или других Group), системные администраторы смогут перемещать Principal в/из Group не затрагивая ACL. Group могут быть сконфигурированы в иерархии для дополнительной гибкости и носить имена, соответствующие бизнес-функциям, помогая определить, кто и к чему должен иметь доступ.

Кроме того, если бы мы также разрешили группировать Record (возможно, с помощью Table (таблица) или Folder (папка)), то могли бы добавлять и удалять ссылки на Record без обновления самих ACL. Системы на основе ACL, поддерживающие обе возможности, приближены к минимальной реализации RBAC. Недостающей составляющей было бы экстернализировать Action, чтобы их не нужно было определять для каждой Record, и тогда мы могли бы полностью избавиться от ACL. RBAC предлагает именно такую модель.

Доступ на основе ролей (RBAC)

RBAC была представлена в 1992 году, чтобы упростить определение отношений между Principal и Record, которые мы пытаемся защитить. Роли (Roles) содержат ссылки на пользователей (Users) и объекты (Objects) или, в нашем случае, на Principal и Record. Они также содержат Action, которые Principal могут выполнять над этими Record.

Из документа 1992 года, представляющего RBAC

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

def test_rbac(
    principals: tuple[Principal, Principal],
    records: list[Record[NoneType]]
    ):

    # The Role allows us to define relationships between many Principals and
    # many Records in one place.
    @dataclass
    class Role:
        name: str
        principal_ids: set[PrincipalId]
        permissions: list[tuple[set[Action], set[RecordId]]]

        def has_principal(self, id: PrincipalId) -> bool:
            return id in self.principal_ids

        def has_action_for_record(
            self, action: Action, record_id: RecordId
        ) -> bool:
            for (actions, record_ids) in self.permissions:
                if action in actions and record_id in record_ids:
                    return True
            return False

    # Convenience method to pull ids out of records.
    def get_ids(records) -> set[RecordId]:
        return set(map(lambda r: r.id, records))

    (alice, bob) = principals
    roles = [
        Role(
            "Admin",
            {alice.id},
            [ ({Action.READ, Action.WRITE}, get_ids(records)) ],
        ),
        Role(
            "ReadOne",
            {bob.id},
            [ ( { Action.READ }, get_ids(records[1:])) ]
        ),
    ]

    def rbac_authorizer(
        principal: Principal, action: Action, record: Record[NoneType]
    ) -> bool:
        for role in roles:
            if (role.has_principal(principal.id) and
                role.has_action_for_record(action, record.id)
            ):
                return True
        return False

    # main.py::test_rbac PASSED
    authorizer_tests(rbac_authorizer, records, principals)

Обратите внимание, что у наших record больше нет metadata (метаданных), в которых мы хранили наши ACL. Эти метаданные были перенесены в собственный объект Role, а это значит, что нам нужно включить ссылки на Record в формате RecordId. Рассмотрим пример, когда у нас есть общий набор разрешений и множество Record. RBAC упрощает нам жизнь, позволяя создать один Role с этим набором разрешений, вместо того, чтобы помещать те же разрешения в список AccessControl для каждой Record.

Фичи, которые не были отраженны в этом примере, содержат ролевые иерархии, где Role может наследовать разрешения от своих предков. Это удобно, поскольку в организациях часто существуют HR-иерархии, приблизительно соответствующим в общих чертах схемам разрешений, которые постепенно становятся более доступными. Еще одно отличие заключается в том, что в этом коде нет понятия "действия (active)" роли (Role). Он просто проверяет все Role, которые относятся к рассматриваемому Principal и Record. Наконец, еще одна деталь, которую можно считать упущенной в этом примере: большинство современных систем RBAC поддерживают группы Principal, поэтому иерархия Principal может быть определена независимо от иерархии Role.

RBAC и наименьшая привилегия

RBAC чрезвычайно распространена в программных системах и прошла долгий путь с тех пор, как была формализована в 1992 году. Недостатки такого подхода становятся очевидными по мере того, как наименьшие привилегии выходят в организации на первый план. Сокращение Role до минимально необходимого количества разрешений может привести к большому числу пермутаций, в результате чего возникает огромное количество Role, которыми необходимо управлять. Подумайте, что только для S3 в AWS существует 243 возможных Actions (действия). Современные политики безопасности также содержат новые атрибуты, которые могут обеспечить большую защиту, например, IP-адрес пользователя или проверка того, пытается ли он получить доступ именно в рабочее время. В обычной RBAC не хватает возможностей для поддержки этих требований. Тогда на помощь приходит ABAC.

Контроль доступа на основе атрибутов (ABAC)

RBAC и ACL позволяют вам определять отношения между субъектами (Principal) и ресурсами (Record). ABAC разрешает сделать эти отношения условными на базе атрибутов. Такие атрибуты могут исходить от Principal, System или самих данных. Возможность определять логику доступа по сравнению со жесткой взаимосвязью, а также использование такого богатого набора информации делает ABAC очень мощным.

ABAC также известен как управление доступом на основе политики. Чтобы смоделировать его, создадим класс Policy, который содержит набор Rule (правил), а также Principal и Record, к которым эти правила относятся. Давайте посмотрим, сможем ли мы реализовать простую систему контроля на основе атрибутов, которая поможет нам пройти тесты, которые мы использовали.

def test_abac(
    principals: tuple[Principal, Principal],
    records: list[Record[NoneType]]
    ):

    import operator as op

    # Comparison operators for comparing attributes to values in our Policies
    operators = {
        "=": op.eq,
        "!=": op.ne,
        "any": lambda _1, _2: True,
        "true": lambda x, _: bool(x),
        "false": lambda x, _: not x,
    }

    # A Rule holds the information required to look up an attribute in the
    # right entity and compare it to a given value. An example of a rule
    # could be "RecordAttributes.email_opt_out = True" to represent a record
    # related to a user who has opted out of email correspondence.
    @dataclass
    class Rule:
        entity_name: str
        attribute_name: str
        operator: str
        compare_value: Optional[Any] = None

    # Policy has Principals and Actions but not Records. Whether a Principal
    # has access to a given record is determined by the conditions defined
    # in the Rules
    @dataclass
    class Policy:
        name: str
        principal_ids: set[PrincipalId]
        actions: set[Action]
        rules: list[Rule]

        def has_principal(self, id: PrincipalId) -> bool:
            return id in self.principal_ids

        def has_action(self, action: Action) -> bool:
            return action in self.actions

    (alice, bob) = principals

    # Define policies to pass our tests (even if this is not a realistic use
    # case for ABAC)
    policies = [
        Policy(
            "Admin",
            {alice.id},
            {Action.READ, Action.WRITE},
            [Rule("Record", "id", "any")],
        ),
        Policy(
            "ReadOne",
            {bob.id},
            {Action.READ},
            [Rule("Record", "id", "=", 2)],
        ),
    ]

    # This will evaluate a policy for every record passed in. We're only
    # going to support attributes in Records to get the tests to pass.
    def abac_authorizer(
        principal: Principal, action: Action, record: Record[NoneType]
    ) -> bool:
        for policy in policies:
            if (policy.has_principal(principal.id) and
                policy.has_action(action)
            ):
                for rule in policy.rules:
                    if rule.entity_name == "Record":
                        # Evaluate the Rule by pulling the attribute from the
                        # Record and comparing it to the value in the Rule
                        record_value = getattr(record, rule.attribute_name)
                        if operators[rule.operator](
                            record_value, rule.compare_value
                        ):
                            return True
        return False

    # main.py::test_abac PASSED
    authorizer_tests(abac_authorizer, records, principals)

Мы сделали минимум, чтобы наши тесты прошли, создав правила, которые ссылаются непосредственно на RecordId. Это не является наиболее вероятным или практическим применением ABAC, но демонстрирует, как механика отличается от RBAC и ACL. Большинство систем управления доступом на основе ABAC работают вместе с RBAC, поэтому вы можете назначать Rule для Role вместо статического списка PrincipalId. Если бы мы использовали гибридную систему ABAC/RBAC, то, вероятно бы, просто настроили наши Role для прохождения тестов и не беспокоились о Rule.

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

Примеры атрибутов, которые могут быть доступны системе ABAC

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

# Convenience function to get the value of an attribute that a rule is
    # referring to (e.g. `Environment.business_hours`)
    def get_attribute_value_from_entity(rule: Rule, entities: list):
        for entity in entities:
            if rule.entity_name == type(entity).__name__:
                return getattr(entity, rule.attribute_name)

    # A class to hold System attributes
    @dataclass
    class Environment:
        business_hours: bool  # Is the current time within business hours

    # We want to test a few individual policies. We'll do that by redefining
    # abac_authorizer with different combinations of Policy and Environment
    # objects
    def get_system(
        policy: Policy,
        records: list[Record],
        env: Environment = Environment(True)
        ) -> System:

        def abac_authorizer(
            principal: Principal, action: Action, record: Record[NoneType]
        ) -> bool:
            if (policy.has_principal(principal.id) and
                policy.has_action(action)):

                for rule in policy.rules:
                    value = get_attribute_value_from_entity(
                        rule, [record, record.metadata, env, principal]
                    )
                    if operators[rule.operator](value, rule.compare_value):
                        return True
            return False

        return System(records, abac_authorizer)

    # Let's see if we can create some interesting policies ...

    # Policy 1: Alice can only access Records during business hours
    business_hours_policy = Policy(
        "BusinessHoursAccess",
        {alice.id},
        {Action.READ},
        [Rule("Environment", "business_hours", "true")],
    )

    # Should be able to access during business hours
    system = get_system(business_hours_policy, records, Environment(True))
    assert system.get(1, alice)

    # Should not be able to access after business hours
    system = get_system(business_hours_policy, records, Environment(False))
    assert not system.get(1, alice)

    # Policy 2: Make sure we ignore `Record`s where the user opted out

    # Let's start by attaching opt out attributes to each record
    @dataclass
    class RecordAttributes:
        user_opt_out: bool

    # Create a set of `Record`s with an attribute that indicates whether the
    # user has opted out of email correspondence
    opt_outs: list[Record[RecordAttributes]] = records_with_metadata(
        (RecordAttributes(True), RecordAttributes(False))
    )

    opt_out_policy = Policy(
        "IgnoreOptOuts",
        {alice.id},
        {Action.READ},
        [Rule("RecordAttributes", "user_opt_out", "false")],
    )
    system = get_system(opt_out_policy, opt_outs)

    # Should not be able to access the first record (user_opt_out == True)
    assert not system.get(1, alice)

    # Should be able to access the second record (user_opt_out == False)
    assert system.get(2, alice)

    # Policy 3: Sorry Bob
    no_bobs = Policy(
        "NoBobs",
        {bob.id},
        {Action.READ},
        [Rule("Principal", "name", "!=", bob.name)],
    )
    system = get_system(no_bobs, records)
    assert not system.get(1, bob)
    assert not system.get(2, bob)

    bob.name = "Robert"
    assert system.get(1, bob)

    # Still passing!
    # main.py::test_abac PASSED

Надеемся, эти примеры дают некоторое представление о силе и гибкости ABAC. Современные системы ABAC включают в себя возможности RBAC, поэтому вы можете получить мощь условных политик в сочетании с гибкостью иерархий. Они также включают гораздо более экспрессивные языки политик, нежели те, которые я создал здесь. Взгляните на XACML, чтобы получить представление о том, насколько выразительными могут быть политики ABAC.

Если вы когда-либо занимались управлением доступом к программным системам, вы почти наверняка использовали те или иные версии ABAC и RBAC, поэтому эти механизмы должны быть вам знакомы. Суть кодирования отдельных элементов данной логики заключается в том, чтобы заложить основу для изучения управления доступом на основе целей (PBAC), которое требует возможностей ABAC в создании условных ролей. Ищите подробное описание PBAC в одном из следующих постов.

В заключение приглашаем всех на бесплатный урок курса Python Developer. Profeddional, где обсудим моки и границы их применимости, high и low gear тестирование, а также особенности дизайна тестов

Комментарии (5)


  1. masai
    08.11.2022 17:54
    +1

    Explained using Python в оригинальном заголовке — это причастие, то есть дословно означает объяснённая с помощью Python. Страдательный залог в прошедшем времени тут никак не получить.


  1. AlexSpaizNet
    08.11.2022 20:10

    Мне интересно как люди имплементируют проверку доступа к документам/филдам в рипортах когда у нас many to many и гранулярность по id

    Объясню.


    Есть работник. Работнику разрешают доступ к определенномым ресурсам с определенными айдишниками. Например, работнику Worker_1 можно "видеть" листинги с айдишниками Listing_1, Listing_2, .... ,без ограничений. Worker_2 тоже может видеть те же листинги и другие. Мэни ту мэни...

    И есть GUI/API поиска всех листингов в системе. Продукт манагеры хотят что бы работнику показывались только листинги которые ему можно смотреть.

    Сегодня у нас это реализовано в лоб с фильтрацией в базе (монга). Что то вроде:


    allowedListingIds = permissions.getAllowedListingIds(workerId); // пермишены хранятся где то в редисе

    listingsCollection.find({...., _id: {$in [...allowedListingIds]};

    А теперь представьте когда на воркере 10k айдишников... перформанс гамно как в базе так и в коде.

    Не передавать айдишники в базу и фильтровать в рантайме - будет еще хуже...

    Вижу 2 решения

    1- переезд на sql и копировать пермишены в базу листингов, и джоинить на уровне базы

    2 - остаемся в монге, и дуплицируем листинг пер работник и фильтровать по listing.workerId

    Хранить список работников на листинге мы не можем потому что их может быть неограниченно количество и мы упремся в 16мб монги.

    В итоге база увеличится в разы + проблема множественных апдейтов...

    з.ы.

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


    1. virtualtoy
      09.11.2022 09:45

      Трудно советовать не зная деталей. Возможно, получится завести новую коллекцию WorkerListingRelation (с полями workerId, listingId), поддерживать ее в актуальном состоянии и делать запросы по ней.


      1. AlexSpaizNet
        09.11.2022 10:45

        Ну это то что я и предлагаю в решении на SQL. Переезжаем на sql и копируем пермишены в локальную базу по которой производится поиск данных с фильтрами по различным полям + определенному работнику можно видеть определенны листинги.

        В монге это ничего не дает потому что нет джоинов...

        Мой поинт в том что везде обсуждаются красивые решения по доступу по id к какому-то агрегату. Это сравнительно легко. Да, есть ньюансы, но если делать правильно можно разделить логику пермишенов и бизнес логику.

        Но вот как только нужно фильтровать сет данных, получается очень тяжелый кауплинг к пермишенам если нам приходится копировать всегда релейшены в локальную базу... либо пермишены достаются из другого хранилища, и убивается перформанс когда передаются например 10K монговских айдишников в квери в виде $in: [...ids]


        1. virtualtoy
          09.11.2022 17:59

          Я предположил, что миграция на SQL для вас более трудоемкий вариант. В монге джойны можно делать или агрегациями или руками. Запросы вида $in: [...ids] вам в результате окажутся не нужны