Files
MokoSuiteCross/source/packages/com_mokojoomcross/src/Controller/ServiceController.php
T
Jonathan Miller 9484d6bde9
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
security: fix 9 security and critical bugs (#107-#115, #120)
- #107: Fix testConnection() broken event dispatch (Joomla 5+
  ArrayAccess pattern) and add CSRF + ACL checks
- #108: Add CSRF checkToken() to OauthController::authorize()
- #109: Add core.manage ACL check to REST dispatch endpoint
- #110: Fix LinkedIn null-coalesce on organization_id
- #111: Add CURLOPT_PROTOCOLS to webhook, mastodon, ghost, bluesky
  to prevent SSRF via user-controlled URLs
- #112: Encrypt credentials at rest using sodium_crypto_secretbox
  with key derived from Joomla secret; backward-compat with
  existing plaintext JSON credentials
- #113: Fix unclosed <script> tag in dashboard template
- #114: Fix hasPendingWork() to use exponential backoff matching
  processQueue() instead of linear delay
- #115: Fix timestamp lock TOCTOU race with atomic UPDATE + WHERE
- #120: Add CSRF token to dashboard migration link

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 09:33:12 -05:00

105 lines
3.5 KiB
PHP

<?php
/**
* @package MokoJoomCross
* @subpackage com_mokojoomcross
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Joomla\Component\MokoJoomCross\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\FormController;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Response\JsonResponse;
use Joomla\Component\MokoJoomCross\Administrator\Service\MokoJoomCrossServiceInterface;
class ServiceController extends FormController
{
/**
* Test connection to a service by validating its credentials.
*
* @return void
*/
public function testConnection(): void
{
$this->checkToken();
if (!$this->app->getIdentity()->authorise('core.manage', 'com_mokojoomcross')) {
throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
}
$app = $this->app;
$id = (int) $this->input->getInt('id', 0);
try {
if ($id <= 0) {
throw new \RuntimeException(Text::_('COM_MOKOJOOMCROSS_TEST_CONNECTION_NO_SERVICE'));
}
// Load the service record
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__mokojoomcross_services'))
->where($db->quoteName('id') . ' = ' . $id);
$db->setQuery($query);
$service = $db->loadObject();
if (!$service) {
throw new \RuntimeException(Text::_('COM_MOKOJOOMCROSS_TEST_CONNECTION_NOT_FOUND'));
}
// Get service plugins via dispatcher (Joomla 5+ Event ArrayAccess pattern)
PluginHelper::importPlugin('mokojoomcross');
$servicePlugins = [];
$event = new \Joomla\Event\Event('onMokoJoomCrossGetServices', [$servicePlugins]);
$app->getDispatcher()->dispatch('onMokoJoomCrossGetServices', $event);
$idx = 1;
while (isset($event[$idx])) {
$servicePlugins[] = $event[$idx];
$idx++;
}
// Find the matching plugin
$plugin = null;
foreach ($servicePlugins as $sp) {
if ($sp instanceof MokoJoomCrossServiceInterface && $sp->getServiceType() === $service->service_type) {
$plugin = $sp;
break;
}
}
if (!$plugin) {
throw new \RuntimeException(Text::sprintf('COM_MOKOJOOMCROSS_TEST_CONNECTION_NO_PLUGIN', $service->service_type));
}
// Decode credentials and validate
$credentials = \Joomla\Component\MokoJoomCross\Administrator\Helper\CredentialHelper::decrypt($service->credentials ?: '');
$result = $plugin->validateCredentials($credentials);
$app->mimeType = 'application/json';
$app->setHeader('Content-Type', 'application/json; charset=utf-8');
echo new JsonResponse($result);
} catch (\Throwable $e) {
$app->mimeType = 'application/json';
$app->setHeader('Content-Type', 'application/json; charset=utf-8');
echo new JsonResponse($e);
}
$app->close();
}
}