А вот этот баг — один из самых сложных и злостных, с которым я сталкивался в MODX-е… На официальном форуме вопрос задали уже более двух месяцев назад (хотя я столкнулся гораздо раньше), и до сих пор нет четкого ответа на него. Но вот вчера у меня опять вылезла эта бяка на сайте, и я тогда сел основательно ее копать. Суть проблемы заключается в том, что как бы админка вся работает и все клева, но при сохранении документа получаем сообщение Access denied (или Доступ запрещен, в зависимости от языка админки). При чем самое интересное в том, что при сохранении документа через быстрое редактирование документ сохраняется, все ОК, а при редактировании в полном редакторе у нас ошибка доступа.
Собственно, за 3 часа я получил боле менее вменяемый ответ на природу этого феномена, но самого решения у меня сейчас нет. Тем не менее под катом вас ждет перевод этого ответа и описание самых интересных моментов.
Итак, когда я начал ковырять все это дело, я обратил внимание на то, что на сервер передается HTTP_MODAUTH='0', то есть ключ нулевой (именно нулевой, а не отсутствующий). То есть это и не полный ключ сессии, но и не отсутствие его. То есть с натяжкой можно считать, что ключ сессии есть. НО: при запросе на update документа из быстрого редактора документ нормально сохраняется, а при запросе на update из полного редактора у нас Access denied. При этом в обоих запросах присутствует HTTP_MODAUTH='0' (здесь и далее мы будем рассматривать только эти два запроса (быстрый и полные редактор), так как их принцип примерно общий, а результат разный). Так в чем же тогда фокус? На этот вопрос я получил ответ, когда более внимательно изучил modConnectorResponse. Вот самый интересный кусочек кода:
if (!$isLogin && !isset($_SERVER['HTTP_MODAUTH']) && (!isset($_REQUEST['HTTP_MODAUTH']) || empty($_REQUEST['HTTP_MODAUTH']))) { $this->responseCode = 401; $this->body = $modx->error->failure($modx->lexicon('access_denied'),array('code' => 401)); }
И вот самое интересное мне не сразу в глаза кинулось, а именно переменные $_SERVER['HTTP_MODAUTH'] и $_REQUEST['HTTP_MODAUTH']. То есть ключи одинаковые HTTP_MODAUTH, а массивы разные $_SERVER и $_REQUEST. Думаю, многие понимают что это за массивы и в чем их отличие, поэтому не буду их объяснять. Но вот что интересней всего: $_SERVER['HTTP_MODAUTH'] нигде не объявляется. То есть во всем ядре MODX-а нет где-либо присвоения значения этой переменной. Но ведь она не может просто так появиться… Тогда я полез ковырять JS-файлы, и вот такой интересный кусочек нашел в core/modx.layout.js:
Ext.Ajax.defaultHeaders = { 'modAuth': config.auth }; Ext.Ajax.extraParams = { 'HTTP_MODAUTH': config.auth };
Ext.Ajax.extraParams — это параметры, передаваемые в запросе (по умолчанию для всех запросов). А вот Ext.Ajax.defaultHeaders — это заголовки запроса. И вот как раз их-то если специально искать не будешь, не увидишь. А они есть.
?
А увидеть этот параметр мы можем в коде страницы админки. К примеру:
<script type="text/javascript">Ext.onReady(function() { ...... MODx.load({xtype: "modx-layout",accordionPanels: MODx.accordionPanels || [], auth: "modx51b7a546bbe9a4.05164626_25223640a1da1e4.42369214"}); });</script>
Формируется он в modManagerController.
$siteId = $this->modx->user->getUserToken('mgr'); ........ MODx.load({xtype: "modx-layout",accordionPanels: MODx.accordionPanels || [],auth: "'.$siteId.'"});
modUser::getUserToken():
public function getUserToken($ctx = '') { if (empty($ctx)) $ctx = $this->xpdo->context->get('key'); return isset($_SESSION['modx.'.$ctx.'.user.token']) ? $_SESSION['modx.'.$ctx.'.user.token'] : ''; }
Вот он методом modUser::getUserToken() и получает токен контекста, который и участвует во всей этой цепочке. И что самое интересное, токен может быть и нулевым, лишь бы он был хоть каким-то, и везде имел одинаковое значение (и в $_SERVER['HTTP_MODAUTH'], и в $_REQUEST['HTTP_MODAUTH']). Тогда будет считаться, что пользователь корректно авторизован. И в большинстве случаев так и происходит. НО, в случае отправки запроса из полного редактора страницы этого не происходит. Просто потому что его механизм отправки данных отличается, и он не отправляет modAuth-заголовок на сервер. В результате чего переменная $_SERVER['HTTP_MODAUTH'] пустая, и мы имеем Action denied.
Каждый может проверить этот баг очень просто (на MODX Revolution 2.2.8 и выше). Просто выполните в Console (или другим способом) этот простой php-код:
$_SESSION['modx.mgr.user.token'] = '0';
То есть просто обнулите сессию в контексте mgr, после чего обновите страницу админки. Админка работать будет, но попробуйте обновить документ в полном редакторе, и скорее всего получите Access denied.
Резюме: в какой-то момент токен сессии обнуляется (по какой причине — я не знаю. У меня есть подозрение, что такое случается, когда ставишь галочку Запомнить, но в админку потом не заходишь несколько дней). Потом через несколько дней заходишь, и как бы авторизован, но вот с таким обнуленным токеном. И в итоге получается, что как бы авторизован, но не полноценно. И при этом ты и не не авторизован, то есть нет требования авторизоваться.
Такая же проблема решилась у меня последовательностью в админке:
1) Безопасность -> перезагрузить права доступа
2) Безопасность -> завершить все сеансы
Выбрасывает на страницу логина, заходим заново, проблема ушла
Это не решение проблемы в целом. Я описывал, что здесь проблема как раз в пустом ключе сессии. Конечно, завершив все сеансы (удалив записи сессий из БД) и авторизовавшись снова, у вас будет новая сессия с нормальным ключом. Но вообще не исключается вариант, что через некоторое время у вас опять будет пустой ключ в сессии, и опять возникнет эта же проблема.
Вот здесь я писал хоть и хардкорный, но все таки действенный фикс этой проблемы: forums.modx.com/thread/85173/after-upgrade-to-2-2-8-problem-access-denied-on-save?page=6#dis-post-476634
согласен!