4b9a675d0f
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 36s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 40s
Generic: Project CI / Tests (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (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
All Joomla element names, PHP classes, language files, folder structure, and manifest references renamed from mokosuite to mokosuiteclient. This repo is now the client-facing tracker for the MokoSuite platform.
90 lines
4.4 KiB
Markdown
90 lines
4.4 KiB
Markdown
# Plugin Protection
|
|
|
|
MokoSuiteClient uses multiple layers of protection to prevent accidental or unauthorized disabling and uninstallation. The master user retains full control over the plugin at all times.
|
|
|
|
## Protection Layers
|
|
|
|
### 1. Protected Flag (`protected=1`)
|
|
|
|
During installation and on every admin session, MokoSuiteClient sets the `protected` column to `1` in the `#__extensions` database table for both `mokosuiteclient` and `pkg_mokosuiteclient` entries.
|
|
|
|
The Joomla framework itself enforces this flag: protected extensions cannot be disabled or uninstalled through the standard admin interface.
|
|
|
|
The `locked` column is set to `0` so the extension can still receive updates and configuration changes.
|
|
|
|
### 2. Self-Healing
|
|
|
|
The `ensureProtectedFlag()` method runs once per admin session (using a static flag to avoid repeated queries). If the `protected` column has been reset to `0` (e.g., by a database modification), it is automatically restored to `1`.
|
|
|
|
This runs in the `protectPlugin()` method, which is called from `onBeforeRender()` on every admin page load.
|
|
|
|
### 3. Hidden from Plugin List
|
|
|
|
For non-master users, MokoSuiteClient injects JavaScript on the `com_plugins` and `com_installer` pages that hides any table row containing "mokosuiteclient" or "MokoSuiteClient". This prevents non-master users from seeing the plugin in the extension list.
|
|
|
|
The `hidePluginFromList()` method adds an inline script that runs on `DOMContentLoaded`:
|
|
|
|
```javascript
|
|
document.querySelectorAll('tr').forEach(function(row) {
|
|
var text = row.textContent || '';
|
|
if (text.indexOf('mokosuiteclient') !== -1 || text.indexOf('MokoSuiteClient') !== -1) {
|
|
row.style.display = 'none';
|
|
}
|
|
});
|
|
```
|
|
|
|
### 4. onExtensionBeforeSave Interception
|
|
|
|
The `onExtensionBeforeSave` event handler intercepts save attempts on the plugin configuration. If a non-master user attempts to save the plugin with `enabled = 0`, the handler:
|
|
|
|
1. Displays an error message: "MokoSuiteClient cannot be disabled."
|
|
2. Forces `enabled` back to `1` on the table object
|
|
3. Returns `true` to allow the save to proceed (with the corrected value)
|
|
|
|
### 5. protectPlugin() -- Uninstall and Disable Blocking
|
|
|
|
The `protectPlugin()` method runs on every admin page request and checks for active uninstall or disable attempts:
|
|
|
|
**Uninstall blocking**: If the current request is to `com_installer` with task `manage.remove`, and the extension IDs include any MokoSuiteClient extension, the request is blocked with an error message and a redirect back to the installer manage view.
|
|
|
|
**Disable blocking**: If the current request is to `com_plugins` with task `plugins.publish`, and the extension IDs include MokoSuiteClient, the request is blocked with an error message and a redirect back to the plugins list.
|
|
|
|
The `isOurExtension()` helper method checks extension IDs against the database to determine if they belong to MokoSuiteClient (matching on element name `mokosuiteclient` or `pkg_mokosuiteclient`).
|
|
|
|
## Master User Exemption
|
|
|
|
All protection checks call `isMasterUser()` first. If the current user is the designated master user (matching the configured `master_username`), all protections are bypassed. The master user can:
|
|
|
|
- See MokoSuiteClient in the plugin and extension lists
|
|
- Disable the plugin via the configuration page
|
|
- Uninstall the plugin via the Extension Manager
|
|
- Modify all plugin settings
|
|
|
|
## Installation-Time Protection
|
|
|
|
The package installer script (`Pkg_MokosuiteInstallerScript`) and the plugin installer script both set `protected=1` during `postflight`:
|
|
|
|
- `enableAndLockPlugin()` sets `enabled=1`, `locked=1`, `protected=1` on the system plugin
|
|
- `protectExtensions()` sets `protected=1`, `locked=0` on all MokoSuiteClient extensions (plugin and package)
|
|
|
|
This ensures protection is active immediately after installation, before the first admin page load triggers the self-healing logic.
|
|
|
|
## Summary of Protection Flow
|
|
|
|
```
|
|
Installation
|
|
-> postflight sets protected=1, enabled=1
|
|
|
|
Every admin page load (onBeforeRender)
|
|
-> protectPlugin()
|
|
-> ensureProtectedFlag() (once per session, restores protected=1 if needed)
|
|
-> if not master user:
|
|
-> block uninstall attempts (com_installer manage.remove)
|
|
-> block disable attempts (com_plugins plugins.publish)
|
|
-> hidePluginFromList() for non-master users
|
|
|
|
Plugin config save (onExtensionBeforeSave)
|
|
-> if not master user and enabled=0:
|
|
-> force enabled=1, show error
|
|
```
|