Manual:Building anti-abuse features into your extension
Anti-abuse, including but not limited to anti-spam, features are essential when it comes to extensions which allow content to be posted. "Content" should be understood here as "anything user-submitted sent to the site". If an extension fails to implement anti-abuse features, depending on how busy of a site we're talking about, it could easily become an abuse vector which in the worst case might force site admins to even disable the extension completely.
Types of anti-abuse mechanisms
- Rate limiting (throttling)
- Usually requires memcached or a similar cache setup
- CAPTCHAs (the ConfirmEdit extension)
- Different types of CAPTCHAs: distorted word (FancyCaptcha), questions (QuestyCaptcha), not-so-great JavaScript-only CAPTCHAs dependent on external services (hCaptcha etc.)
- AbuseFilter — framework for highly customizable anti-abuse filters tailored to a specific site's particular needs
- SpamRegex — both the core configuration variable and the extension by the same name
How do I implement...
rate limiting?
Web UI
if ( $user->pingLimiter( 'edit' ) ) {
$out->setPageTitle( $this->msg( 'error' )->escaped() );
$out->addWikiMsg( 'actionthrottledtext' );
$out->addReturnTo( Title::newMainPage() ); // Make sure that there is a "use MediaWiki\Title\Title;" statement somewhere!
return;
}
$user
is a regular User object, $out
is the OutputPage object.
Note that this increases the named throttle (edit). If you wish to merely check the value instead of also increasing it, use if ( $user->pingLimiter( 'edit', 0 ) ) {
instead.
API
if ( $user->pingLimiter( 'edit' ) ) {
$this->dieWithError( 'actionthrottledtext', 'throttled' );
}
Nice and simple. Read the disclaimer on the web UI section above regarding increasing throttles.
...CAPTCHAs?
This is surprisingly complex.
Web UI
For the web UI, this has been done a million times and then some. Some good examples where to borrow code:
- Extension:ContactPage
- This is good if you want to check a custom trigger instead of the usual
edit
,addurl
orcreate
triggers.
- This is good if you want to check a custom trigger instead of the usual
- Extension:CreateAPage
- Extension:Form
- Extension:WikiForum
If your extension does not have an API module or modules or you don't care about the API, that's pretty simple.
Code example from Extension:Form, for a similar SpecialPage or other ContextSource where the usual totally-not-global-variables are available:
# This part goes whereever your extension is building its HTML output:
// Is CAPTCHA enabled?
if ( $this->useCaptcha() ) {
$out->addHTML( $this->getCaptcha() );
}
/**
* @return bool True if CAPTCHA should be used, false otherwise
*/
private function useCaptcha() {
global $wgCaptchaClass, $wgCaptchaTriggers;
return $wgCaptchaClass &&
isset( $wgCaptchaTriggers['form'] ) &&
$wgCaptchaTriggers['form'] &&
!$this->getUser()->isAllowed( 'skipcaptcha' );
}
/**
* @return string CAPTCHA form HTML
*/
private function getCaptcha() {
// NOTE: make sure we have a session. May be required for CAPTCHAs to work.
\MediaWiki\Session\SessionManager::getGlobalSession()->persist();
$captcha = MediaWiki\Extension\ConfirmEdit\Hooks::getInstance();
$captcha->setTrigger( 'form' ); // <-- this is used for $wgCaptchaTriggers, see above
$captcha->setAction( 'createpageviaform' ); // <-- you may want to change this to 'edit' or 'create'
$formInformation = $captcha->getFormInformation();
$formMetainfo = $formInformation;
unset( $formMetainfo['html'] );
$captcha->addFormInformationToOutput( $this->getOutput(), $formMetainfo );
return '<div class="captcha">' .
$formInformation['html'] .
"</div>\n";
}
And this one goes for whereever you're checking the POST request:
// Check ordinary CAPTCHA
if ( $this->useCaptcha() && !MediaWiki\Extension\ConfirmEdit\Hooks::getInstance()->passCaptchaFromRequest( $request, $user ) ) {
// Display an error message here...
// @todo Using 'captcha-edit-fail' isn't necessarily correct, it depends on the trigger in getCaptcha()
// The message key you use here should be consistent with the trigger name in getCaptcha()
$out->addHTML( Html::errorBox( $this->msg( 'captcha-edit-fail' )->parse() ) ); // Again, make sure the file has a "use MediaWiki\Html\Html;" statement at the beginning of the file!
return;
}
API
If your extension has an API module and you care about it not being abused, things get a bit hairy. You will need to have the module first get a CAPTCHA (if possible) and return it to the user for solving, and on the second try the module should process the parameters as usual.
Now, the thing is, as of June 2024 ConfirmEdit includes many frankly awful types of CAPTCHAs which rely on JavaScript and external, proprietary services. hCaptcha and reCAPTCHA are some such examples. These are simply not compatible with the API at all, period. If your API module implements ConfirmEdit support (as e.g. LinkFilter's ApiLinkEdit and ApiLinkSubmit modules do) but the wiki is using one of the aforementioned CAPTCHA types, it means that the API modules will literally be unusable by users who'd be subject to CAPTCHAs. This may or may not be a big deal to you.
...AbuseFilter support?
Web UI
TODO AFTv5 example etc.
API
TODO?
...SpamRegex support?
API and web UI
ArticleFeedbackv5 (AFTv5) does a lot of things. Its SpamRegex support is surprisingly robust. Check it out: the code is in the ArticleFeedbackv5Utils
class, method validateSpamRegex
.
A note about the SpamRegex extension integration in AFTv5: this code in AFTv5 checks entries which are blocked in SpamRegex in article text only, it does not check for the entries which are blocked with the "block the expression in edit summaries" option. If you wish to check for that, then either:
- change the line
SpamRegex::TYPE_TEXTBOX
toSpamRegex::TYPE_SUMMARY
— this changes the code to check only for the expressions which have been blocked with the "block the expression in edit summaries" option. If you wish to check SpamRegex for both options, - duplicate the relevant portion of code and then in the new, duplicated portion, change
SpamRegex::TYPE_TEXTBOX
toSpamRegex::TYPE_SUMMARY
— that way you'll be checking for both SpamRegexed entries.
See also
External links
- Risker's checklist for content-creation extensions on the English Wikipedia