116896b584
Generic: Project CI / Tests (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 3s
Generic: Project CI / Lint & Validate (push) Successful in 11s
Update Server / Update Server (push) Successful in 11s
Completes the MokoJoomCross → MokoSuiteCross rebrand across all language string keys, Joomla event names, documentation, and wiki pages. - 1,151 language key references renamed (COM_, PLG_, PKG_ prefixes) - Event names renamed (onMokoJoomCross* → onMokoSuiteCross*) - CLAUDE.md, CHANGELOG.md, wiki docs updated - Zero mokojoomcross references remaining in codebase Closes #128, closes #138
199 lines
6.4 KiB
PHP
199 lines
6.4 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @package MokoSuiteCross
|
|
* @subpackage com_mokosuitecross
|
|
* @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\MokoSuiteCross\Administrator\Controller;
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
use Joomla\CMS\Factory;
|
|
use Joomla\CMS\Language\Text;
|
|
use Joomla\CMS\MVC\Controller\BaseController;
|
|
use Joomla\CMS\Plugin\PluginHelper;
|
|
use Joomla\CMS\Router\Route;
|
|
use Joomla\Component\MokoSuiteCross\Administrator\Helper\OAuthHelper;
|
|
|
|
/**
|
|
* OAuth controller for handling browser-based authorization flows.
|
|
*
|
|
* Endpoints:
|
|
* task=oauth.authorize — Initiate OAuth flow (redirect to platform)
|
|
* task=oauth.callback — Handle platform redirect with auth code
|
|
*/
|
|
class OauthController extends BaseController
|
|
{
|
|
/**
|
|
* Initiate OAuth authorization for a service.
|
|
*
|
|
* Expects: service_id (int) in request
|
|
*/
|
|
public function authorize(): void
|
|
{
|
|
$this->checkToken();
|
|
|
|
$serviceId = $this->input->getInt('service_id', 0);
|
|
|
|
if (!$serviceId) {
|
|
$this->setRedirect(
|
|
Route::_('index.php?option=com_mokosuitecross&view=services', false),
|
|
Text::_('COM_MOKOSUITECROSS_OAUTH_NO_SERVICE'),
|
|
'error'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
$db = \Joomla\CMS\Factory::getDbo();
|
|
|
|
$query = $db->getQuery(true)
|
|
->select('*')
|
|
->from($db->quoteName('#__mokosuitecross_services'))
|
|
->where($db->quoteName('id') . ' = ' . $serviceId);
|
|
|
|
$db->setQuery($query);
|
|
$service = $db->loadObject();
|
|
|
|
if (!$service) {
|
|
$this->setRedirect(
|
|
Route::_('index.php?option=com_mokosuitecross&view=services', false),
|
|
Text::_('COM_MOKOSUITECROSS_OAUTH_SERVICE_NOT_FOUND'),
|
|
'error'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// Get client ID from plugin params
|
|
PluginHelper::importPlugin('mokosuitecross');
|
|
$pluginParams = PluginHelper::getPlugin('mokosuitecross', $service->service_type);
|
|
$params = json_decode($pluginParams->params ?? '{}', true) ?: [];
|
|
|
|
$clientId = $params['client_id'] ?? '';
|
|
|
|
if (empty($clientId)) {
|
|
$this->setRedirect(
|
|
Route::_('index.php?option=com_mokosuitecross&view=services', false),
|
|
Text::sprintf('COM_MOKOSUITECROSS_OAUTH_NO_CLIENT_ID', ucfirst($service->service_type)),
|
|
'error'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// Generate CSRF nonce and store in session
|
|
$nonce = bin2hex(random_bytes(16));
|
|
Factory::getApplication()->getSession()->set('mokosuitecross.oauth_nonce', $nonce);
|
|
|
|
$url = OAuthHelper::getAuthorizeUrl($service->service_type, $serviceId, $clientId, $nonce);
|
|
|
|
if (!$url) {
|
|
$this->setRedirect(
|
|
Route::_('index.php?option=com_mokosuitecross&view=services', false),
|
|
Text::sprintf('COM_MOKOSUITECROSS_OAUTH_NOT_SUPPORTED', ucfirst($service->service_type)),
|
|
'error'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
$this->app->redirect($url);
|
|
}
|
|
|
|
/**
|
|
* Handle OAuth callback from platform.
|
|
*
|
|
* Expects: code (string), state (base64 JSON with service_id)
|
|
*/
|
|
public function callback(): void
|
|
{
|
|
$code = $this->input->getString('code', '');
|
|
$state = $this->input->getString('state', '');
|
|
$error = $this->input->getString('error', '');
|
|
|
|
if ($error) {
|
|
$this->setRedirect(
|
|
Route::_('index.php?option=com_mokosuitecross&view=services', false),
|
|
Text::sprintf('COM_MOKOSUITECROSS_OAUTH_PLATFORM_ERROR', $error),
|
|
'error'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
if (empty($code) || empty($state)) {
|
|
$this->setRedirect(
|
|
Route::_('index.php?option=com_mokosuitecross&view=services', false),
|
|
Text::_('COM_MOKOSUITECROSS_OAUTH_INVALID_CALLBACK'),
|
|
'error'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
$stateData = json_decode(base64_decode($state), true);
|
|
$serviceId = (int) ($stateData['service_id'] ?? 0);
|
|
$serviceType = $stateData['type'] ?? '';
|
|
$stateNonce = $stateData['nonce'] ?? '';
|
|
|
|
if (!$serviceId || !$serviceType) {
|
|
$this->setRedirect(
|
|
Route::_('index.php?option=com_mokosuitecross&view=services', false),
|
|
Text::_('COM_MOKOSUITECROSS_OAUTH_INVALID_STATE'),
|
|
'error'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// CSRF nonce validation — compare state nonce against session
|
|
$session = Factory::getApplication()->getSession();
|
|
$sessionNonce = $session->get('mokosuitecross.oauth_nonce', '');
|
|
$session->clear('mokosuitecross.oauth_nonce');
|
|
|
|
if (empty($stateNonce) || !hash_equals($sessionNonce, $stateNonce)) {
|
|
$this->setRedirect(
|
|
Route::_('index.php?option=com_mokosuitecross&view=services', false),
|
|
Text::_('COM_MOKOSUITECROSS_OAUTH_INVALID_STATE'),
|
|
'error'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// Get client credentials from plugin params
|
|
PluginHelper::importPlugin('mokosuitecross');
|
|
$pluginParams = PluginHelper::getPlugin('mokosuitecross', $serviceType);
|
|
$params = json_decode($pluginParams->params ?? '{}', true) ?: [];
|
|
|
|
$clientId = $params['client_id'] ?? '';
|
|
$clientSecret = $params['client_secret'] ?? '';
|
|
|
|
$tokenData = OAuthHelper::exchangeCode($serviceType, $code, $clientId, $clientSecret);
|
|
|
|
if (!empty($tokenData['error'])) {
|
|
$this->setRedirect(
|
|
Route::_('index.php?option=com_mokosuitecross&task=service.edit&id=' . $serviceId, false),
|
|
Text::sprintf('COM_MOKOSUITECROSS_OAUTH_TOKEN_ERROR', $tokenData['error']),
|
|
'error'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
OAuthHelper::storeToken($serviceId, $tokenData);
|
|
|
|
$this->setRedirect(
|
|
Route::_('index.php?option=com_mokosuitecross&task=service.edit&id=' . $serviceId, false),
|
|
Text::sprintf('COM_MOKOSUITECROSS_OAUTH_SUCCESS', ucfirst($serviceType)),
|
|
'success'
|
|
);
|
|
}
|
|
}
|