Проверка прав на xPDO-объекты для нескольких пользователей

Материал для экспертов. В одном из топиков я уже писал довольно подробно про политики безопасности в MODX. Тогда мы разобрались, что основной метод проверки — modAccessibleObject::checkPolicy(). В этот раз мы попробуем более внимательно изучить этот механизм, и рассмотрим очень важную недоработку MODX в этом плане. Во-первых, сразу хочу отметить, что метод checkPolicy не содержится в исходных классах xPDO типа xPDOObject или xPDOSimpleObject. Этот метод появляется только в MODX-классе modAccessibleObject, который входит в сам пакет modx (да, не забываем, что MODX — это надстройка над xPDO и подключается как дополнительный пакет). То есть, если вы хотите использовать чистый xPDO, то имейте ввиду, что механизм политик безопасности там попросту отсутствует. А во-вторых, рассмотрим важную недоработку: отсутствие возможности выполнить проверку доступов к объекту сразу для нескольких пользователей. Простой пример: вот у нас здесь на сайте есть закрытые блоги, к которым имеют доступы только определенные группы пользователей. Встала задача — при публикации нового топика в блог, разослать уведомления всем пользователям, которые имеют доступ к блогу, в который публикуется топик. И вот вопрос: как это сделать? По логике, нам надо получить всех пользователей, перебрать их в цикле и каждого из них проверить на предмет доступа к объекту блога. Это по логике. А на практике? А на практике метод modAccessibleObject::checkPolicy() принимает только два параметра — criteriaиcriteria и targets. А внутри метода прописана работа с текущим пользователем MODX this>xpdo>user.Такимобразомпроверятьправамыможемтолькодлятекущегопользователя:)Хотяможнобылобыконечнопопробоватьчтотовродетакогоизврата:<?phpthis->xpdo->user. Таким образом проверять права мы можем только для текущего пользователя :) Хотя можно было бы конечно попробовать что-то вроде такого изврата: <?php modx->switchContext('web'); blogid=1;//IDнужногоблогаblog_id = 1; // ID нужного блога currentUser = modx>user;modx->user; blog = modx>getObject(SocietyBlog,modx->getObject('SocietyBlog', blog_id); foreach(modx>getCollection(modUser)asmodx->getCollection('modUser' ) as user){ modx>user=modx->user = user; print "\n
HasAccess: ". (int)blog->checkPolicy('view'); } modx->user = currentUser;ТоестьзагоняемтекущегоMODXпользователявпеременную,затемвциклеперегружаемтекущегопользователяипроверяемдоступкобъекту,послечегоужевозвращаемтекущегопользователявобъектcurrentUser; То есть загоняем текущего MODX-пользователя в переменную, затем в цикле перегружаем текущего пользователя и проверяем доступ к объекту, после чего уже возвращаем текущего пользователя в объект modx. Но это конечно же не по фэншую. Хотя просто так альтернативы нет… В нашем же случае с топиками и блогами есть вариант — это немного переписать метод checkPolicy() в наших пользовательских классах, добавив третий параметр — user.Кслову,пуллреквествMODXяужеотправил(смотритеизменениявкоде).Врезультатевсеполучаетсятак,какидолжнобыть:<?phpuser. К слову, пуллреквест в MODX я уже отправил (смотрите изменения в коде). В результате все получается так, как и должно быть: <?php modx->switchContext('web'); blogid=1;//IDнужногоблогаblog_id = 1; // ID нужного блога blog = modx>getObject(SocietyBlog,modx->getObject('SocietyBlog', blog_id); foreach(modx>getCollection(modUser)asmodx->getCollection('modUser' ) as user){ print "\n
HasAccess: ". (int)blog>checkPolicy(view,null,blog->checkPolicy('view', null, user); } Без всякой перегрузки текущего MODX-пользователя. Будем надеяться, что пуллреквест будет приниматься не особо долго. По поводу производительности: на холодную перебор 500 пользователей на предмет доступа к объекту на моем сервере занимает примерно 4-5 секунд. Далее уже значительно быстрее, в районе 0.2-0.3 сек. Здесь играет роль внутренний механизм кеширования доступов для каждого пользователя в отдельности, заложенный в MODX-е, но изучать детально я его буду чуть позже. Тогда и статью по этому поводу напишу отдельно. Опыт подсказывает, что там скрываются очень полезные механизмы, которые можно будет использовать для существенного снижения нагрузки на сайтах с распределенными правами доступов. И напоследок, приведу код метода SocietyTopic::checkPolicy(). Дело в том, что там метод checkPolicy еще чуть более измененный. Во-первых, права проверяются не только на сам топик, но и на доступы к блогам, в которых топик размещен (Да, топик может быть индивидуально ограничен в доступах. И да, топик может располагаться сразу в нескольких блогах. Тогда наличие доступа хотя бы к одному из блогов топика будет свидетельствовать о наличии прав на топик). <?php public function checkPolicy(criteria,criteria, targets = null, modUser user = null) { if(!user){ user = & this->xpdo->user; } // Проверяем права на блог (хотя бы один) hasBlogAccess=false;foreach((array)hasBlogAccess = false; foreach((array)this->TopicBlogs as topicblog){ // print_r(topicblog->Blog->toArray()); if( blog=blog = topicblog->Blog AND blog>checkPolicy(blog->checkPolicy(criteria, targets,targets, user) ){ hasBlogAccess = true; break; } } if(!hasBlogAccess){ return false; } if (criteria && this->xpdo instanceof modX && this->xpdo->getSessionState() == modX::SESSION_STATE_INITIALIZED) { if (user->get('sudo')) return true; if (!is_array(criteria) && is_scalar(criteria)) { criteria = array("{criteria}" => true); } policy=policy = this->findPolicy(); if (!empty(policy)) { // print "sdfdfd"; principal = user>getAttributes(user->getAttributes(targets); if (!empty(principal)) { foreach (policy as policyAccess=>policyAccess => access) { foreach (accessasaccess as targetId => targetPolicy) { foreach (targetPolicy as policyIndex=>policyIndex => applicablePolicy) { if (this>xpdo>getDebug()===true)this->xpdo->getDebug() === true) this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, 'target pk='. this>getPrimaryKey().;evaluatingpolicy:.printr(this->getPrimaryKey() .'; evaluating policy: ' . print_r(applicablePolicy, 1) . ' against principal for user id=' . user>id.:.printr(user->id .': ' . print_r(principal[policyAccess],1));policyAccess], 1)); principalPolicyData = array(); principalAuthority=9999;if(isset(principalAuthority = 9999; if (isset(principal[policyAccess][policyAccess][targetId]) && is_array(principal[principal[policyAccess][targetId])) { foreach (principal[policyAccess][policyAccess][targetId] as acl) { principalAuthority = intval(acl[authority]);acl['authority']); principalPolicyData = acl[policy];acl['policy']; principalId = acl[principal];if(acl['principal']; if (applicablePolicy['principal'] == principalId) { if (principalAuthority <= applicablePolicy['authority']) { if (!applicablePolicy['policy']) { return true; } if (empty(principalPolicyData))principalPolicyData)) principalPolicyData = array(); matches=arrayintersectassoc(matches = array_intersect_assoc(principalPolicyData, applicablePolicy[policy]);if(applicablePolicy['policy']); if (matches) { if (this>xpdo>getDebug()===true)this->xpdo->getDebug() === true) this->xpdo->log(modX::LOG_LEVEL_DEBUG, 'Evaluating policy matches: ' . print_r(matches,1));matches, 1)); matched = array_diff_assoc(criteria,criteria, matches); if (empty($matched)) { return true; } } } } } } } } } } return false; } } return true; } UPD: В итоге я отправил пулл-реквест, который все-таки приняли, и теперь можно проверять права для любых пользователей.