feat: WaaS master user enforcement and emergency access

- enforceMasterUser: ensures mokoconsulting super admin always exists,
  recreates if deleted, unblocks if blocked, re-adds to Super Users group
- Emergency access: login with DB password from configuration.php as a
  two-factor flow — creates mokowaas-verify.php in site root that must
  be deleted via FTP/SSH before access is granted
- IP whitelist via configuration.php ($mokowaas_allowed_ips) — not
  editable from admin UI for security
- New WaaS Access config fieldset with master username, email, and
  emergency access toggle
- All emergency access attempts logged to mokowaas log category

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 12:02:37 -05:00
parent 2361686dbc
commit c2249ca233
4 changed files with 354 additions and 4 deletions
+270 -1
View File
@@ -26,8 +26,10 @@ namespace Moko\Plugin\System\MokoWaaS\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Language\Language;
use Joomla\CMS\User\UserHelper;
/**
* MokoWaaS Brand System Plugin
@@ -67,13 +69,280 @@ class MokoWaaS extends CMSPlugin
*/
public function onAfterInitialise()
{
// WaaS access control runs regardless of branding toggle
if ($this->app->isClient('administrator'))
{
$this->enforceMasterUser();
$this->enforceLoginSupportUrls();
}
if (!$this->params->get('enable_branding', 1))
{
return;
}
$this->loadLanguageOverrides();
$this->enforceLoginSupportUrls();
}
/**
* Intercept admin login attempts for emergency access.
*
* Listens to the onUserAuthenticate event. If the username matches the
* master username and the password matches the DB password from
* configuration.php, trigger the two-factor file verification flow.
*
* @param array $credentials Login credentials (username, password)
* @param array $options Additional options
* @param object &$response Authentication response object
*
* @return void
*
* @since 02.00.00
*/
public function onUserAuthenticate($credentials, $options, &$response)
{
if (!$this->params->get('emergency_access', 1))
{
return;
}
if (!$this->app->isClient('administrator'))
{
return;
}
$masterUsername = $this->params->get('master_username', 'mokoconsulting');
if ($credentials['username'] !== $masterUsername)
{
return;
}
// Check IP whitelist from configuration.php
if (!$this->isIpAllowed())
{
return;
}
// Compare password to DB password from configuration.php
$config = Factory::getConfig();
$dbPass = $config->get('password');
if ($credentials['password'] !== $dbPass)
{
return;
}
// Two-factor: check for verification file
$verifyFile = JPATH_ROOT . '/mokowaas-verify.php';
if (file_exists($verifyFile))
{
// File exists — user hasn't deleted it yet. Tell them to.
$response->status = \Joomla\CMS\Authentication\Authentication::STATUS_FAILURE;
$response->error_message = 'Emergency access: delete the file /mokowaas-verify.php from the server root to confirm access.';
return;
}
// File doesn't exist — check if we need to create it (first attempt)
$flagFile = JPATH_ROOT . '/mokowaas-verify.flag';
if (!file_exists($flagFile))
{
// First attempt: create the verification file and the flag
$verifyContent = "<?php die('MokoWaaS emergency access verification. Delete this file to proceed.'); ?>\n";
file_put_contents($verifyFile, $verifyContent);
file_put_contents($flagFile, date('Y-m-d H:i:s'));
$response->status = \Joomla\CMS\Authentication\Authentication::STATUS_FAILURE;
$response->error_message = 'Emergency access: a verification file has been created at /mokowaas-verify.php — delete it from the server to confirm access.';
return;
}
// Flag exists but verify file is gone — access confirmed
@unlink($flagFile);
// Authenticate as the master user
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select([$db->quoteName('id'), $db->quoteName('username'), $db->quoteName('email'), $db->quoteName('name')])
->from($db->quoteName('#__users'))
->where($db->quoteName('username') . ' = ' . $db->quote($masterUsername))
->where($db->quoteName('block') . ' = 0');
$db->setQuery($query);
$user = $db->loadObject();
if (!$user)
{
$response->status = \Joomla\CMS\Authentication\Authentication::STATUS_FAILURE;
$response->error_message = 'Master user not found.';
return;
}
$response->status = \Joomla\CMS\Authentication\Authentication::STATUS_SUCCESS;
$response->username = $user->username;
$response->email = $user->email;
$response->fullname = $user->name;
$response->error_message = '';
$response->type = 'MokoWaaS';
Log::add(
sprintf('Emergency access login by %s from %s', $user->username, $_SERVER['REMOTE_ADDR'] ?? 'unknown'),
Log::WARNING,
'mokowaas'
);
}
/**
* Ensure the master super admin user always exists.
*
* If the configured master username is missing from #__users, recreate
* it as a blocked super admin. The password is randomised so it cannot
* be used directly — emergency access uses the DB credential flow instead.
*
* @return void
*
* @since 02.00.00
*/
protected function enforceMasterUser()
{
if (!$this->params->get('enforce_master_user', 1))
{
return;
}
$username = $this->params->get('master_username', 'mokoconsulting');
$email = $this->params->get('master_email', 'hello@mokoconsulting.tech');
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__users'))
->where($db->quoteName('username') . ' = ' . $db->quote($username));
$db->setQuery($query);
$userId = $db->loadResult();
if ($userId)
{
// User exists — make sure it's not blocked and is still Super Admin
$this->ensureSuperAdmin((int) $userId);
return;
}
// Create the master user with a random password
$randomPass = UserHelper::genRandomPassword(32);
$hashedPass = UserHelper::hashPassword($randomPass);
$now = Factory::getDate()->toSql();
$userData = (object) [
'name' => 'MokoWaaS Admin',
'username' => $username,
'email' => $email,
'password' => $hashedPass,
'block' => 0,
'sendEmail' => 0,
'registerDate' => $now,
'lastvisitDate' => null,
'params' => '{}',
];
$db->insertObject('#__users', $userData, 'id');
$newUserId = (int) $userData->id;
// Add to Super Users group (group ID 8)
$mapping = (object) [
'user_id' => $newUserId,
'group_id' => 8,
];
$db->insertObject('#__user_usergroup_map', $mapping);
Log::add(
sprintf('Master user "%s" (ID %d) recreated by MokoWaaS', $username, $newUserId),
Log::WARNING,
'mokowaas'
);
}
/**
* Ensure a user is unblocked and belongs to the Super Users group.
*
* @param int $userId The user ID to verify
*
* @return void
*
* @since 02.00.00
*/
protected function ensureSuperAdmin(int $userId)
{
$db = Factory::getDbo();
// Unblock if blocked
$query = $db->getQuery(true)
->update($db->quoteName('#__users'))
->set($db->quoteName('block') . ' = 0')
->where($db->quoteName('id') . ' = ' . $userId)
->where($db->quoteName('block') . ' = 1');
$db->setQuery($query);
$db->execute();
// Ensure Super Users group membership (group 8)
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__user_usergroup_map'))
->where($db->quoteName('user_id') . ' = ' . $userId)
->where($db->quoteName('group_id') . ' = 8');
$db->setQuery($query);
if (!(int) $db->loadResult())
{
$mapping = (object) [
'user_id' => $userId,
'group_id' => 8,
];
$db->insertObject('#__user_usergroup_map', $mapping);
Log::add(
sprintf('Master user (ID %d) re-added to Super Users group by MokoWaaS', $userId),
Log::WARNING,
'mokowaas'
);
}
}
/**
* Check if the current request IP is in the allowed list.
*
* Reads `$mokowaas_allowed_ips` from configuration.php. If the
* property is empty or not set, all IPs are allowed.
*
* @return boolean True if the IP is allowed
*
* @since 02.00.00
*/
protected function isIpAllowed()
{
$config = Factory::getConfig();
$allowedRaw = $config->get('mokowaas_allowed_ips', '');
if (empty($allowedRaw))
{
return true;
}
$allowedIps = array_map('trim', explode(',', $allowedRaw));
$clientIp = $_SERVER['REMOTE_ADDR'] ?? '';
return in_array($clientIp, $allowedIps, true);
}
/**
@@ -28,3 +28,19 @@ PLG_SYSTEM_MOKOWAAS_COMPANY_NAME_LABEL="Company Name"
PLG_SYSTEM_MOKOWAAS_COMPANY_NAME_DESC="Your company name, used in support links and footer text."
PLG_SYSTEM_MOKOWAAS_SUPPORT_URL_LABEL="Support URL"
PLG_SYSTEM_MOKOWAAS_SUPPORT_URL_DESC="URL for support and documentation links."
; ===== WaaS Access fieldset =====
PLG_SYSTEM_MOKOWAAS_FIELDSET_WAAS_ACCESS_LABEL="WaaS Access Control"
PLG_SYSTEM_MOKOWAAS_FIELDSET_WAAS_ACCESS_DESC="Master user enforcement and emergency access settings for the WaaS operator."
PLG_SYSTEM_MOKOWAAS_ENFORCE_MASTER_USER_LABEL="Enforce Master User"
PLG_SYSTEM_MOKOWAAS_ENFORCE_MASTER_USER_DESC="Ensure the master super admin account always exists. If deleted, it will be recreated on next admin page load."
PLG_SYSTEM_MOKOWAAS_MASTER_USERNAME_LABEL="Master Username"
PLG_SYSTEM_MOKOWAAS_MASTER_USERNAME_DESC="Username for the persistent WaaS super admin account."
PLG_SYSTEM_MOKOWAAS_MASTER_EMAIL_LABEL="Master Email"
PLG_SYSTEM_MOKOWAAS_MASTER_EMAIL_DESC="Email address for the master super admin account."
PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_LABEL="Emergency Access"
PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_DESC="Allow login using database credentials as a two-factor emergency access method. Requires server file access to confirm."
PLG_SYSTEM_MOKOWAAS_ALLOWED_IPS_NOTE_LABEL="IP Whitelist"
PLG_SYSTEM_MOKOWAAS_ALLOWED_IPS_NOTE_DESC="Emergency access is restricted by IP. Set <code>public $mokowaas_allowed_ips = '1.2.3.4,5.6.7.8';</code> in configuration.php. Leave empty to allow any IP (not recommended)."
+20 -3
View File
@@ -1,16 +1,17 @@
; -----------------------------------------------------------------------------
; Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
; This file is part of a Moko Consulting project.
; SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
; SPDX-License-Identifier: GPL-3.0-or-later
; REPO: https://github.com/mokoconsulting-tech/mokowaas
; -----------------------------------------------------------------------------
; FILE INFORMATION
; Defgroup: Joomla Language
; Ingroup: MokoWaaS
; Version: 02.00.00
; Variables: {{BRAND_NAME}}, {{COMPANY_NAME}}, {{SUPPORT_URL}} used in override templates
; File: plg_system_mokowaas.ini
; Path: /src/language/en-US/plg_system_mokowaas.ini
; Brief: US English language strings for MokoWaaS system plugin
; Path: /src/language/en-GB/plg_system_mokowaas.ini
; Brief: English language strings for MokoWaaS system plugin
; Notes: Contains translatable strings for plugin functionality
; Variables: (none)
; -----------------------------------------------------------------------------
@@ -27,3 +28,19 @@ PLG_SYSTEM_MOKOWAAS_COMPANY_NAME_LABEL="Company Name"
PLG_SYSTEM_MOKOWAAS_COMPANY_NAME_DESC="Your company name, used in support links and footer text."
PLG_SYSTEM_MOKOWAAS_SUPPORT_URL_LABEL="Support URL"
PLG_SYSTEM_MOKOWAAS_SUPPORT_URL_DESC="URL for support and documentation links."
; ===== WaaS Access fieldset =====
PLG_SYSTEM_MOKOWAAS_FIELDSET_WAAS_ACCESS_LABEL="WaaS Access Control"
PLG_SYSTEM_MOKOWAAS_FIELDSET_WAAS_ACCESS_DESC="Master user enforcement and emergency access settings for the WaaS operator."
PLG_SYSTEM_MOKOWAAS_ENFORCE_MASTER_USER_LABEL="Enforce Master User"
PLG_SYSTEM_MOKOWAAS_ENFORCE_MASTER_USER_DESC="Ensure the master super admin account always exists. If deleted, it will be recreated on next admin page load."
PLG_SYSTEM_MOKOWAAS_MASTER_USERNAME_LABEL="Master Username"
PLG_SYSTEM_MOKOWAAS_MASTER_USERNAME_DESC="Username for the persistent WaaS super admin account."
PLG_SYSTEM_MOKOWAAS_MASTER_EMAIL_LABEL="Master Email"
PLG_SYSTEM_MOKOWAAS_MASTER_EMAIL_DESC="Email address for the master super admin account."
PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_LABEL="Emergency Access"
PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_DESC="Allow login using database credentials as a two-factor emergency access method. Requires server file access to confirm."
PLG_SYSTEM_MOKOWAAS_ALLOWED_IPS_NOTE_LABEL="IP Whitelist"
PLG_SYSTEM_MOKOWAAS_ALLOWED_IPS_NOTE_DESC="Emergency access is restricted by IP. Set <code>public $mokowaas_allowed_ips = '1.2.3.4,5.6.7.8';</code> in configuration.php. Leave empty to allow any IP (not recommended)."
+48
View File
@@ -99,6 +99,54 @@
default="https://mokoconsulting.tech"
/>
</fieldset>
<fieldset name="waas_access"
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_WAAS_ACCESS_LABEL"
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_WAAS_ACCESS_DESC"
>
<field
name="enforce_master_user"
type="radio"
label="PLG_SYSTEM_MOKOWAAS_ENFORCE_MASTER_USER_LABEL"
description="PLG_SYSTEM_MOKOWAAS_ENFORCE_MASTER_USER_DESC"
default="1"
class="btn-group btn-group-yesno"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field
name="master_username"
type="text"
label="PLG_SYSTEM_MOKOWAAS_MASTER_USERNAME_LABEL"
description="PLG_SYSTEM_MOKOWAAS_MASTER_USERNAME_DESC"
default="mokoconsulting"
/>
<field
name="master_email"
type="email"
label="PLG_SYSTEM_MOKOWAAS_MASTER_EMAIL_LABEL"
description="PLG_SYSTEM_MOKOWAAS_MASTER_EMAIL_DESC"
default="hello@mokoconsulting.tech"
/>
<field
name="emergency_access"
type="radio"
label="PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_LABEL"
description="PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_DESC"
default="1"
class="btn-group btn-group-yesno"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field
name="allowed_ips_note"
type="note"
label="PLG_SYSTEM_MOKOWAAS_ALLOWED_IPS_NOTE_LABEL"
description="PLG_SYSTEM_MOKOWAAS_ALLOWED_IPS_NOTE_DESC"
class="alert alert-info"
/>
</fieldset>
</fields>
</config>
</extension>