Files
MokoSuite/source/packages/com_mokosuite/api/src/Controller/ProvisionController.php
T
Jonathan Miller 00d44256b4 refactor: rename MokoWaaS to MokoSuite across entire codebase
Rebrand all 17 sub-extensions from mokowaas to mokosuite naming,
including component, plugins, modules, task plugins, and webservices.
Updates package manifest, workflows, docs, wiki, and issue templates.
Adds new plg_system_mokosuite_license extension.
2026-06-07 09:25:45 -05:00

237 lines
6.0 KiB
PHP

<?php
/**
* @package MokoSuite
* @subpackage com_mokosuite
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Moko\Component\MokoSuite\Api\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Controller\BaseController;
/**
* Provision reset API controller.
*
* POST /api/index.php/v1/mokosuite/provision-reset
*
* Resets a site for new client provisioning: clears hits, versions,
* download keys, and flags the site for fresh client info collection.
* Used after copying a demo site to create a new client install.
*
* @since 02.35.00
*/
class ProvisionController extends BaseController
{
/**
* Reset the site for new client provisioning.
*
* @return void
*/
public function execute($task = 'provision'): void
{
$app = Factory::getApplication();
$user = $app->getIdentity();
if (!$user->authorise('core.manage', 'com_mokosuite'))
{
$this->sendJson(403, ['error' => 'Not authorized']);
return;
}
if ($app->input->getMethod() !== 'POST')
{
$this->sendJson(405, ['error' => 'POST required']);
return;
}
$db = Factory::getDbo();
$results = [];
// 1. Reset article hit counters
try
{
$db->setQuery(
$db->getQuery(true)
->update($db->quoteName('#__content'))
->set($db->quoteName('hits') . ' = 0')
)->execute();
$results['hits_reset'] = $db->getAffectedRows();
}
catch (\Throwable $e)
{
$results['hits_reset'] = 'error: ' . $e->getMessage();
}
// 2. Delete content version history
try
{
$db->setQuery(
$db->getQuery(true)->delete($db->quoteName('#__history'))
)->execute();
$results['versions_deleted'] = $db->getAffectedRows();
}
catch (\Throwable $e)
{
$results['versions_deleted'] = 'error: ' . $e->getMessage();
}
// 3. Regenerate heartbeat token if requested
$input = $app->getInput()->json;
$resetToken = (bool) ($input->get('reset_token', false, 'BOOLEAN'));
if ($resetToken)
{
try
{
$newToken = bin2hex(random_bytes(32));
$plugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokosuite');
if ($plugin)
{
$pluginParams = new \Joomla\Registry\Registry($plugin->params);
$pluginParams->set('health_api_token', $newToken);
$db->setQuery(
$db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote($pluginParams->toString()))
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuite'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
)->execute();
$results['token_regenerated'] = true;
$results['new_token'] = $newToken;
}
}
catch (\Throwable $e)
{
$results['token_regenerated'] = 'error: ' . $e->getMessage();
}
}
// 4. Reset all user API tokens if requested
$resetApiTokens = (bool) ($input->get('reset_api_tokens', false, 'BOOLEAN'));
if ($resetApiTokens)
{
try
{
// Get users who have API tokens before deleting
$db->setQuery(
$db->getQuery(true)
->select('DISTINCT ' . $db->quoteName('user_id'))
->from($db->quoteName('#__user_keys'))
->where($db->quoteName('series') . ' LIKE ' . $db->quote('api-%'))
);
$affectedUserIds = $db->loadColumn() ?: [];
$db->setQuery(
$db->getQuery(true)->delete($db->quoteName('#__user_keys'))
->where($db->quoteName('series') . ' LIKE ' . $db->quote('api-%'))
)->execute();
$results['api_tokens_revoked'] = $db->getAffectedRows();
// Notify affected users
if (!empty($affectedUserIds))
{
$this->notifyTokenReset($db, $affectedUserIds);
$results['users_notified'] = \count($affectedUserIds);
}
}
catch (\Throwable $e)
{
$results['api_tokens_revoked'] = 'error: ' . $e->getMessage();
}
}
// 5. Flag site for fresh client info setup
try
{
// Write a flag file that the core plugin checks on next admin load
$flagFile = JPATH_ADMINISTRATOR . '/cache/mokosuite_setup_required.flag';
file_put_contents($flagFile, json_encode([
'created' => gmdate('Y-m-d\TH:i:s\Z'),
'reason' => 'provision-reset',
'remote_ip' => $_SERVER['REMOTE_ADDR'] ?? '',
]));
$results['setup_flag'] = true;
}
catch (\Throwable $e)
{
$results['setup_flag'] = 'error: ' . $e->getMessage();
}
$this->sendJson(200, [
'status' => 'ok',
'message' => 'Site provisioned for new client.',
'results' => $results,
]);
}
/**
* Notify users that their API tokens have been revoked.
*/
private function notifyTokenReset($db, array $userIds): void
{
try
{
$db->setQuery(
$db->getQuery(true)
->select([$db->quoteName('name'), $db->quoteName('email')])
->from($db->quoteName('#__users'))
->whereIn($db->quoteName('id'), $userIds)
->where($db->quoteName('block') . ' = 0')
);
$users = $db->loadObjectList() ?: [];
$config = Factory::getConfig();
$siteName = $config->get('sitename', 'Joomla');
$siteUrl = rtrim(\Joomla\CMS\Uri\Uri::root(), '/');
$mailer = Factory::getMailer();
foreach ($users as $u)
{
try
{
$mailer->clearAllRecipients();
$mailer->addRecipient($u->email, $u->name);
$mailer->setSubject($siteName . ' — API tokens have been reset');
$mailer->setBody(
"Hello {$u->name},\n\n"
. "Your API access tokens on {$siteName} have been revoked by an administrator.\n\n"
. "If you use API integrations, please log in and generate a new token:\n"
. "{$siteUrl}/administrator/\n\n"
. "— {$siteName}"
);
$mailer->send();
}
catch (\Throwable $e)
{
// Non-critical
}
}
}
catch (\Throwable $e)
{
// Non-critical
}
}
private function sendJson(int $code, array $data): void
{
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_UNESCAPED_SLASHES);
Factory::getApplication()->close();
}
}