У вас нет разрешения на доступ к API. OpenCart

2021.02.01
Экзотическая проблема У вас нет разрешения на доступ к API с OpenCart 2.3 и ее тривиальное решение на примере одного из клиентов

Как правило проблема с доступом к API OpenCart возникает когда не настроен доступ по API.

IP адрес еще не добавлен в список разрешенных для доступа по API

Для решения этой проблемы нужно пройти в админке в Система-Пользователи-API, зайти в нужный объект списка и добавить свой IP адрес в список.

Добавляем свой IP адрес в список разрешенных для доступа по API

Либо на странице, с сообщением о проблеме с API просто нажать на кнопку Добавить IP-адрес и обновить страницу.

Но у нашего клиента на OpenCart 2.3 было не все так просто ...

Случай клиента

Перед началом разработки модуля клиент сообщил мне, что в админке на странице редактирования заказа у него часто/рандомно не работает изменение заказа: У вас нет разрешения на доступ к API!

А как позже выяснилось, такая проблема у клиента на 2 сайтах, где используется одна и та же версия OpenCart 2.3, сайты размещены у одного и того же хостера.

Сообщение которое часто вылезало на странице редактирования заказа

Изначально я не придал этому значения, так как проблема у клиента была до меня, а сроки по разработке были крайне сжатые (как обычно), а значит ее решение это второстепенная задача, однако на этапе внедрения модуля в интернет-магазин клиента начались проблемы.

Дело в том, что интерфейс функциональности разработанного мной модуля располагался на странице редактирования заказа, во вкладке Товары, а из-за проблем доступа к API клиент не мог проверить работу модуля. Но это была первая часть проблемы. Как выяснилось позже мой "модуль не мог корректно функционировать", так как использовал доступ к заказу основываясь на API.

Первым делом я перепроверил ajax запросы на корретность работы, как на клиенте так и на сервере. Токен на клиенте есть, проверка токена и авторизация по токену на сервере есть. Все также как и у API запросов, но не работает ...

В чем проблема?

Посмотрим контроллер catalog/controller/api/login.php (запрос получения токена для работы с API /index.php?route=api/login), в случае валидного API key и наличия в этой группе IP адреса выполняющего (того кто делает запрос), данный запрос стартует новую сессию с именем api с единственным ключом api_id:

$session_id_new = $this->session->createId();
$this->session->start('api', $session_id_new);
$this->session->data['api_id'] = $api_info['api_id'];

Затем посмотрим catalog/controller/startup/session.php, это первичный контроллер, который запускается при любом запросе в catalog, до основного контроллера). Здесь при наличии токена полученного в предыдущем запросе происходит старт сессии с именем api:

$this->session->start('api', $query->row['session_id']);

А теперь пройдем в файл с классом сессии system/library/session.php и смотрим метод start:

public function start($key = 'default', $value = '') {
if ($value) {
	$this->session_id = $value;
} elseif (isset($_COOKIE[$key])) {
	$this->session_id = $_COOKIE[$key];
} else {
	$this->session_id = $this->createId();
}	

if (!isset($_SESSION[$this->session_id])) {
	$_SESSION[$this->session_id] = array();
}

$this->data = &$_SESSION[$this->session_id];

//...
}

На основании предыдущих файлов можно сказать: session_id может быть взят из куки api, а данные сессии можно получить по session_id.

Вспоминаем что API запросы OpenCart проверяют валидность доступа в catalog контекст по токену следующим образом:

if (!isset($this->session->data['api_id'])) {
  $json['error']['warning'] = $this->language->get('error_permission');
} else {
  ...
}

И приходим к понимаю того, что с сессией проблемы, хотя судя по коду должно быть все ровно.

Для того чтобы понять в чем проблема, можно попробовать записывать данные $_SESSION в файл в запросе получения токена (после его получения), и при первом API запросе в файле catalog/controller/startup/session.php прямо перед или после старта api сессии.

В итоге я увидел что:

Сессия с session_id создается на этапе авторизации и в нее записывается один единственный ключ api_key, но уже при следующем запросе к API, массив данных сессии с этим session_id пуст, но заполняется при отработке всех контроллеров указанных в массиве action_pre_action (в файле system/config/catalog.php) и данные сессии сохраняются. Однако при этом в нем отсутствует ключ api_id, без которого дальнейшая работа с API невозможна и поэтому мы видим сообщение: У вас нет разрешения на доступ к API!.

Решение проблем

Почему возникает данная проблема мне не удалось выяснить. Развернув точную копию сайта клиента у себя на локальном сервере, воспроизвести проблему не получилось.

После обращения клиента в ТП хостинга, проблема исчезла на некоторое время (в это же время я пытался решить ее самостоятельно, но безуспешно, ибо не воспроизводилось), а потом благополучно, проблема вернулась.

Повторный дебаг $_SESSION не дал результатов, все также: при запросе авторизации создавалась новая сессия с ключом api_id, а при следующем обращении к API эта новая сессия была пуста.

Понимая что данные сессии не могут сохранится в $_SESSION при запросе авторизации снова смотрим catalog/controller/startup/session.php и видим запрос к БД:

$query = $this->db->query("SELECT DISTINCT * FROM `" . DB_PREFIX . "api` `a` LEFT JOIN `" . DB_PREFIX . "api_session` `as` ON (a.api_id = as.api_id) LEFT JOIN " . DB_PREFIX . "api_ip `ai` ON (as.api_id = ai.api_id) WHERE a.status = '1' AND as.token = '" . $this->db->escape($this->request->get['token']) . "' AND ai.ip = '" . $this->db->escape($this->request->server['REMOTE_ADDR']) . "'");

Если этот запрос возвращает не пустой массив, значит можно считать что авторизация прошла успешно, а среди выбранных из БД данных есть api_id.

В catalog/controller/startup/session.php сразу после старта сессии пишем:

if ($query->num_rows) {
	$this->session->start('api', $query->row['session_id']);

	//это может решить проблему с доступом по апи
	$this->session->data["api_id"] = $query->row["api_id"];
	
	//...
}

Для решения проблемы У вас нет разрешения на доступ к API! в данном случае достаточно после старта сессии в catalog/controller/startup/session.php вставить в массив сессии ключ api_id: $this->session->data["api_id"] = $query->row["api_id"];

Для убедительности я провел тест: получил ошибку У вас нет разрешения на доступ к API!, а затем применил описанное выше решение и не перезагружая страницы (где была ошибка) провел несколько ajax запросов к API OpenCart, которые прошли успешно.

Итог

Клиент доволен, оба сайта работают, проблема решена.

В телеграм канале DevOps от первого лица можно оставить комментарий или почитать интересные истории из практики DevOps