Fi1osof 30 марта 2013 0 2
Продолжение недавней статьи о доступах к объектам. Там я писал:
Сразу отмечу, что большинство используемых методов рассчитаны только на работу уже с конечными объектами (что может повлиять на разницу подсчета строк в базе данных, и конечным числом полученных объектов). То есть при выборке записей из базы данных xPDO не формирует автоматически запрос так, чтобы исключить записи для тех объектов, к которым у пользователя нет доступов. xPDO сделает выборку всех записей, и только потом для каждой записи он постарается получить инстанс объекта в методе xPDOObject::_loadInstance().

Как оказалось, в этом моменте крылся глобальный пробел в системе MODX в плане выборки защищенных объектов. Попробую объяснить… Представьте, что у вас на сайте 10000 документов. 15 документов из этих 10000 относятся к приватным группам ресурсов, то есть должны быть доступны только определенным пользователям. Казалось бы, наша задача — просто сделать выборку этих 15-ти документов и все. И вот здесь epic fail… В xPDO в методах выборки getCollection() и т.п. не прописываются автоматом условия проверки на право load объекта. То есть xPDO сделает выборку всех записей, в потом по каждой из них попытается создать новый объект, и уже в этот момент он будет пытаться проверить на право доступа к объекту. Вот такой невеселый момент…
Есть еще пара неприятностей, но это уже не так принципиально. Самый пробел это именно с выборками, так как, к примеру, процессор подсчитал, что в БД есть 500 записей, удовлетворяющих условию, выбрал по лимиту 20 записей из БД, и когда попытался проинициировать конечные объекты, оказалось, что пользователь не имеет к ним доступа. И итоговая информация следующая: «всего 500 записей, но ничего не найдено для вас», хотя где-то в глубине таблицы есть записи объектов, к которым пользователь доступ имеет, просто ему не повезло с выборкой.

В общем все это печально, но не безнадежно. У нас же ООП :-) И если учесть, что мы не используем все методы работы с xPDO-объектами, то можно просто перегрузить некоторые методы, к примеру load, getCollection() и т.п., и в них добавить индивидуальные условия для SQL-запросов. Но надо учитывать то, что в большинстве случаев MODx оперирует с JSON-массивом наборов политик в таблице modx_access_policies, а это нам не позволит делать выборки на уровне SQL. В общем, сейчас ищу пути решения…

UPD: В общем я пришел к вот такому интересному способу: Проблема уже была озвучена чуть выше — настройки прав доступов хранятся в JSON-формате, и для выборки они нам вообще бесполезны. Но для тех классов, число записей для которых предполагается очень большое количество, у меня собственная таблица записей прав доступов. И так как эта таблица содержит записи только для моих классов, а на уровне выборки нас интересует только право «load» (то есть право получать инстанс объекта), то сам факт наличия записи в этой таблице уже с большой долей вероятности сигнализирует о том, что по объекту необходимо будет проверить права. А если для конкретного объекта нет записей в этой таблице, значит для него действует правило «не запрещено — значит разрешено всем». В таком случае нас интересует только три ситуации:

  1. Для конкретного объекта не существует записи в таблице прав доступов.
  2. Запись есть, но для нее есть настройки прав текущего пользователя.
  3. Запись есть, но для нее есть настройки прав групп пользователей текущего пользователя.

Кстати, да — в моей модели к элементам будут настраиваться как доступы для отдельных пользователей, так и для групп пользователей. Это очень важный момент. К тем же группам ресурсов нельзя дать доступ конкретному пользователю. Можно только создать группу пользователей и дать доступ для группы пользователей с какой-либо ролью, и чтобы конкретный пользователь получил доступ, его надо отнести к этой группе пользователей. И получается, что если 1000 пользователей захотят дать доступ другой 1000 пользователей к каким-то уникальным элементам, надо будет создать 1000 групп пользователей. Вообще не айс. А так можно или дать доступ группе пользователей (и все пользователи этой роли получат разрешенные доступы к этому элементу, если роль позволяет), или можно просто дать доступ пользователю (опять же с указанием роли), и тогда уже будут проверяться доступы конкретного пользователя. И вот это вообще ураган! К примеру, есть крупный проект, над которым предполагается командная работа нескольких специалистов (дизайнер, верстальщик, программист и т.п.). Для удобства можно создать группу проекта, и занести всех пользователей в эту группу. Но с разными ролями (пусть это так же будет дизайнер, верстальщик, программист и т.п.). Так вот, допустим, все эти пользователи из группы будут иметь права на просмотр тикетов (ведь вроде все в одной коляске), и еще у них будет собственная закрытая ветка на форуме. И все, кто в группе, те имеют эти доступы (доступ группы). При этом программисты имеют еще какие-то права, и верстальщики. Но решили взять еще одного верстальщика. И вот хотя для него роль «верстальщик» лучше всего подходит (у него есть доступ к спецресурсам для верстальщиков), доступ к своей ветке форума ему не хочется давать (это только для совсем своих). Так вот, можно его не в группу с ролью «верстальщик» добавить, а просто дать доступ как для отдельного пользователя с ролью верстальщика.

Но для того, чтобы эта выборка работала правильно, мне надо добавлять определенные условия в выборку. Можно было бы конечно перегрузить метод loadCollection(), но по ряду причин я не стал этого делать. Вместо это я создал статический метод addCheckListConditions в самом классе ModzillaAccess, так как все мои классы в большинстве случаев для хранения настроек доступов будут использовать именно этот его. Вот код:
public static function addCheckListConditions(xPDO & $xpdo, xPDOCriteria $criteria){
    if(empty($criteria->hasCheckListConditions)){
        if(!$xpdo->user->get('sudo')){
            $criteria->leftJoin('ModzillaAccess', 'Accesses');
            $criteria->leftJoin('modUserGroupMember', 'membergroup', "Accesses.principal_class='modUserGroup' AND Accesses.principal = membergroup.user_group");
            $criteria->where(array(
                'Accesses.id' => null,    
            ));
            $criteria->orCondition(array(
                "Accesses.principal_class" => "modUser",
                "AND:Accesses.principal:="    => $xpdo->user->get('id'),
            ));
        }
        $criteria->hasCheckListConditions = true;
    }
    return $criteria;
}  
Тонкости используемых здесь условий писал в прошлом топике.

Вот теперь если я хочу загрузить коллекцию объектов ModzillaProject с учетом прав ModzillaAccess, я делаю так:
//  Создаем новый запрос
$q = $modx->newQuery('ModzillaProject');
// Добавляем правила проверки в запрос
ModzillaAccess::addCheckListConditions($modx, $q);
// Если хотим, считаем количество записей
$total = $modx->getCount('ModzillaProject', $q);
// Получаем коллекцию объектов
$projects = $modx->getCollection('ModzillaProject', $q);
И все.

Но еще вопрос: все ли на этом? Это все проверки? Нет, не все :-) Как говорится «стрижка только началась»…
Напоминаю, что в методе modAccessibleObject::loadCollection() сначала происходит выборка записей из БД, потом для каждой записи выполняется попытка создания объекта с проверкой прав пользователя на этот объект. Записи с фильтром мы выбрали. Теперь нам еще предстоит выполнить проверку прав на создание конечных объектов. Этот механизм описывался здесь: modxclub.ru/blog/dokumentatsiya-dlya-spetsialistov/26.html

У нас это не ломается. То есть нам предстоит только прописать метод поиска настроек доступов в методе findPolicy();
2 комментария
ilyautkin1
ilyautkin 30 марта 2013г в 19:38 #
Спасибо! Очень интересно. Я в своем проекте только-только к этому подбираться начал, эта статья будет хорошим подспорьем)

У меня как раз к обращению и к контрагенту по умолчанию имеют доступ пользователи одной организации, но каждый отдельный объект (обращение, контрагент) может передаваться в другую организацию, но с меньшими правами… Или вообще — только какому-то одному пользователю дать доступ (например, для чтения)… И вот тут статья будет очень кстати. Буду на практике разбираться плотнее)))
Fi1osof1
Fi1osof 30 марта 2013г в 20:09 #
Джейсон ответил в скайп по этому поводу:
your are absolutely correct about the permissions...we need a way to query it rather than depend on checks at hydration time...can you enter a tracker ticket where we can start a discussion on implementation to share publicly?

Будем обсуждать, развивать. Наверняка появятся какие-то новые полезные методы в самом xPDO.
Авторизуйтесь или зарегистрируйтесь (можно через соцсети ), чтобы оставлять комментарии.