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