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

Phalcon\Acl предоставляет простое и легкое управление списками контроля доступа, а также разрешениями, назначаемыми этим спискам. Списки управления доступом (ACL) позволяют приложению управлять доступом к различным своим частям и запрошенным объектам. Рекомендуется ознакомится с ACL подробнее, чтобы понимать принцип работы и основные понятия.

В целом, ACL основано на таких понятиях как роли и ресурсы. Ресурсами являются объекты, на которые накладываются определенные разрешения с помощью ACL. Роли — это объекты, которые запрашивают доступ к ресурсам, который может быть разрешен или запрещен ACL механизмом.

Создание списков контроля доступа

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

<?php

use Phalcon\Acl\Adapter\Memory as AclList;

$acl = new AclList();

По умолчанию Phalcon\Acl разрешает доступ к действию над ресурсом, которое еще не было определенно в ACL. Для повышения уровня безопасности списка доступа к мы можем определить уровень deny как уровень доступа по умолчанию.

<?php

use Phalcon\Acl;

// Действие по умолчанию: запретить доступ
$acl->setDefaultAction(
    Acl::DENY
);

Добавление ролей к ACL

Ролью является объект, который имеет или не имеет доступа к определенному ресурсу в списке доступа. Для примера, мы определим роли людей в организации. Класс Phalcon\Acl\Role позволяет создать роли в более структурированной форме. Давайте добавим несколько ролей в наш недавно созданный список:

<?php

use Phalcon\Acl\Role;

// Создаем роли.
// Первый параметр это название роли, второй параметр необязателен - это описание роли.
$roleAdmins = new Role('Administrators', 'Super-User role');
$roleGuests = new Role('Guests');

// Добавляем 'Guests' в список ACL
$acl->addRole($roleGuests);

// Добавляем 'Designers' без класса Phalcon\Acl\Role
$acl->addRole('Designers');

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

Добавление ресурсов

Ресурсами являются объекты, доступ к которым контролируется. Обычно в MVC приложениях ресурсы относятся к контроллерам. Хотя это не является обязательным, класс Phalcon\Acl\Resource может быть использован при определении любых ресурсов. Важно добавить связующие действия или операции над ресурсами, чтобы ACL мог понимать, что ему нужно контролировать.

<?php

use Phalcon\Acl\Resource;

// Определяем ресурс 'Customers'
$customersResource = new Resource('Customers');

// Добавим ресурс 'Customers' с несколькими операциями

$acl->addResource(
    $customersResource,
    'search'
);

$acl->addResource(
    $customersResource,
    [
        'create',
        'update',
    ]
);

Определение контроля доступа

Теперь у нас есть роли и ресурсы. Настало время указать для ACL, какие роли имеют разрешения доступа к ресурсам. Этот этап очень важен, особенно принимая во внимание используемый по умолчанию уровень allow или deny.

<?php

// Указываем уровень доступа для ролей на определенный ресурс

$acl->allow('Guests', 'Customers', 'search');

$acl->allow('Guests', 'Customers', 'create');

$acl->deny('Guests', 'Customers', 'update');

Метод allow() указывает, что определенная роль предоставила доступ к определенному ресурсу. Метод deny() делает обратное.

Запросы к ACL

После того, как список был полностью определен. Мы можем запросить его, чтобы проверить, имеет ли роль данное разрешение или нет.

<?php

// Проверяем, имеет ли роль доступ к операциям

// Вернёт 0
$acl->isAllowed('Guests', 'Customers', 'edit');

// Вернёт 1
$acl->isAllowed('Guests', 'Customers', 'search');

// Вернёт 1
$acl->isAllowed('Guests', 'Customers', 'create');

Доступ на основе функций

Также, вы можете добавить четвертым параметром, вашу собственную функцию, которая должна возвращать булево значение. Она будет вызвана каждый раз при использовании метода isAllowed(). Если ваша функция должна принимать значения — передайте в качестве 4-го агрумента ассоциативный массив в метод isAllowed(), где ключом является название параметра в пользовательской функции.

<?php

// Установим уровень доступа к ресурсу для роли, используя пользовательскую функцию
$acl->allow(
    'Guests',
    'Customers',
    'search',
    function ($a) {
        return $a % 2 === 0;
    }
);

// Теперь проверим, есть ли у роли доступ к операции

// Вернёт true
$acl->isAllowed(
    'Guests',
    'Customers',
    'search',
    [
        'a' => 4,
    ]
);

// Вернёт false
$acl->isAllowed(
    'Guests',
    'Customers',
    'search',
    [
        'a' => 3,
    ]
);

Следует понимать, если ваша функция принимает аргументы и вы не передаёте какие-либо параметры в метод isAllowed(), то поведением по умолчанию является Acl::ALLOW. Вы можете изменить это поведение с помощью метода setNoArgumentsDefaultAction().

<?php

use Phalcon\Acl;

// Установим уровень доступа к ресурсу для роли, используя пользовательскую функцию
$acl->allow(
    'Guests',
    'Customers',
    'search',
    function ($a) {
        return $a % 2 === 0;
    }
);

// Теперь проверим, есть ли у роли доступ к операции

// Вернёт true
$acl->isAllowed(
    'Guests',
    'Customers',
    'search'
);

// Изменим значение по умолчанию если не переданы аргументы
$acl->setNoArgumentsDefaultAction(
    Acl::DENY
);

// Вернёт false
$acl->isAllowed(
    'Guests',
    'Customers',
    'search'
);

Объекты в качестве названия роли и ресурса

Вы можете использовать свои классы в качестве объектов роли или ресурса и передавать экземпляры объектов в аргументах roleName и resourceName. Ваши классы должны реализовывать интерфейс Phalcon\Acl\RoleAware для roleName и Phalcon\Acl\ResourceAware для resourceName.

Пример пользовательского класса UserRole:

<?php

use Phalcon\Acl\RoleAware;

// Создадим класс, который будет использоваться как roleName
class UserRole implements RoleAware
{
    protected $id;

    protected $roleName;

    public function __construct($id, $roleName)
    {
        $this->id       = $id;
        $this->roleName = $roleName;
    }

    public function getId()
    {
        return $this->id;
    }

    // Реализуем интерфейс Phalcon\Acl\RoleAware
    public function getRoleName()
    {
        return $this->roleName;
    }
}

Реализуем ещё один наш класс ModelResource:

<?php

use Phalcon\Acl\ResourceAware;

// Создадим класс, который будет использоваться в качестве как resourceName
class ModelResource implements ResourceAware
{
    protected $id;

    protected $resourceName;

    protected $userId;

    public function __construct($id, $resourceName, $userId)
    {
        $this->id           = $id;
        $this->resourceName = $resourceName;
        $this->userId       = $userId;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getUserId()
    {
        return $this->userId;
    }

    // Реализуем интерфейс Phalcon\Acl\ResourceAware
    public function getResourceName()
    {
        return $this->resourceName;
    }
}

Теперь мы можем использовать эти классы в методе isAllowed().

<?php

use UserRole;
use ModelResource;

// Задаем уровень доступа для ролей на определенный ресурс
$acl->allow('Guests', 'Customers', 'search');
$acl->allow('Guests', 'Customers', 'create');
$acl->deny('Guests', 'Customers', 'update');

// Создадим экземпляры наших классов для roleName и resourceName

$customer = new ModelResource(
    1,
    'Customers',
    2
);

$designer = new UserRole(
    1,
    'Designers'
);

$guest = new UserRole(
    2,
    'Guests'
);

$anotherGuest = new UserRole(
    3,
    'Guests'
);

// Проверяем, имеют ли наши объекты ролей доступ к разным операциям
// по отношению к ресурсу

// Вернёт false
$acl->isAllowed(
    $designer,
    $customer,
    'search'
);

// Вернёт true
$acl->isAllowed(
    $guest,
    $customer,
    'search'
);

// Вернёт true
$acl->isAllowed(
    $anotherGuest,
    $customer,
    'search'
);

Также вы можете получить доступ к этим объектам в вашей пользовательской функции в allow() или deny(). Они автоматически связываются с параметрами по типу функции.

<?php

use UserRole;
use ModelResource;

// Установим уровень доступа с пользовательской функцией
$acl->allow(
    'Guests',
    'Customers',
    'search',
    // Необходимые классы User и Model
    function (UserRole $user, ModelResource $model) {
        return $user->getId == $model->getUserId();
    }
);

$acl->allow(
    'Guests',
    'Customers',
    'create'
);

$acl->deny(
    'Guests',
    'Customers',
    'update'
);

// Создадим экземпляры наших классов для roleName и resourceName

$customer = new ModelResource(
    1,
    'Customers',
    2
);

$designer = new UserRole(
    1,
    'Designers'
);

$guest = new UserRole(
    2,
    'Guests'
);

$anotherGuest = new UserRole(
    3,
    'Guests'
);

// Проверяем, имеют ли наши объекты ролей доступ к разным операциям
// по отношению к ресурсу

// Вернёт false
$acl->isAllowed(
    $designer,
    $customer,
    'search'
);

// Вернёт true
$acl->isAllowed(
    $guest,
    $customer,
    'search'
);

// Вернёт false
$acl->isAllowed(
    $anotherGuest,
    $customer,
    'search'
);

Вы по-прежнему можете добавлять любые пользовательские параметры в функцию и передавать ассоциативный массив в метод isAllowed(). Также порядок не имеет значения.

Наследование ролей

Вы можете строить сложные структуры ролей используя наследование, которое предоставляет класс Phalcon\Acl\Role. Роли могут наследовать доступ других ролей, таким образом предоставляя доступ к надмножествам или подмножествам ресурсов. Наследование ролей можно использовать двумя способами:

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

<?php

use Phalcon\Acl\Role;

// ...

// Создаём некоторые роли

$roleAdmins = new Role('Administrators', 'Super-User role');

$roleGuests = new Role('Guests');

// Добавляем роль 'Guests' в ACL
$acl->addRole($roleGuests);

// Добавляем роль 'Administrators' наследуемую от роли 'Guests'
$acl->addRole($roleAdmins, $roleGuests);

Отношения можно настроить после добавления ролей

<?php

use Phalcon\Acl\Role;

// Create some roles
$roleAdmins = new Role('Administrators', 'Super-User role');
$roleGuests = new Role('Guests');

// Добавление ролей в ACL
$acl->addRole($roleGuests);
$acl->addRole($roleAdmins);

// Иметь роль 'Administrators' наследуют от 'Guests' его доступы
$acl->addInherit($roleAdmins, $roleGuests);

Сериализация ACL списков

Чтобы увеличить производительность, объект Phalcon\Acl можно сериализовать для хранения в APC, сессии, текстовых файлах или в базе данных. Таким образом, список доступа возможно повторно использовать, без необходимости переобъявлять его каждый раз. Вы можете сделать это следующим образом:

<?php

use Phalcon\Acl\Adapter\Memory as AclList;

// ...

// Проверяем, существуют ли ACL данные
if (!is_file('app/security/acl.data')) {
    $acl = new AclList();

    // ... Определяем роли, ресурсы, доступ и т.д.

    // Сохраняем сериализованный объект в файл
    file_put_contents(
        'app/security/acl.data',
        serialize($acl)
    );
} else {
    // Восстанавливаем ACL объект из текстового файла
    $acl = unserialize(
        file_get_contents('app/security/acl.data')
    );
}

// Используем ACL
if ($acl->isAllowed('Guests', 'Customers', 'edit')) {
    echo 'Доступ разрешен!';
} else {
    echo 'Доступ запрещен :(';
}

Рекомендуется использовать адаптер Memory в процессе разработки, но использовать любой другой адаптер в процессе эксплуатации вашего приложения.

События

Phalcon\Acl может отправлять события в EventsManager если он существует. События срабатывают используя тип 'acl'. Некоторые события могут возвращать false, чтобы прервать текущую операцию. Поддерживаются следующие типы событий:

Название событияСрабатываетМожно остановить операцию?
beforeCheckAccess Срабатывает перед проверкой доступа роли/ресурса Да
afterCheckAccess Срабатывает после проверки доступа роли/ресурса Нет

В следующем примере показано, как назначить слушателей к компоненту:

<?php

use Phalcon\Acl\Adapter\Memory as AclList;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;

// ...

// Создаем менеджер событий
$eventsManager = new EventsManager();

// Назначаем слушателя к типу 'acl'
$eventsManager->attach(
    'acl:beforeCheckAccess',
    function (Event $event, $acl) {
        echo $acl->getActiveRole();

        echo $acl->getActiveResource();

        echo $acl->getActiveAccess();
    }
);

$acl = new AclList();

// Настраиваем $acl
// ...

// Присваиваем менеджер событий компоненту ACL
$acl->setEventsManager($eventsManager);

Реализация собственных адаптеров

Для создания своего адаптера необходимо реализовать интерфейс Phalcon\Acl\AdapterInterface, или использовать наследование от существующего адаптера.