Перейти к содержанию

AccessControl в Solidity: администраторы ролей

Разделение издателя и редактора полезно только тогда, когда понятно, кто назначает эти роли. В AccessControl у каждой роли есть административная роль. Участник с такой ролью вправе выдавать и отзывать соответствующее полномочие через внешний интерфейс.

Рассмотрим отдельную модель EditorialRoster. Она управляет составом редакции, но не содержит реестр курсов. Её задача — показать граф назначения, прежде чем соединить его с публикациями. Используем ту же закреплённую библиотеку OpenZeppelin Contracts 5.4.0.

Стрелка означает назначение

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

Добавим EDITOR_ADMIN_ROLE, который назначает издателей. Главный администратор назначает редакционных администраторов, а те — издателей. Получается двухуровневый граф:

DEFAULT_ADMIN_ROLE → EDITOR_ADMIN_ROLE
EDITOR_ADMIN_ROLE  → PUBLISHER_ROLE
DEFAULT_ADMIN_ROLE → DEFAULT_ADMIN_ROLE

Стрелка относится к grant и revoke, а не к праву выполнить publish напрямую. Участник редакционной административной роли не становится издателем сам от этой стрелки. Но он может назначить издательскую роль себе, если дополнительная политика это не запрещает. Поэтому косвенная власть важна так же, как прямые модификаторы функций.

Полная модель назначений

// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";

contract EditorialRoster is AccessControl {
    error InvalidAccount();
    bytes32 public constant EDITOR_ADMIN_ROLE = keccak256("EDITOR_ADMIN_ROLE");
    bytes32 public constant PUBLISHER_ROLE = keccak256("PUBLISHER_ROLE");

    constructor(address root, address editorAdmin) {
        if (root == address(0) || editorAdmin == address(0)) {
            revert InvalidAccount();
        }
        _grantRole(DEFAULT_ADMIN_ROLE, root);
        _grantRole(EDITOR_ADMIN_ROLE, editorAdmin);
        _setRoleAdmin(PUBLISHER_ROLE, EDITOR_ADMIN_ROLE);
    }
}

Здесь _setRoleAdmin выполняется в конструкторе. Мы не добавили общедоступный метод произвольной перестройки графа. Последнее действие меняет администратора именно PUBLISHER_ROLE. Администратор EDITOR_ADMIN_ROLE остаётся по начальному правилу главным.

До назначения издателя его роль пустая. Редакционный администратор может вызвать grantRole(PUBLISHER_ROLE,P). Root не проходит такую проверку непосредственно, если не имеет EDITOR_ADMIN_ROLE. Однако root способен выдать эту роль себе, а затем назначить P. Это показывает, почему таблица только прямых вызовов может недооценивать власть.

Читаем граф по текущему состоянию

getRoleAdmin(role) возвращает идентификатор административной роли. hasRole(adminRole,address) сообщает, относится ли адрес к ней. Для ответа «может ли A назначить издателя» нужно соединить оба вопроса, а не считать, что любой главный администратор всегда напрямую проходит любую проверку.

При изменяемом графе эти чтения должны относиться к понятному состоянию. Наш пример задаёт граф конструктором и не предоставляет внешнюю перестройку, поэтому соответствующее правило стабильно через его интерфейс. Но набор участников каждой роли может меняться после grant и revoke.

Субъект Прямо назначает publisher Способен получить такой маршрут
EditorAdmin Да Уже имеет административную роль
Только Root Нет по текущему модификатору Может назначить себе EditorAdmin
Только Publisher Нет Не имеет административного маршрута
Читатель Нет Не имеет разрешения назначений

Циклы и потеря управления

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

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

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

Граф и политика организации

Если издатель не должен назначать себе дополнительную роль, одной константы недостаточно. Требование может обеспечиваться более строгой моделью назначения или управлением административного субъекта через мультиподпись. Код AccessControl представляет проверяемые связи, но не знает должностей и конфликтов интересов людей.

Не стоит называть root «только техническим» при возможности назначать все ветви. Его прямой доступ к publish может отсутствовать, но конечный результат доступен через назначение ролей. Такое описание влияния важно и для читателя материала, и для будущего отчёта безопасности.

События RoleGranted, RoleRevoked и RoleAdminChanged помогают внешнему наблюдению. Однако обычный AccessControl не выдаёт весь список участников автоматически. Панель управления использует известные адреса и индекс событий, показывая актуальность своих сведений. По одному имени роли невозможно восстановить полный состав.

Разберём изменение условия

Пусть editorAdmin потерял возможность отправлять действия. Root назначает нового M в EDITOR_ADMIN_ROLE, после чего M выдаёт издательскую роль нужному адресу. Hash курса от этих назначений сам не меняется. Управление составом и публикация материала остаются отдельными действиями.

Если root тоже потерян, в этом простом примере восстановление не обещано. Нельзя включить в панель кнопку, которая якобы обходит граф через серверную сессию. Требуется либо заранее предусмотренная другая модель, либо новый экземпляр с понятным переходом.

Мы получили карту прямых и косвенных полномочий. Следующий урок рассмотрит отзыв роли и самостоятельный отказ, чтобы изменение состава было описано с той же точностью. Исходник AccessControl 5.4.0 фиксирует getRoleAdmin, grantRole и внутреннюю настройку графа.

Граф для отдельного экземпляра

Два EditorialRoster могут использовать одинаковые константы ролей, но иметь разный состав. Хеш строки PUBLISHER_ROLE не является глобальным правом Ethereum. Это ключ проверки внутри конкретного состояния. Участник, назначенный в первом реестре, не получает право во втором от совпадения идентификатора.

Поэтому отчёт панели включает адрес экземпляра рядом с role и account. При переключении сети кэш состава также меняется. Иначе старый список редакторов будет выглядеть действующим там, где никакие назначения не выполнялись.

Также проверьте первоначальный root. Если он не назначил editorAdmin, соответствующая ветвь может быть пустой. В показанном конструкторе адрес задан явно. Удаление последнего EditorAdmin ещё не обязательно окончательно останавливает ветвь: Root способен назначить нового. Но после потери Root тот же исход может стать невосстановимым по интерфейсу. Одно действие имеет разные последствия при разных составах.

Для самостоятельного уточнения: AccessControl.sol (5.4.0).

Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.

Оглавление серии