2007-11-12 07:42:25 +00:00
|
|
|
|
<?php
|
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
use MediaWiki\Auth\AuthenticationRequest;
|
2019-07-03 12:43:23 +00:00
|
|
|
|
use MediaWiki\MediaWikiServices;
|
2020-01-25 05:38:50 +00:00
|
|
|
|
use Wikimedia\IPUtils;
|
2015-04-21 12:14:52 +00:00
|
|
|
|
|
Remove getForm() and replace by getFormInformation()
This commit removes SimpleCaptcha::getForm() and replaces it by its more informative
counterpart getFormInformation(), which returns an array, which provides some
more information about the form than only the html.
The information included in the array is:
* html: The HTML of the CAPTCHA form (this is the same as what you expected from
getForm() previously)
* modules: ResourceLoader modules, if any, that should be added to the output of the
page
* modulestyles: ResourceLoader style modules, if any, that should be added to th
output of the page
* headitems: Head items that should be added to the output (see OutputPage::addHeadItems)
Mostly you shouldn't need to handle the response of getFormInformation() anymore, as there's
a new function, addFormToOutput(), which takes an instance of OutputPage as a first parameter
and handles the response of getFormInformation for you (adds all information to the given
OutputPage instance, if they're provided).
Bug: T141300
Depends-On: I433afd124b57526caa13a540cda48ba2b99a9bde
Change-Id: I25f344538052fc18993c43185fbd97804a7cfc81
2016-07-26 16:08:42 +00:00
|
|
|
|
/**
|
|
|
|
|
* Demo CAPTCHA (not for production usage) and base class for real CAPTCHAs
|
|
|
|
|
*/
|
2007-11-12 07:42:25 +00:00
|
|
|
|
class SimpleCaptcha {
|
2016-04-25 20:58:18 +00:00
|
|
|
|
protected static $messagePrefix = 'captcha-';
|
|
|
|
|
|
2015-04-02 16:27:37 +00:00
|
|
|
|
/** @var boolean|null Was the CAPTCHA already passed and if yes, with which result? */
|
|
|
|
|
private $captchaSolved = null;
|
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
/**
|
|
|
|
|
* Used to select the right message.
|
|
|
|
|
* One of sendmail, createaccount, badlogin, edit, create, addurl.
|
|
|
|
|
* @var string
|
|
|
|
|
*/
|
|
|
|
|
protected $action;
|
|
|
|
|
|
|
|
|
|
/** @var string Used in log messages. */
|
|
|
|
|
protected $trigger;
|
|
|
|
|
|
2017-02-17 13:09:34 +00:00
|
|
|
|
/**
|
|
|
|
|
* @param string $action
|
|
|
|
|
*/
|
2016-04-25 20:58:18 +00:00
|
|
|
|
public function setAction( $action ) {
|
|
|
|
|
$this->action = $action;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-17 13:09:34 +00:00
|
|
|
|
/**
|
|
|
|
|
* @param string $trigger
|
|
|
|
|
*/
|
2016-04-25 20:58:18 +00:00
|
|
|
|
public function setTrigger( $trigger ) {
|
|
|
|
|
$this->trigger = $trigger;
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-03 16:42:00 +00:00
|
|
|
|
/**
|
|
|
|
|
* Return the error from the last passCaptcha* call.
|
|
|
|
|
* Not implemented but needed by some child classes.
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @return mixed
|
2016-05-03 16:42:00 +00:00
|
|
|
|
*/
|
|
|
|
|
public function getError() {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
/**
|
|
|
|
|
* Returns an array with 'question' and 'answer' keys.
|
|
|
|
|
* Subclasses might use different structure.
|
|
|
|
|
* Since MW 1.27 all subclasses must implement this method.
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
public function getCaptcha() {
|
2009-07-19 15:13:01 +00:00
|
|
|
|
$a = mt_rand( 0, 100 );
|
|
|
|
|
$b = mt_rand( 0, 10 );
|
2010-10-29 19:31:07 +00:00
|
|
|
|
|
|
|
|
|
/* Minus sign is used in the question. UTF-8,
|
|
|
|
|
since the api uses text/plain, not text/html */
|
|
|
|
|
$op = mt_rand( 0, 1 ) ? '+' : '−';
|
2008-02-28 17:42:23 +00:00
|
|
|
|
|
2011-12-08 14:22:41 +00:00
|
|
|
|
// No space before and after $op, to ensure correct
|
|
|
|
|
// directionality.
|
|
|
|
|
$test = "$a$op$b";
|
2009-07-19 15:13:01 +00:00
|
|
|
|
$answer = ( $op == '+' ) ? ( $a + $b ) : ( $a - $b );
|
2016-05-09 23:41:17 +00:00
|
|
|
|
return [ 'question' => $test, 'answer' => $answer ];
|
2008-02-28 17:42:23 +00:00
|
|
|
|
}
|
2009-07-19 15:13:01 +00:00
|
|
|
|
|
2017-02-17 13:09:34 +00:00
|
|
|
|
/**
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param array &$resultArr
|
2017-02-17 13:09:34 +00:00
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
protected function addCaptchaAPI( &$resultArr ) {
|
2008-02-28 17:42:23 +00:00
|
|
|
|
$captcha = $this->getCaptcha();
|
|
|
|
|
$index = $this->storeCaptcha( $captcha );
|
2016-04-25 20:58:18 +00:00
|
|
|
|
$resultArr['captcha'] = $this->describeCaptchaType();
|
2008-02-28 17:42:23 +00:00
|
|
|
|
$resultArr['captcha']['id'] = $index;
|
|
|
|
|
$resultArr['captcha']['question'] = $captcha['question'];
|
|
|
|
|
}
|
2009-07-19 15:13:01 +00:00
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
/**
|
|
|
|
|
* Describes the captcha type for API clients.
|
|
|
|
|
* @return array An array with keys 'type' and 'mime', and possibly other
|
|
|
|
|
* implementation-specific
|
|
|
|
|
*/
|
|
|
|
|
public function describeCaptchaType() {
|
|
|
|
|
return [
|
|
|
|
|
'type' => 'simple',
|
|
|
|
|
'mime' => 'text/plain',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2007-11-12 07:42:25 +00:00
|
|
|
|
/**
|
|
|
|
|
* Insert a captcha prompt into the edit form.
|
|
|
|
|
* This sample implementation generates a simple arithmetic operation;
|
|
|
|
|
* it would be easy to defeat by machine.
|
|
|
|
|
*
|
|
|
|
|
* Override this!
|
|
|
|
|
*
|
Remove getForm() and replace by getFormInformation()
This commit removes SimpleCaptcha::getForm() and replaces it by its more informative
counterpart getFormInformation(), which returns an array, which provides some
more information about the form than only the html.
The information included in the array is:
* html: The HTML of the CAPTCHA form (this is the same as what you expected from
getForm() previously)
* modules: ResourceLoader modules, if any, that should be added to the output of the
page
* modulestyles: ResourceLoader style modules, if any, that should be added to th
output of the page
* headitems: Head items that should be added to the output (see OutputPage::addHeadItems)
Mostly you shouldn't need to handle the response of getFormInformation() anymore, as there's
a new function, addFormToOutput(), which takes an instance of OutputPage as a first parameter
and handles the response of getFormInformation for you (adds all information to the given
OutputPage instance, if they're provided).
Bug: T141300
Depends-On: I433afd124b57526caa13a540cda48ba2b99a9bde
Change-Id: I25f344538052fc18993c43185fbd97804a7cfc81
2016-07-26 16:08:42 +00:00
|
|
|
|
* It is not guaranteed that the CAPTCHA will load synchronously with the main page
|
|
|
|
|
* content. So you can not rely on registering handlers before page load. E.g.:
|
|
|
|
|
*
|
|
|
|
|
* NOT SAFE: $( window ).on( 'load', handler )
|
|
|
|
|
* SAFE: $( handler )
|
|
|
|
|
*
|
|
|
|
|
* However, if the HTML is loaded dynamically via AJAX, the following order will
|
|
|
|
|
* be used.
|
|
|
|
|
*
|
|
|
|
|
* headitems => modulestyles + modules => add main HTML to DOM when modulestyles +
|
|
|
|
|
* modules are ready.
|
|
|
|
|
*
|
|
|
|
|
* @param int $tabIndex Tab index to start from
|
|
|
|
|
*
|
|
|
|
|
* @return array Associative array with the following keys:
|
|
|
|
|
* string html - Main HTML
|
|
|
|
|
* array modules (optional) - Array of ResourceLoader module names
|
|
|
|
|
* array modulestyles (optional) - Array of ResourceLoader module names to be
|
2017-10-21 04:19:58 +00:00
|
|
|
|
* included as style-only modules.
|
Remove getForm() and replace by getFormInformation()
This commit removes SimpleCaptcha::getForm() and replaces it by its more informative
counterpart getFormInformation(), which returns an array, which provides some
more information about the form than only the html.
The information included in the array is:
* html: The HTML of the CAPTCHA form (this is the same as what you expected from
getForm() previously)
* modules: ResourceLoader modules, if any, that should be added to the output of the
page
* modulestyles: ResourceLoader style modules, if any, that should be added to th
output of the page
* headitems: Head items that should be added to the output (see OutputPage::addHeadItems)
Mostly you shouldn't need to handle the response of getFormInformation() anymore, as there's
a new function, addFormToOutput(), which takes an instance of OutputPage as a first parameter
and handles the response of getFormInformation for you (adds all information to the given
OutputPage instance, if they're provided).
Bug: T141300
Depends-On: I433afd124b57526caa13a540cda48ba2b99a9bde
Change-Id: I25f344538052fc18993c43185fbd97804a7cfc81
2016-07-26 16:08:42 +00:00
|
|
|
|
* array headitems (optional) - Head items (see OutputPage::addHeadItems), as a numeric array
|
|
|
|
|
* of raw HTML strings. Do not use unless no other option is feasible.
|
2007-11-12 07:42:25 +00:00
|
|
|
|
*/
|
Remove getForm() and replace by getFormInformation()
This commit removes SimpleCaptcha::getForm() and replaces it by its more informative
counterpart getFormInformation(), which returns an array, which provides some
more information about the form than only the html.
The information included in the array is:
* html: The HTML of the CAPTCHA form (this is the same as what you expected from
getForm() previously)
* modules: ResourceLoader modules, if any, that should be added to the output of the
page
* modulestyles: ResourceLoader style modules, if any, that should be added to th
output of the page
* headitems: Head items that should be added to the output (see OutputPage::addHeadItems)
Mostly you shouldn't need to handle the response of getFormInformation() anymore, as there's
a new function, addFormToOutput(), which takes an instance of OutputPage as a first parameter
and handles the response of getFormInformation for you (adds all information to the given
OutputPage instance, if they're provided).
Bug: T141300
Depends-On: I433afd124b57526caa13a540cda48ba2b99a9bde
Change-Id: I25f344538052fc18993c43185fbd97804a7cfc81
2016-07-26 16:08:42 +00:00
|
|
|
|
public function getFormInformation( $tabIndex = 1 ) {
|
2008-02-28 17:42:23 +00:00
|
|
|
|
$captcha = $this->getCaptcha();
|
|
|
|
|
$index = $this->storeCaptcha( $captcha );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
|
Remove getForm() and replace by getFormInformation()
This commit removes SimpleCaptcha::getForm() and replaces it by its more informative
counterpart getFormInformation(), which returns an array, which provides some
more information about the form than only the html.
The information included in the array is:
* html: The HTML of the CAPTCHA form (this is the same as what you expected from
getForm() previously)
* modules: ResourceLoader modules, if any, that should be added to the output of the
page
* modulestyles: ResourceLoader style modules, if any, that should be added to th
output of the page
* headitems: Head items that should be added to the output (see OutputPage::addHeadItems)
Mostly you shouldn't need to handle the response of getFormInformation() anymore, as there's
a new function, addFormToOutput(), which takes an instance of OutputPage as a first parameter
and handles the response of getFormInformation for you (adds all information to the given
OutputPage instance, if they're provided).
Bug: T141300
Depends-On: I433afd124b57526caa13a540cda48ba2b99a9bde
Change-Id: I25f344538052fc18993c43185fbd97804a7cfc81
2016-07-26 16:08:42 +00:00
|
|
|
|
return [
|
2018-12-07 18:23:39 +00:00
|
|
|
|
'html' =>
|
|
|
|
|
new OOUI\FieldLayout(
|
|
|
|
|
new OOUI\NumberInputWidget( [
|
|
|
|
|
'name' => 'wpCaptchaWord',
|
|
|
|
|
'classes' => [ 'simplecaptcha-answer' ],
|
|
|
|
|
'id' => 'wpCaptchaWord',
|
|
|
|
|
'autocomplete' => 'off',
|
|
|
|
|
// tab in before the edit textarea
|
|
|
|
|
'tabIndex' => $tabIndex
|
|
|
|
|
] ),
|
|
|
|
|
[
|
|
|
|
|
'align' => 'left',
|
|
|
|
|
'label' => $captcha['question'] . ' = ',
|
|
|
|
|
'classes' => [ 'simplecaptcha-field' ],
|
|
|
|
|
]
|
|
|
|
|
) .
|
|
|
|
|
new OOUI\HiddenInputWidget( [
|
|
|
|
|
'name' => 'wpCaptchaId',
|
|
|
|
|
'id' => 'wpCaptchaId',
|
|
|
|
|
'value' => $index
|
|
|
|
|
] ),
|
|
|
|
|
'modulestyles' => [
|
|
|
|
|
'ext.confirmEdit.simpleCaptcha'
|
|
|
|
|
]
|
Remove getForm() and replace by getFormInformation()
This commit removes SimpleCaptcha::getForm() and replaces it by its more informative
counterpart getFormInformation(), which returns an array, which provides some
more information about the form than only the html.
The information included in the array is:
* html: The HTML of the CAPTCHA form (this is the same as what you expected from
getForm() previously)
* modules: ResourceLoader modules, if any, that should be added to the output of the
page
* modulestyles: ResourceLoader style modules, if any, that should be added to th
output of the page
* headitems: Head items that should be added to the output (see OutputPage::addHeadItems)
Mostly you shouldn't need to handle the response of getFormInformation() anymore, as there's
a new function, addFormToOutput(), which takes an instance of OutputPage as a first parameter
and handles the response of getFormInformation for you (adds all information to the given
OutputPage instance, if they're provided).
Bug: T141300
Depends-On: I433afd124b57526caa13a540cda48ba2b99a9bde
Change-Id: I25f344538052fc18993c43185fbd97804a7cfc81
2016-07-26 16:08:42 +00:00
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Uses getFormInformation() to get the CAPTCHA form and adds it to the given
|
|
|
|
|
* OutputPage object.
|
|
|
|
|
*
|
|
|
|
|
* @param OutputPage $out The OutputPage object to which the form should be added
|
2017-09-01 04:47:36 +00:00
|
|
|
|
* @param int $tabIndex See self::getFormInformation
|
Remove getForm() and replace by getFormInformation()
This commit removes SimpleCaptcha::getForm() and replaces it by its more informative
counterpart getFormInformation(), which returns an array, which provides some
more information about the form than only the html.
The information included in the array is:
* html: The HTML of the CAPTCHA form (this is the same as what you expected from
getForm() previously)
* modules: ResourceLoader modules, if any, that should be added to the output of the
page
* modulestyles: ResourceLoader style modules, if any, that should be added to th
output of the page
* headitems: Head items that should be added to the output (see OutputPage::addHeadItems)
Mostly you shouldn't need to handle the response of getFormInformation() anymore, as there's
a new function, addFormToOutput(), which takes an instance of OutputPage as a first parameter
and handles the response of getFormInformation for you (adds all information to the given
OutputPage instance, if they're provided).
Bug: T141300
Depends-On: I433afd124b57526caa13a540cda48ba2b99a9bde
Change-Id: I25f344538052fc18993c43185fbd97804a7cfc81
2016-07-26 16:08:42 +00:00
|
|
|
|
*/
|
|
|
|
|
public function addFormToOutput( OutputPage $out, $tabIndex = 1 ) {
|
|
|
|
|
$this->addFormInformationToOutput( $out, $this->getFormInformation( $tabIndex ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Processes the given $formInformation array and adds the options (see getFormInformation())
|
|
|
|
|
* to the given OutputPage object.
|
|
|
|
|
*
|
|
|
|
|
* @param OutputPage $out The OutputPage object to which the form should be added
|
|
|
|
|
* @param array $formInformation
|
|
|
|
|
*/
|
|
|
|
|
public function addFormInformationToOutput( OutputPage $out, array $formInformation ) {
|
|
|
|
|
if ( !$formInformation ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ( isset( $formInformation['html'] ) ) {
|
|
|
|
|
$out->addHTML( $formInformation['html'] );
|
|
|
|
|
}
|
|
|
|
|
if ( isset( $formInformation['modules'] ) ) {
|
|
|
|
|
$out->addModules( $formInformation['modules'] );
|
|
|
|
|
}
|
|
|
|
|
if ( isset( $formInformation['modulestyles'] ) ) {
|
|
|
|
|
$out->addModuleStyles( $formInformation['modulestyles'] );
|
|
|
|
|
}
|
|
|
|
|
if ( isset( $formInformation['headitems'] ) ) {
|
|
|
|
|
$out->addHeadItems( $formInformation['headitems'] );
|
|
|
|
|
}
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
/**
|
|
|
|
|
* @param array $captchaData Data given by getCaptcha
|
|
|
|
|
* @param string $id ID given by storeCaptcha
|
|
|
|
|
* @return string Description of the captcha. Format is not specified; could be text, HTML, URL...
|
|
|
|
|
*/
|
|
|
|
|
public function getCaptchaInfo( $captchaData, $id ) {
|
2019-12-03 17:08:13 +00:00
|
|
|
|
return array_key_exists( 'question', $captchaData ) ? ( $captchaData['question'] . ' =' ) : '';
|
2016-04-25 20:58:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2007-11-12 07:42:25 +00:00
|
|
|
|
/**
|
2014-05-30 14:33:05 +00:00
|
|
|
|
* Show error message for missing or incorrect captcha on EditPage.
|
2019-11-19 03:28:05 +00:00
|
|
|
|
* @param EditPage $editPage
|
|
|
|
|
* @param OutputPage $out
|
2007-11-12 07:42:25 +00:00
|
|
|
|
*/
|
2019-11-19 03:28:05 +00:00
|
|
|
|
public function showEditFormFields( EditPage $editPage, OutputPage $out ) {
|
2019-09-07 09:41:23 +00:00
|
|
|
|
$out->enableOOUI();
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
$page = $editPage->getArticle()->getPage();
|
|
|
|
|
if ( !isset( $page->ConfirmEdit_ActivateCaptcha ) ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-01-12 17:03:40 +00:00
|
|
|
|
|
|
|
|
|
if ( $this->action !== 'edit' ) {
|
|
|
|
|
unset( $page->ConfirmEdit_ActivateCaptcha );
|
2019-05-18 19:27:16 +00:00
|
|
|
|
$out->addHTML( $this->getMessage( $this->action )->parseAsBlock() );
|
Remove getForm() and replace by getFormInformation()
This commit removes SimpleCaptcha::getForm() and replaces it by its more informative
counterpart getFormInformation(), which returns an array, which provides some
more information about the form than only the html.
The information included in the array is:
* html: The HTML of the CAPTCHA form (this is the same as what you expected from
getForm() previously)
* modules: ResourceLoader modules, if any, that should be added to the output of the
page
* modulestyles: ResourceLoader style modules, if any, that should be added to th
output of the page
* headitems: Head items that should be added to the output (see OutputPage::addHeadItems)
Mostly you shouldn't need to handle the response of getFormInformation() anymore, as there's
a new function, addFormToOutput(), which takes an instance of OutputPage as a first parameter
and handles the response of getFormInformation for you (adds all information to the given
OutputPage instance, if they're provided).
Bug: T141300
Depends-On: I433afd124b57526caa13a540cda48ba2b99a9bde
Change-Id: I25f344538052fc18993c43185fbd97804a7cfc81
2016-07-26 16:08:42 +00:00
|
|
|
|
$this->addFormToOutput( $out );
|
2015-01-12 17:03:40 +00:00
|
|
|
|
}
|
2014-05-30 14:33:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Insert the captcha prompt into an edit form.
|
|
|
|
|
* @param EditPage $editPage
|
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
public function editShowCaptcha( $editPage ) {
|
2014-05-30 14:33:05 +00:00
|
|
|
|
$context = $editPage->getArticle()->getContext();
|
|
|
|
|
$page = $editPage->getArticle()->getPage();
|
|
|
|
|
$out = $context->getOutput();
|
|
|
|
|
if ( isset( $page->ConfirmEdit_ActivateCaptcha ) ||
|
2015-08-12 01:25:44 +00:00
|
|
|
|
$this->shouldCheck( $page, '', '', $context )
|
2014-05-30 14:33:05 +00:00
|
|
|
|
) {
|
2019-05-18 19:27:16 +00:00
|
|
|
|
$out->addHTML( $this->getMessage( $this->action )->parseAsBlock() );
|
Remove getForm() and replace by getFormInformation()
This commit removes SimpleCaptcha::getForm() and replaces it by its more informative
counterpart getFormInformation(), which returns an array, which provides some
more information about the form than only the html.
The information included in the array is:
* html: The HTML of the CAPTCHA form (this is the same as what you expected from
getForm() previously)
* modules: ResourceLoader modules, if any, that should be added to the output of the
page
* modulestyles: ResourceLoader style modules, if any, that should be added to th
output of the page
* headitems: Head items that should be added to the output (see OutputPage::addHeadItems)
Mostly you shouldn't need to handle the response of getFormInformation() anymore, as there's
a new function, addFormToOutput(), which takes an instance of OutputPage as a first parameter
and handles the response of getFormInformation for you (adds all information to the given
OutputPage instance, if they're provided).
Bug: T141300
Depends-On: I433afd124b57526caa13a540cda48ba2b99a9bde
Change-Id: I25f344538052fc18993c43185fbd97804a7cfc81
2016-07-26 16:08:42 +00:00
|
|
|
|
$this->addFormToOutput( $out );
|
2014-05-30 14:33:05 +00:00
|
|
|
|
}
|
|
|
|
|
unset( $page->ConfirmEdit_ActivateCaptcha );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Show a message asking the user to enter a captcha on edit
|
|
|
|
|
* The result will be treated as wiki text
|
|
|
|
|
*
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param string $action Action being performed
|
2016-04-25 20:58:18 +00:00
|
|
|
|
* @return Message
|
2007-11-12 07:42:25 +00:00
|
|
|
|
*/
|
2016-04-25 20:58:18 +00:00
|
|
|
|
public function getMessage( $action ) {
|
|
|
|
|
// one of captcha-edit, captcha-addurl, captcha-badlogin, captcha-createaccount,
|
|
|
|
|
// captcha-create, captcha-sendemail
|
|
|
|
|
$name = static::$messagePrefix . $action;
|
|
|
|
|
$msg = wfMessage( $name );
|
|
|
|
|
// obtain a more tailored message, if possible, otherwise, fall back to
|
|
|
|
|
// the default for edits
|
2017-08-16 08:37:58 +00:00
|
|
|
|
return $msg->isDisabled() ? wfMessage( static::$messagePrefix . 'edit' ) : $msg;
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
2010-04-10 21:26:03 +00:00
|
|
|
|
/**
|
|
|
|
|
* Inject whazawhoo
|
|
|
|
|
* @fixme if multiple thingies insert a header, could break
|
2019-11-19 03:28:05 +00:00
|
|
|
|
* @param HTMLForm $form
|
2010-04-10 21:26:03 +00:00
|
|
|
|
* @return bool true to keep running callbacks
|
|
|
|
|
*/
|
2019-11-19 03:28:05 +00:00
|
|
|
|
public function injectEmailUser( HTMLForm $form ) {
|
Remove getForm() and replace by getFormInformation()
This commit removes SimpleCaptcha::getForm() and replaces it by its more informative
counterpart getFormInformation(), which returns an array, which provides some
more information about the form than only the html.
The information included in the array is:
* html: The HTML of the CAPTCHA form (this is the same as what you expected from
getForm() previously)
* modules: ResourceLoader modules, if any, that should be added to the output of the
page
* modulestyles: ResourceLoader style modules, if any, that should be added to th
output of the page
* headitems: Head items that should be added to the output (see OutputPage::addHeadItems)
Mostly you shouldn't need to handle the response of getFormInformation() anymore, as there's
a new function, addFormToOutput(), which takes an instance of OutputPage as a first parameter
and handles the response of getFormInformation for you (adds all information to the given
OutputPage instance, if they're provided).
Bug: T141300
Depends-On: I433afd124b57526caa13a540cda48ba2b99a9bde
Change-Id: I25f344538052fc18993c43185fbd97804a7cfc81
2016-07-26 16:08:42 +00:00
|
|
|
|
$out = $form->getOutput();
|
|
|
|
|
$user = $form->getUser();
|
2017-08-14 15:39:20 +00:00
|
|
|
|
if ( $this->triggersCaptcha( CaptchaTriggers::SENDEMAIL ) ) {
|
2015-01-18 13:39:02 +00:00
|
|
|
|
$this->action = 'sendemail';
|
2018-05-18 18:29:00 +00:00
|
|
|
|
if ( $this->canSkipCaptcha( $user, $form->getConfig() ) ) {
|
2010-04-10 21:26:03 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
Remove getForm() and replace by getFormInformation()
This commit removes SimpleCaptcha::getForm() and replaces it by its more informative
counterpart getFormInformation(), which returns an array, which provides some
more information about the form than only the html.
The information included in the array is:
* html: The HTML of the CAPTCHA form (this is the same as what you expected from
getForm() previously)
* modules: ResourceLoader modules, if any, that should be added to the output of the
page
* modulestyles: ResourceLoader style modules, if any, that should be added to th
output of the page
* headitems: Head items that should be added to the output (see OutputPage::addHeadItems)
Mostly you shouldn't need to handle the response of getFormInformation() anymore, as there's
a new function, addFormToOutput(), which takes an instance of OutputPage as a first parameter
and handles the response of getFormInformation for you (adds all information to the given
OutputPage instance, if they're provided).
Bug: T141300
Depends-On: I433afd124b57526caa13a540cda48ba2b99a9bde
Change-Id: I25f344538052fc18993c43185fbd97804a7cfc81
2016-07-26 16:08:42 +00:00
|
|
|
|
$formInformation = $this->getFormInformation();
|
|
|
|
|
$formMetainfo = $formInformation;
|
|
|
|
|
unset( $formMetainfo['html'] );
|
|
|
|
|
$this->addFormInformationToOutput( $out, $formMetainfo );
|
2011-11-23 19:09:57 +00:00
|
|
|
|
$form->addFooterText(
|
2010-04-10 21:26:03 +00:00
|
|
|
|
"<div class='captcha'>" .
|
2018-10-26 18:43:44 +00:00
|
|
|
|
$this->getMessage( 'sendemail' )->parseAsBlock() .
|
Remove getForm() and replace by getFormInformation()
This commit removes SimpleCaptcha::getForm() and replaces it by its more informative
counterpart getFormInformation(), which returns an array, which provides some
more information about the form than only the html.
The information included in the array is:
* html: The HTML of the CAPTCHA form (this is the same as what you expected from
getForm() previously)
* modules: ResourceLoader modules, if any, that should be added to the output of the
page
* modulestyles: ResourceLoader style modules, if any, that should be added to th
output of the page
* headitems: Head items that should be added to the output (see OutputPage::addHeadItems)
Mostly you shouldn't need to handle the response of getFormInformation() anymore, as there's
a new function, addFormToOutput(), which takes an instance of OutputPage as a first parameter
and handles the response of getFormInformation for you (adds all information to the given
OutputPage instance, if they're provided).
Bug: T141300
Depends-On: I433afd124b57526caa13a540cda48ba2b99a9bde
Change-Id: I25f344538052fc18993c43185fbd97804a7cfc81
2016-07-26 16:08:42 +00:00
|
|
|
|
$formInformation['html'] .
|
2010-04-10 21:26:03 +00:00
|
|
|
|
"</div>\n" );
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
/**
|
|
|
|
|
* Increase bad login counter after a failed login.
|
|
|
|
|
* The user might be required to solve a captcha if the count is high.
|
|
|
|
|
* @param string $username
|
|
|
|
|
* TODO use Throttler
|
|
|
|
|
*/
|
|
|
|
|
public function increaseBadLoginCounter( $username ) {
|
2017-08-14 15:39:20 +00:00
|
|
|
|
global $wgCaptchaBadLoginExpiration, $wgCaptchaBadLoginPerUserExpiration;
|
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
$cache = ObjectCache::getLocalClusterInstance();
|
|
|
|
|
|
2017-08-14 15:39:20 +00:00
|
|
|
|
if ( $this->triggersCaptcha( CaptchaTriggers::BAD_LOGIN ) ) {
|
2019-07-18 08:17:55 +00:00
|
|
|
|
$key = $this->badLoginKey( $cache );
|
2019-08-09 23:50:55 +00:00
|
|
|
|
$cache->incrWithInit( $key, $wgCaptchaBadLoginExpiration );
|
2016-04-25 20:58:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-14 15:39:20 +00:00
|
|
|
|
if ( $this->triggersCaptcha( CaptchaTriggers::BAD_LOGIN_PER_USER ) && $username ) {
|
2019-07-18 08:17:55 +00:00
|
|
|
|
$key = $this->badLoginPerUserKey( $username, $cache );
|
2019-08-09 23:50:55 +00:00
|
|
|
|
$cache->incrWithInit( $key, $wgCaptchaBadLoginPerUserExpiration );
|
2016-04-25 20:58:18 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reset bad login counter after a successful login.
|
|
|
|
|
* @param string $username
|
|
|
|
|
*/
|
|
|
|
|
public function resetBadLoginCounter( $username ) {
|
2017-08-14 15:39:20 +00:00
|
|
|
|
if ( $this->triggersCaptcha( CaptchaTriggers::BAD_LOGIN_PER_USER ) && $username ) {
|
2016-04-25 20:58:18 +00:00
|
|
|
|
$cache = ObjectCache::getLocalClusterInstance();
|
2019-07-18 08:17:55 +00:00
|
|
|
|
$cache->delete( $this->badLoginPerUserKey( $username, $cache ) );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2009-07-19 15:13:01 +00:00
|
|
|
|
|
2007-11-12 07:42:25 +00:00
|
|
|
|
/**
|
|
|
|
|
* Check if a bad login has already been registered for this
|
|
|
|
|
* IP address. If so, require a captcha.
|
|
|
|
|
* @return bool
|
2019-02-06 12:04:20 +00:00
|
|
|
|
* @private
|
2007-11-12 07:42:25 +00:00
|
|
|
|
*/
|
2016-04-25 20:58:18 +00:00
|
|
|
|
public function isBadLoginTriggered() {
|
2017-08-14 15:39:20 +00:00
|
|
|
|
global $wgCaptchaBadLoginAttempts;
|
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
$cache = ObjectCache::getLocalClusterInstance();
|
2017-08-14 15:39:20 +00:00
|
|
|
|
return $this->triggersCaptcha( CaptchaTriggers::BAD_LOGIN )
|
2019-07-18 08:17:55 +00:00
|
|
|
|
&& (int)$cache->get( $this->badLoginKey( $cache ) ) >= $wgCaptchaBadLoginAttempts;
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
2009-07-19 15:13:01 +00:00
|
|
|
|
|
2016-02-29 01:26:17 +00:00
|
|
|
|
/**
|
|
|
|
|
* Is the per-user captcha triggered?
|
|
|
|
|
*
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param User|string $u User object, or name
|
2017-09-01 04:47:36 +00:00
|
|
|
|
* @return bool|null False: no, null: no, but it will be triggered next time
|
2016-02-29 01:26:17 +00:00
|
|
|
|
*/
|
2016-04-25 20:58:18 +00:00
|
|
|
|
public function isBadLoginPerUserTriggered( $u ) {
|
2017-08-14 15:39:20 +00:00
|
|
|
|
global $wgCaptchaBadLoginPerUserAttempts;
|
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
$cache = ObjectCache::getLocalClusterInstance();
|
2016-02-29 01:26:17 +00:00
|
|
|
|
|
|
|
|
|
if ( is_object( $u ) ) {
|
|
|
|
|
$u = $u->getName();
|
|
|
|
|
}
|
2019-07-18 08:17:55 +00:00
|
|
|
|
$badLoginPerUserKey = $this->badLoginPerUserKey( $u, $cache );
|
2017-08-14 15:39:20 +00:00
|
|
|
|
return $this->triggersCaptcha( CaptchaTriggers::BAD_LOGIN_PER_USER )
|
2019-07-18 08:17:55 +00:00
|
|
|
|
&& (int)$cache->get( $badLoginPerUserKey ) >= $wgCaptchaBadLoginPerUserAttempts;
|
2016-02-29 01:26:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-05-09 14:00:13 +00:00
|
|
|
|
/**
|
2016-03-07 16:29:53 +00:00
|
|
|
|
* Check if the current IP is allowed to skip captchas. This checks
|
|
|
|
|
* the whitelist from two sources.
|
|
|
|
|
* 1) From the server-side config array $wgCaptchaWhitelistIP
|
|
|
|
|
* 2) From the local [[MediaWiki:Captcha-ip-whitelist]] message
|
|
|
|
|
*
|
|
|
|
|
* @return bool true if whitelisted, false if not
|
2009-05-09 14:00:13 +00:00
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
private function isIPWhitelisted() {
|
2016-03-07 16:29:53 +00:00
|
|
|
|
global $wgCaptchaWhitelistIP, $wgRequest;
|
|
|
|
|
$ip = $wgRequest->getIP();
|
2012-01-12 08:58:40 +00:00
|
|
|
|
|
2009-07-19 15:13:01 +00:00
|
|
|
|
if ( $wgCaptchaWhitelistIP ) {
|
2020-01-25 05:38:50 +00:00
|
|
|
|
if ( IPUtils::isInRanges( $ip, $wgCaptchaWhitelistIP ) ) {
|
2016-03-07 16:29:53 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-01-12 08:58:40 +00:00
|
|
|
|
|
2016-03-07 16:29:53 +00:00
|
|
|
|
$whitelistMsg = wfMessage( 'captcha-ip-whitelist' )->inContentLanguage();
|
|
|
|
|
if ( !$whitelistMsg->isDisabled() ) {
|
|
|
|
|
$whitelistedIPs = $this->getWikiIPWhitelist( $whitelistMsg );
|
2020-01-25 05:38:50 +00:00
|
|
|
|
if ( IPUtils::isInRanges( $ip, $whitelistedIPs ) ) {
|
2016-03-07 16:29:53 +00:00
|
|
|
|
return true;
|
2009-05-09 14:00:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-07 16:29:53 +00:00
|
|
|
|
|
2009-05-09 14:00:13 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2009-07-19 15:13:01 +00:00
|
|
|
|
|
2016-03-07 16:29:53 +00:00
|
|
|
|
/**
|
|
|
|
|
* Get the on-wiki IP whitelist stored in [[MediaWiki:Captcha-ip-whitelist]]
|
|
|
|
|
* page from cache if possible.
|
|
|
|
|
*
|
|
|
|
|
* @param Message $msg whitelist Message on wiki
|
|
|
|
|
* @return array whitelisted IP addresses or IP ranges, empty array if no whitelist
|
|
|
|
|
*/
|
|
|
|
|
private function getWikiIPWhitelist( Message $msg ) {
|
2019-07-03 12:43:23 +00:00
|
|
|
|
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
2016-03-07 16:29:53 +00:00
|
|
|
|
$cacheKey = $cache->makeKey( 'confirmedit', 'ipwhitelist' );
|
|
|
|
|
|
|
|
|
|
$cachedWhitelist = $cache->get( $cacheKey );
|
|
|
|
|
if ( $cachedWhitelist === false ) {
|
|
|
|
|
// Could not retrieve from cache so build the whitelist directly
|
|
|
|
|
// from the wikipage
|
|
|
|
|
$whitelist = $this->buildValidIPs(
|
|
|
|
|
explode( "\n", $msg->plain() )
|
|
|
|
|
);
|
|
|
|
|
// And then store it in cache for one day. This cache is cleared on
|
|
|
|
|
// modifications to the whitelist page.
|
|
|
|
|
// @see ConfirmEditHooks::onPageContentSaveComplete()
|
|
|
|
|
$cache->set( $cacheKey, $whitelist, 86400 );
|
|
|
|
|
} else {
|
|
|
|
|
// Whitelist from the cache
|
|
|
|
|
$whitelist = $cachedWhitelist;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $whitelist;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* From a list of unvalidated input, get all the valid
|
|
|
|
|
* IP addresses and IP ranges from it.
|
|
|
|
|
*
|
|
|
|
|
* Note that only lines with just the IP address or IP range is considered
|
|
|
|
|
* as valid. Whitespace is allowed but if there is any other character on
|
|
|
|
|
* the line, it's not considered as a valid entry.
|
|
|
|
|
*
|
|
|
|
|
* @param string[] $input
|
|
|
|
|
* @return string[] of valid IP addresses and IP ranges
|
|
|
|
|
*/
|
|
|
|
|
private function buildValidIPs( array $input ) {
|
|
|
|
|
// Remove whitespace and blank lines first
|
|
|
|
|
$ips = array_map( 'trim', $input );
|
|
|
|
|
$ips = array_filter( $ips );
|
|
|
|
|
|
2016-05-09 23:41:17 +00:00
|
|
|
|
$validIPs = [];
|
2016-03-07 16:29:53 +00:00
|
|
|
|
foreach ( $ips as $ip ) {
|
2020-01-25 05:38:50 +00:00
|
|
|
|
if ( IPUtils::isIPAddress( $ip ) ) {
|
2016-03-07 16:29:53 +00:00
|
|
|
|
$validIPs[] = $ip;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $validIPs;
|
|
|
|
|
}
|
|
|
|
|
|
2007-11-12 07:42:25 +00:00
|
|
|
|
/**
|
|
|
|
|
* Internal cache key for badlogin checks.
|
2019-07-18 08:17:55 +00:00
|
|
|
|
* @param BagOStuff $cache
|
2007-11-12 07:42:25 +00:00
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2019-07-18 08:17:55 +00:00
|
|
|
|
private function badLoginKey( BagOStuff $cache ) {
|
2011-12-13 21:24:03 +00:00
|
|
|
|
global $wgRequest;
|
2012-09-02 12:26:25 +00:00
|
|
|
|
$ip = $wgRequest->getIP();
|
2019-07-18 08:17:55 +00:00
|
|
|
|
|
|
|
|
|
return $cache->makeGlobalKey( 'captcha', 'badlogin', 'ip', $ip );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
2009-07-19 15:13:01 +00:00
|
|
|
|
|
2017-02-17 13:09:34 +00:00
|
|
|
|
/**
|
2016-02-29 01:26:17 +00:00
|
|
|
|
* Cache key for badloginPerUser checks.
|
2017-02-17 13:09:34 +00:00
|
|
|
|
* @param string $username
|
2019-07-18 08:17:55 +00:00
|
|
|
|
* @param BagOStuff $cache
|
2016-02-29 01:26:17 +00:00
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2019-07-18 08:17:55 +00:00
|
|
|
|
private function badLoginPerUserKey( $username, BagOStuff $cache ) {
|
2016-02-29 01:26:17 +00:00
|
|
|
|
$username = User::getCanonicalName( $username, 'usable' ) ?: $username;
|
2019-07-18 08:17:55 +00:00
|
|
|
|
|
|
|
|
|
return $cache->makeGlobalKey(
|
|
|
|
|
'captcha', 'badlogin', 'user', md5( $username )
|
|
|
|
|
);
|
2016-02-29 01:26:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2007-11-12 07:42:25 +00:00
|
|
|
|
/**
|
|
|
|
|
* Check if the submitted form matches the captcha session data provided
|
|
|
|
|
* by the plugin when the form was generated.
|
|
|
|
|
*
|
|
|
|
|
* Override this!
|
|
|
|
|
*
|
2008-01-24 21:42:21 +00:00
|
|
|
|
* @param string $answer
|
2007-11-12 07:42:25 +00:00
|
|
|
|
* @param array $info
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
protected function keyMatch( $answer, $info ) {
|
2008-01-24 21:42:21 +00:00
|
|
|
|
return $answer == $info['answer'];
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ----------------------------------
|
|
|
|
|
|
|
|
|
|
/**
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
* @param Title $title
|
2007-11-12 07:42:25 +00:00
|
|
|
|
* @param string $action (edit/create/addurl...)
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
* @return bool true if action triggers captcha on $title's namespace
|
2017-08-14 15:39:20 +00:00
|
|
|
|
* @deprecated since 1.5.1 Use triggersCaptcha instead
|
|
|
|
|
*/
|
|
|
|
|
public function captchaTriggers( $title, $action ) {
|
|
|
|
|
return $this->triggersCaptcha( $action, $title );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks, whether the passed action should trigger a CAPTCHA. The optional $title parameter
|
|
|
|
|
* will be used to check namespace specific CAPTCHA triggers.
|
|
|
|
|
*
|
|
|
|
|
* @param string $action The CAPTCHA trigger to check (see CaptchaTriggers for ConfirmEdit
|
|
|
|
|
* built-in triggers)
|
|
|
|
|
* @param Title|null $title An optional Title object, if the namespace specific triggers
|
|
|
|
|
* should be checked, too.
|
|
|
|
|
* @return bool True, if the action should trigger a CAPTCHA, false otherwise
|
2007-11-12 07:42:25 +00:00
|
|
|
|
*/
|
2017-08-14 15:39:20 +00:00
|
|
|
|
public function triggersCaptcha( $action, $title = null ) {
|
2009-07-19 15:13:01 +00:00
|
|
|
|
global $wgCaptchaTriggers, $wgCaptchaTriggersOnNamespace;
|
2017-08-14 15:39:20 +00:00
|
|
|
|
|
|
|
|
|
$result = false;
|
|
|
|
|
$triggers = $wgCaptchaTriggers;
|
|
|
|
|
$attributeCaptchaTriggers = ExtensionRegistry::getInstance()
|
|
|
|
|
->getAttribute( CaptchaTriggers::EXT_REG_ATTRIBUTE_NAME );
|
|
|
|
|
if ( is_array( $attributeCaptchaTriggers ) ) {
|
|
|
|
|
$triggers += $attributeCaptchaTriggers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( isset( $triggers[$action] ) ) {
|
|
|
|
|
$result = $triggers[$action];
|
2017-07-08 13:55:41 +00:00
|
|
|
|
}
|
2007-11-12 07:42:25 +00:00
|
|
|
|
|
2017-08-14 15:39:20 +00:00
|
|
|
|
if (
|
|
|
|
|
$title !== null &&
|
|
|
|
|
isset( $wgCaptchaTriggersOnNamespace[$title->getNamespace()][$action] )
|
|
|
|
|
) {
|
|
|
|
|
$result = $wgCaptchaTriggersOnNamespace[$title->getNamespace()][$action];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $result;
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
* @param WikiPage $page
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param Content|string $content
|
|
|
|
|
* @param string $section
|
2015-08-12 01:25:44 +00:00
|
|
|
|
* @param IContextSource $context
|
2018-05-26 01:41:30 +00:00
|
|
|
|
* @param string|null $oldtext The content of the revision prior to $content When
|
2015-03-25 22:17:08 +00:00
|
|
|
|
* null this will be loaded from the database.
|
2007-11-12 07:42:25 +00:00
|
|
|
|
* @return bool true if the captcha should run
|
|
|
|
|
*/
|
2018-07-17 15:25:35 +00:00
|
|
|
|
public function shouldCheck( WikiPage $page, $content, $section, $context, $oldtext = null ) {
|
2015-08-12 01:25:44 +00:00
|
|
|
|
if ( !$context instanceof IContextSource ) {
|
|
|
|
|
$context = RequestContext::getMain();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$request = $context->getRequest();
|
|
|
|
|
$user = $context->getUser();
|
|
|
|
|
|
2018-05-18 18:29:00 +00:00
|
|
|
|
if ( $this->canSkipCaptcha( $user, $context->getConfig() ) ) {
|
2015-06-22 19:24:21 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
$title = $page->getTitle();
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$this->trigger = '';
|
2014-12-14 21:16:57 +00:00
|
|
|
|
|
2015-08-12 01:25:44 +00:00
|
|
|
|
if ( $content instanceof Content ) {
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
if ( $content->getModel() == CONTENT_MODEL_WIKITEXT ) {
|
|
|
|
|
$newtext = $content->getNativeData();
|
|
|
|
|
} else {
|
|
|
|
|
$newtext = null;
|
|
|
|
|
}
|
2015-02-27 18:29:04 +00:00
|
|
|
|
$isEmpty = $content->isEmpty();
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
} else {
|
|
|
|
|
$newtext = $content;
|
2015-02-27 18:29:04 +00:00
|
|
|
|
$isEmpty = $content === '';
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
}
|
2007-11-12 07:42:25 +00:00
|
|
|
|
|
2017-08-14 15:39:20 +00:00
|
|
|
|
if ( $this->triggersCaptcha( 'edit', $title ) ) {
|
2007-11-12 07:42:25 +00:00
|
|
|
|
// Check on all edits
|
|
|
|
|
$this->trigger = sprintf( "edit trigger by '%s' at [[%s]]",
|
2015-08-12 01:25:44 +00:00
|
|
|
|
$user->getName(),
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$title->getPrefixedText() );
|
|
|
|
|
$this->action = 'edit';
|
|
|
|
|
wfDebug( "ConfirmEdit: checking all edits...\n" );
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-14 15:39:20 +00:00
|
|
|
|
if ( $this->triggersCaptcha( 'create', $title ) && !$title->exists() ) {
|
2009-07-19 15:13:01 +00:00
|
|
|
|
// Check if creating a page
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$this->trigger = sprintf( "Create trigger by '%s' at [[%s]]",
|
2015-08-12 01:25:44 +00:00
|
|
|
|
$user->getName(),
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$title->getPrefixedText() );
|
|
|
|
|
$this->action = 'create';
|
|
|
|
|
wfDebug( "ConfirmEdit: checking on page creation...\n" );
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-28 15:52:04 +00:00
|
|
|
|
// The following checks are expensive and should be done only,
|
|
|
|
|
// if we can assume, that the edit will be saved
|
2015-08-12 01:25:44 +00:00
|
|
|
|
if ( !$request->wasPosted() ) {
|
2015-10-28 15:52:04 +00:00
|
|
|
|
wfDebug(
|
|
|
|
|
"ConfirmEdit: request not posted, assuming that no content will be saved -> no CAPTCHA check"
|
|
|
|
|
);
|
2015-08-12 01:25:44 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-14 15:39:20 +00:00
|
|
|
|
if ( !$isEmpty && $this->triggersCaptcha( 'addurl', $title ) ) {
|
2007-11-12 07:42:25 +00:00
|
|
|
|
// Only check edits that add URLs
|
2015-08-12 01:25:44 +00:00
|
|
|
|
if ( $content instanceof Content ) {
|
2007-11-12 07:42:25 +00:00
|
|
|
|
// Get links from the database
|
|
|
|
|
$oldLinks = $this->getLinksFromTracker( $title );
|
|
|
|
|
// Share a parse operation with Article::doEdit()
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
$editInfo = $page->prepareContentForEdit( $content );
|
|
|
|
|
if ( $editInfo->output ) {
|
|
|
|
|
$newLinks = array_keys( $editInfo->output->getExternalLinks() );
|
|
|
|
|
} else {
|
2016-05-09 23:41:17 +00:00
|
|
|
|
$newLinks = [];
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
}
|
2007-11-12 07:42:25 +00:00
|
|
|
|
} else {
|
|
|
|
|
// Get link changes in the slowest way known to man
|
2019-03-12 19:08:56 +00:00
|
|
|
|
if ( $oldtext === null ) {
|
|
|
|
|
$oldtext = $this->loadText( $title, $section );
|
|
|
|
|
}
|
2015-04-03 18:14:36 +00:00
|
|
|
|
$oldLinks = $this->findLinks( $title, $oldtext );
|
|
|
|
|
$newLinks = $this->findLinks( $title, $newtext );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-01 03:51:00 +00:00
|
|
|
|
$unknownLinks = array_filter( $newLinks, [ $this, 'filterLink' ] );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$addedLinks = array_diff( $unknownLinks, $oldLinks );
|
|
|
|
|
$numLinks = count( $addedLinks );
|
|
|
|
|
|
2009-07-19 15:13:01 +00:00
|
|
|
|
if ( $numLinks > 0 ) {
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$this->trigger = sprintf( "%dx url trigger by '%s' at [[%s]]: %s",
|
|
|
|
|
$numLinks,
|
2015-08-12 01:25:44 +00:00
|
|
|
|
$user->getName(),
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$title->getPrefixedText(),
|
|
|
|
|
implode( ", ", $addedLinks ) );
|
|
|
|
|
$this->action = 'addurl';
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-02 22:56:48 +00:00
|
|
|
|
global $wgCaptchaRegexes;
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
if ( $newtext !== null && $wgCaptchaRegexes ) {
|
2015-08-12 01:39:12 +00:00
|
|
|
|
if ( !is_array( $wgCaptchaRegexes ) ) {
|
2015-10-28 15:52:04 +00:00
|
|
|
|
throw new UnexpectedValueException(
|
|
|
|
|
'$wgCaptchaRegexes is required to be an array, ' . gettype( $wgCaptchaRegexes ) . ' given.'
|
|
|
|
|
);
|
2015-08-12 01:39:12 +00:00
|
|
|
|
}
|
2012-09-26 21:15:39 +00:00
|
|
|
|
// Custom regex checks. Reuse $oldtext if set above.
|
2019-03-12 19:08:56 +00:00
|
|
|
|
if ( $oldtext === null ) {
|
|
|
|
|
$oldtext = $this->loadText( $title, $section );
|
|
|
|
|
}
|
2007-11-12 07:42:25 +00:00
|
|
|
|
|
2009-07-19 15:13:01 +00:00
|
|
|
|
foreach ( $wgCaptchaRegexes as $regex ) {
|
2016-05-09 23:41:17 +00:00
|
|
|
|
$newMatches = [];
|
2009-07-19 15:13:01 +00:00
|
|
|
|
if ( preg_match_all( $regex, $newtext, $newMatches ) ) {
|
2016-05-09 23:41:17 +00:00
|
|
|
|
$oldMatches = [];
|
2007-11-12 07:42:25 +00:00
|
|
|
|
preg_match_all( $regex, $oldtext, $oldMatches );
|
|
|
|
|
|
|
|
|
|
$addedMatches = array_diff( $newMatches[0], $oldMatches[0] );
|
|
|
|
|
|
|
|
|
|
$numHits = count( $addedMatches );
|
2009-07-19 15:13:01 +00:00
|
|
|
|
if ( $numHits > 0 ) {
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$this->trigger = sprintf( "%dx %s at [[%s]]: %s",
|
|
|
|
|
$numHits,
|
|
|
|
|
$regex,
|
2015-08-12 01:25:44 +00:00
|
|
|
|
$user->getName(),
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$title->getPrefixedText(),
|
|
|
|
|
implode( ", ", $addedMatches ) );
|
|
|
|
|
$this->action = 'edit';
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Filter callback function for URL whitelisting
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param string $url string to check
|
2007-11-12 07:42:25 +00:00
|
|
|
|
* @return bool true if unknown, false if whitelisted
|
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
private function filterLink( $url ) {
|
2007-11-12 07:42:25 +00:00
|
|
|
|
global $wgCaptchaWhitelist;
|
2013-11-23 17:59:53 +00:00
|
|
|
|
static $regexes = null;
|
2007-11-12 07:42:25 +00:00
|
|
|
|
|
2013-11-23 17:59:53 +00:00
|
|
|
|
if ( $regexes === null ) {
|
|
|
|
|
$source = wfMessage( 'captcha-addurl-whitelist' )->inContentLanguage();
|
2007-11-12 07:42:25 +00:00
|
|
|
|
|
2013-11-23 17:59:53 +00:00
|
|
|
|
$regexes = $source->isDisabled()
|
2016-05-09 23:41:17 +00:00
|
|
|
|
? []
|
2013-11-23 17:59:53 +00:00
|
|
|
|
: $this->buildRegexes( explode( "\n", $source->plain() ) );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
|
2013-11-23 17:59:53 +00:00
|
|
|
|
if ( $wgCaptchaWhitelist !== false ) {
|
|
|
|
|
array_unshift( $regexes, $wgCaptchaWhitelist );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ( $regexes as $regex ) {
|
|
|
|
|
if ( preg_match( $regex, $url ) ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Build regex from whitelist
|
2019-05-23 19:16:12 +00:00
|
|
|
|
* @param string[] $lines string from [[MediaWiki:Captcha-addurl-whitelist]]
|
|
|
|
|
* @return string[] Regexes
|
2019-02-06 12:04:20 +00:00
|
|
|
|
* @private
|
2007-11-12 07:42:25 +00:00
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
private function buildRegexes( $lines ) {
|
2007-11-12 07:42:25 +00:00
|
|
|
|
# Code duplicated from the SpamBlacklist extension (r19197)
|
2013-11-23 17:59:53 +00:00
|
|
|
|
# and later modified.
|
2007-11-12 07:42:25 +00:00
|
|
|
|
|
|
|
|
|
# Strip comments and whitespace, then remove blanks
|
|
|
|
|
$lines = array_filter( array_map( 'trim', preg_replace( '/#.*$/', '', $lines ) ) );
|
|
|
|
|
|
|
|
|
|
# No lines, don't make a regex which will match everything
|
|
|
|
|
if ( count( $lines ) == 0 ) {
|
|
|
|
|
wfDebug( "No lines\n" );
|
2016-05-09 23:41:17 +00:00
|
|
|
|
return [];
|
2007-11-12 07:42:25 +00:00
|
|
|
|
} else {
|
|
|
|
|
# Make regex
|
|
|
|
|
# It's faster using the S modifier even though it will usually only be run once
|
2009-07-19 15:13:01 +00:00
|
|
|
|
// $regex = 'http://+[a-z0-9_\-.]*(' . implode( '|', $lines ) . ')';
|
|
|
|
|
// return '/' . str_replace( '/', '\/', preg_replace('|\\\*/|', '/', $regex) ) . '/Si';
|
2016-05-09 23:41:17 +00:00
|
|
|
|
$regexes = [];
|
|
|
|
|
$regexStart = [
|
2014-10-22 14:10:37 +00:00
|
|
|
|
'normal' => '/^(?:https?:)?\/\/+[a-z0-9_\-.]*(?:',
|
2013-11-23 17:59:53 +00:00
|
|
|
|
'noprotocol' => '/^(?:',
|
2016-05-09 23:41:17 +00:00
|
|
|
|
];
|
|
|
|
|
$regexEnd = [
|
2013-11-23 17:59:53 +00:00
|
|
|
|
'normal' => ')/Si',
|
|
|
|
|
'noprotocol' => ')/Si',
|
2016-05-09 23:41:17 +00:00
|
|
|
|
];
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$regexMax = 4096;
|
2016-05-09 23:41:17 +00:00
|
|
|
|
$build = [];
|
2009-07-19 15:13:01 +00:00
|
|
|
|
foreach ( $lines as $line ) {
|
2013-11-23 17:59:53 +00:00
|
|
|
|
# Extract flags from the line
|
2016-05-09 23:41:17 +00:00
|
|
|
|
$options = [];
|
2013-11-23 17:59:53 +00:00
|
|
|
|
if ( preg_match( '/^(.*?)\s*<([^<>]*)>$/', $line, $matches ) ) {
|
|
|
|
|
if ( $matches[1] === '' ) {
|
|
|
|
|
wfDebug( "Line with empty regex\n" );
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$line = $matches[1];
|
|
|
|
|
$opts = preg_split( '/\s*\|\s*/', trim( $matches[2] ) );
|
|
|
|
|
foreach ( $opts as $opt ) {
|
|
|
|
|
$opt = strtolower( $opt );
|
|
|
|
|
if ( $opt == 'noprotocol' ) {
|
|
|
|
|
$options['noprotocol'] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$key = isset( $options['noprotocol'] ) ? 'noprotocol' : 'normal';
|
|
|
|
|
|
2007-11-12 07:42:25 +00:00
|
|
|
|
// FIXME: not very robust size check, but should work. :)
|
2013-11-23 17:59:53 +00:00
|
|
|
|
if ( !isset( $build[$key] ) ) {
|
|
|
|
|
$build[$key] = $line;
|
|
|
|
|
} elseif ( strlen( $build[$key] ) + strlen( $line ) > $regexMax ) {
|
|
|
|
|
$regexes[] = $regexStart[$key] .
|
|
|
|
|
str_replace( '/', '\/', preg_replace( '|\\\*/|', '/', $build[$key] ) ) .
|
|
|
|
|
$regexEnd[$key];
|
|
|
|
|
$build[$key] = $line;
|
2007-11-12 07:42:25 +00:00
|
|
|
|
} else {
|
2013-11-23 17:59:53 +00:00
|
|
|
|
$build[$key] .= '|' . $line;
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2013-11-23 17:59:53 +00:00
|
|
|
|
foreach ( $build as $key => $value ) {
|
|
|
|
|
$regexes[] = $regexStart[$key] .
|
2020-02-03 14:36:31 +00:00
|
|
|
|
str_replace( '/', '\/', preg_replace( '|\\\*/|', '/', $value ) ) .
|
2013-11-23 17:59:53 +00:00
|
|
|
|
$regexEnd[$key];
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
return $regexes;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Load external links from the externallinks table
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param Title $title
|
2017-02-17 13:09:34 +00:00
|
|
|
|
* @return array
|
2007-11-12 07:42:25 +00:00
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
private function getLinksFromTracker( $title ) {
|
2017-09-24 05:22:19 +00:00
|
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2018-06-15 21:08:14 +00:00
|
|
|
|
// should be zero queries
|
|
|
|
|
$id = $title->getArticleID();
|
2016-05-09 23:41:17 +00:00
|
|
|
|
$res = $dbr->select( 'externallinks', [ 'el_to' ],
|
|
|
|
|
[ 'el_from' => $id ], __METHOD__ );
|
|
|
|
|
$links = [];
|
2010-10-29 21:30:20 +00:00
|
|
|
|
foreach ( $res as $row ) {
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$links[] = $row->el_to;
|
|
|
|
|
}
|
|
|
|
|
return $links;
|
2008-02-28 17:42:23 +00:00
|
|
|
|
}
|
2009-07-19 15:13:01 +00:00
|
|
|
|
|
2007-11-12 07:42:25 +00:00
|
|
|
|
/**
|
2016-06-16 15:54:54 +00:00
|
|
|
|
* Backend function for confirmEditMerged()
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
* @param WikiPage $page
|
2019-05-23 19:16:12 +00:00
|
|
|
|
* @param Content|string $newtext
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param string $section
|
2015-08-12 01:25:44 +00:00
|
|
|
|
* @param IContextSource $context
|
2020-01-16 01:19:32 +00:00
|
|
|
|
* @param User $user
|
2008-02-28 17:42:23 +00:00
|
|
|
|
* @return bool false if the CAPTCHA is rejected, true otherwise
|
2007-11-12 07:42:25 +00:00
|
|
|
|
*/
|
2020-01-16 01:19:32 +00:00
|
|
|
|
private function doConfirmEdit(
|
|
|
|
|
WikiPage $page,
|
|
|
|
|
$newtext,
|
|
|
|
|
$section,
|
|
|
|
|
IContextSource $context,
|
|
|
|
|
User $user
|
|
|
|
|
) {
|
|
|
|
|
global $wgRequest;
|
2015-08-12 01:25:44 +00:00
|
|
|
|
$request = $context->getRequest();
|
2016-04-25 20:58:18 +00:00
|
|
|
|
|
2015-11-07 00:25:25 +00:00
|
|
|
|
// FIXME: Stop using wgRequest in other parts of ConfirmEdit so we can
|
|
|
|
|
// stop having to duplicate code for it.
|
2015-08-12 01:25:44 +00:00
|
|
|
|
if ( $request->getVal( 'captchaid' ) ) {
|
|
|
|
|
$request->setVal( 'wpCaptchaId', $request->getVal( 'captchaid' ) );
|
2015-11-07 00:25:25 +00:00
|
|
|
|
$wgRequest->setVal( 'wpCaptchaId', $request->getVal( 'captchaid' ) );
|
2011-11-23 20:37:13 +00:00
|
|
|
|
}
|
2015-08-12 01:25:44 +00:00
|
|
|
|
if ( $request->getVal( 'captchaword' ) ) {
|
|
|
|
|
$request->setVal( 'wpCaptchaWord', $request->getVal( 'captchaword' ) );
|
2015-11-07 00:25:25 +00:00
|
|
|
|
$wgRequest->setVal( 'wpCaptchaWord', $request->getVal( 'captchaword' ) );
|
2011-11-23 20:37:13 +00:00
|
|
|
|
}
|
2015-08-12 01:25:44 +00:00
|
|
|
|
if ( $this->shouldCheck( $page, $newtext, $section, $context ) ) {
|
2020-01-16 01:19:32 +00:00
|
|
|
|
return $this->passCaptchaLimitedFromRequest( $wgRequest, $user );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
} else {
|
|
|
|
|
wfDebug( "ConfirmEdit: no need to show captcha.\n" );
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2008-02-28 17:42:23 +00:00
|
|
|
|
/**
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
* An efficient edit filter callback based on the text after section merging
|
|
|
|
|
* @param RequestContext $context
|
|
|
|
|
* @param Content $content
|
|
|
|
|
* @param Status $status
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param string $summary
|
|
|
|
|
* @param User $user
|
|
|
|
|
* @param bool $minorEdit
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
* @return bool
|
2008-02-28 17:42:23 +00:00
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
public function confirmEditMerged( $context, $content, $status, $summary, $user, $minorEdit ) {
|
2015-05-07 21:24:02 +00:00
|
|
|
|
if ( !$context->canUseWikiPage() ) {
|
|
|
|
|
// we check WikiPage only
|
|
|
|
|
// try to get an appropriate title for this page
|
|
|
|
|
$title = $context->getTitle();
|
|
|
|
|
if ( $title instanceof Title ) {
|
|
|
|
|
$title = $title->getFullText();
|
|
|
|
|
} else {
|
|
|
|
|
// otherwise it's an unknown page where this function is called from
|
|
|
|
|
$title = 'unknown';
|
|
|
|
|
}
|
2015-10-28 15:52:04 +00:00
|
|
|
|
// log this error, it could be a problem in another extension,
|
|
|
|
|
// edits should always have a WikiPage if
|
2015-05-07 21:24:02 +00:00
|
|
|
|
// they go through EditFilterMergedContent.
|
|
|
|
|
wfDebug( __METHOD__ . ': Skipped ConfirmEdit check: No WikiPage for title ' . $title );
|
|
|
|
|
return true;
|
|
|
|
|
}
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
$page = $context->getWikiPage();
|
2020-01-16 01:19:32 +00:00
|
|
|
|
if ( !$this->doConfirmEdit( $page, $content, '', $context, $user ) ) {
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
$status->value = EditPage::AS_HOOK_ERROR_EXPECTED;
|
2016-05-09 23:41:17 +00:00
|
|
|
|
$status->apiHookResult = [];
|
2015-01-12 17:42:39 +00:00
|
|
|
|
// give an error message for the user to know, what goes wrong here.
|
|
|
|
|
// this can't be done for addurl trigger, because this requires one "free" save
|
|
|
|
|
// for the user, which we don't know, when he did it.
|
|
|
|
|
if ( $this->action === 'edit' ) {
|
|
|
|
|
$status->fatal(
|
|
|
|
|
new RawMessage(
|
|
|
|
|
Html::element(
|
|
|
|
|
'div',
|
2016-05-09 23:41:17 +00:00
|
|
|
|
[ 'class' => 'errorbox' ],
|
2015-01-12 17:42:39 +00:00
|
|
|
|
$context->msg( 'captcha-edit-fail' )->text()
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
$this->addCaptchaAPI( $status->apiHookResult );
|
|
|
|
|
$page->ConfirmEdit_ActivateCaptcha = true;
|
2008-02-28 17:42:23 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2007-11-12 07:42:25 +00:00
|
|
|
|
|
2014-01-10 23:16:34 +00:00
|
|
|
|
/**
|
|
|
|
|
* Logic to check if we need to pass a captcha for the current user
|
|
|
|
|
* to create a new account, or not
|
|
|
|
|
*
|
2020-01-16 01:19:32 +00:00
|
|
|
|
* @param User $creatingUser
|
2014-01-10 23:16:34 +00:00
|
|
|
|
* @return bool true to show captcha, false to skip captcha
|
|
|
|
|
*/
|
2020-01-16 01:19:32 +00:00
|
|
|
|
public function needCreateAccountCaptcha( User $creatingUser ) {
|
2017-08-14 15:39:20 +00:00
|
|
|
|
if ( $this->triggersCaptcha( CaptchaTriggers::CREATE_ACCOUNT ) ) {
|
2018-05-18 18:29:00 +00:00
|
|
|
|
if ( $this->canSkipCaptcha( $creatingUser,
|
|
|
|
|
\MediaWiki\MediaWikiServices::getInstance()->getMainConfig() ) ) {
|
2007-11-12 07:42:25 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2014-01-10 23:16:34 +00:00
|
|
|
|
return true;
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
2014-01-10 23:16:34 +00:00
|
|
|
|
return false;
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
2009-07-19 15:13:01 +00:00
|
|
|
|
|
2010-04-10 21:26:03 +00:00
|
|
|
|
/**
|
2011-11-23 19:09:57 +00:00
|
|
|
|
* Check the captcha on Special:EmailUser
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param MailAddress $from
|
|
|
|
|
* @param MailAddress $to
|
|
|
|
|
* @param string $subject
|
|
|
|
|
* @param string $text
|
|
|
|
|
* @param string &$error
|
|
|
|
|
* @return bool true to continue saving, false to abort and show a captcha form
|
2010-04-10 21:26:03 +00:00
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
public function confirmEmailUser( $from, $to, $subject, $text, &$error ) {
|
2020-01-16 01:19:32 +00:00
|
|
|
|
global $wgRequest;
|
2016-04-25 20:58:18 +00:00
|
|
|
|
|
2020-01-16 01:19:32 +00:00
|
|
|
|
$user = RequestContext::getMain()->getUser();
|
2017-08-14 15:39:20 +00:00
|
|
|
|
if ( $this->triggersCaptcha( CaptchaTriggers::SENDEMAIL ) ) {
|
2020-01-16 01:19:32 +00:00
|
|
|
|
if ( $this->canSkipCaptcha( $user,
|
2018-05-18 18:29:00 +00:00
|
|
|
|
\MediaWiki\MediaWikiServices::getInstance()->getMainConfig() ) ) {
|
2010-04-10 21:26:03 +00:00
|
|
|
|
return true;
|
2017-02-17 13:09:34 +00:00
|
|
|
|
}
|
2011-11-23 19:09:57 +00:00
|
|
|
|
|
2010-04-10 21:26:03 +00:00
|
|
|
|
if ( defined( 'MW_API' ) ) {
|
|
|
|
|
# API mode
|
|
|
|
|
# Asking for captchas in the API is really silly
|
2016-11-03 19:11:34 +00:00
|
|
|
|
$error = Status::newFatal( 'captcha-disabledinapi' );
|
2010-04-10 21:26:03 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-01-16 01:19:32 +00:00
|
|
|
|
$this->trigger = "{$user->getName()} sending email";
|
|
|
|
|
if ( !$this->passCaptchaLimitedFromRequest( $wgRequest, $user ) ) {
|
2016-11-03 19:11:34 +00:00
|
|
|
|
$error = Status::newFatal( 'captcha-sendemail-fail' );
|
2010-04-10 21:26:03 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-16 15:42:51 +00:00
|
|
|
|
/**
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param ApiBase $module
|
2013-03-16 15:42:51 +00:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected function isAPICaptchaModule( $module ) {
|
2017-02-17 13:09:34 +00:00
|
|
|
|
return $module instanceof ApiEditPage;
|
2013-03-16 15:42:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-11-23 19:09:57 +00:00
|
|
|
|
/**
|
2019-11-19 03:28:05 +00:00
|
|
|
|
* @param ApiBase $module
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param array &$params
|
|
|
|
|
* @param int $flags
|
2011-11-23 19:09:57 +00:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2019-11-19 03:28:05 +00:00
|
|
|
|
public function apiGetAllowedParams( ApiBase $module, &$params, $flags ) {
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
if ( $this->isAPICaptchaModule( $module ) ) {
|
2016-09-20 18:49:32 +00:00
|
|
|
|
$params['captchaword'] = [
|
|
|
|
|
ApiBase::PARAM_HELP_MSG => 'captcha-apihelp-param-captchaword',
|
|
|
|
|
];
|
|
|
|
|
$params['captchaid'] = [
|
|
|
|
|
ApiBase::PARAM_HELP_MSG => 'captcha-apihelp-param-captchaid',
|
|
|
|
|
];
|
2011-11-23 19:09:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-02 22:56:02 +00:00
|
|
|
|
/**
|
|
|
|
|
* Checks, if the user reached the amount of false CAPTCHAs and give him some vacation
|
|
|
|
|
* or run self::passCaptcha() and clear counter if correct.
|
|
|
|
|
*
|
2016-04-25 20:58:18 +00:00
|
|
|
|
* @param WebRequest $request
|
|
|
|
|
* @param User $user
|
|
|
|
|
* @return bool
|
2015-01-02 22:56:02 +00:00
|
|
|
|
*/
|
2016-04-25 20:58:18 +00:00
|
|
|
|
public function passCaptchaLimitedFromRequest( WebRequest $request, User $user ) {
|
2016-05-17 17:55:28 +00:00
|
|
|
|
list( $index, $word ) = $this->getCaptchaParamsFromRequest( $request );
|
|
|
|
|
return $this->passCaptchaLimited( $index, $word, $user );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param WebRequest $request
|
|
|
|
|
* @return array [ captcha ID, captcha solution ]
|
|
|
|
|
*/
|
|
|
|
|
protected function getCaptchaParamsFromRequest( WebRequest $request ) {
|
2016-04-25 20:58:18 +00:00
|
|
|
|
$index = $request->getVal( 'wpCaptchaId' );
|
|
|
|
|
$word = $request->getVal( 'wpCaptchaWord' );
|
2016-05-17 17:55:28 +00:00
|
|
|
|
return [ $index, $word ];
|
2016-04-25 20:58:18 +00:00
|
|
|
|
}
|
2015-01-02 22:56:02 +00:00
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
/**
|
|
|
|
|
* Checks, if the user reached the amount of false CAPTCHAs and give him some vacation
|
|
|
|
|
* or run self::passCaptcha() and clear counter if correct.
|
|
|
|
|
*
|
|
|
|
|
* @param string $index Captcha idenitifier
|
|
|
|
|
* @param string $word Captcha solution
|
|
|
|
|
* @param User $user User for throttling captcha solving attempts
|
|
|
|
|
* @return bool
|
|
|
|
|
* @see self::passCaptcha()
|
|
|
|
|
*/
|
|
|
|
|
public function passCaptchaLimited( $index, $word, User $user ) {
|
2015-01-02 22:56:02 +00:00
|
|
|
|
// don't increase pingLimiter here, just check, if CAPTCHA limit exceeded
|
2016-04-25 20:58:18 +00:00
|
|
|
|
if ( $user->pingLimiter( 'badcaptcha', 0 ) ) {
|
2015-01-02 22:56:02 +00:00
|
|
|
|
// for debugging add an proper error message, the user just see an false captcha error message
|
2017-02-09 20:04:26 +00:00
|
|
|
|
$this->log( 'User reached RateLimit, preventing action' );
|
2015-01-02 22:56:02 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
if ( $this->passCaptcha( $index, $word ) ) {
|
2015-01-02 22:56:02 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// captcha was not solved: increase limit and return false
|
2016-04-25 20:58:18 +00:00
|
|
|
|
$user->pingLimiter( 'badcaptcha' );
|
2015-01-02 22:56:02 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-17 17:55:28 +00:00
|
|
|
|
/**
|
|
|
|
|
* Given a required captcha run, test form input for correct
|
|
|
|
|
* input on the open session.
|
|
|
|
|
* @param WebRequest $request
|
|
|
|
|
* @param User $user
|
|
|
|
|
* @return bool if passed, false if failed or new session
|
|
|
|
|
*/
|
|
|
|
|
public function passCaptchaFromRequest( WebRequest $request, User $user ) {
|
|
|
|
|
list( $index, $word ) = $this->getCaptchaParamsFromRequest( $request );
|
2017-02-17 13:09:34 +00:00
|
|
|
|
return $this->passCaptcha( $index, $word );
|
2016-05-17 17:55:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2007-11-12 07:42:25 +00:00
|
|
|
|
/**
|
|
|
|
|
* Given a required captcha run, test form input for correct
|
|
|
|
|
* input on the open session.
|
2016-04-25 20:58:18 +00:00
|
|
|
|
* @param string $index Captcha idenitifier
|
|
|
|
|
* @param string $word Captcha solution
|
2007-11-12 07:42:25 +00:00
|
|
|
|
* @return bool if passed, false if failed or new session
|
|
|
|
|
*/
|
2016-04-25 20:58:18 +00:00
|
|
|
|
protected function passCaptcha( $index, $word ) {
|
2015-10-28 15:52:04 +00:00
|
|
|
|
// Don't check the same CAPTCHA twice in one session,
|
|
|
|
|
// if the CAPTCHA was already checked - Bug T94276
|
2015-04-02 16:27:37 +00:00
|
|
|
|
if ( isset( $this->captchaSolved ) ) {
|
|
|
|
|
return $this->captchaSolved;
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
$info = $this->retrieveCaptcha( $index );
|
2009-07-19 15:13:01 +00:00
|
|
|
|
if ( $info ) {
|
2016-04-25 20:58:18 +00:00
|
|
|
|
if ( $this->keyMatch( $word, $info ) ) {
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$this->log( "passed" );
|
2016-04-25 20:58:18 +00:00
|
|
|
|
$this->clearCaptcha( $index );
|
2015-04-02 16:27:37 +00:00
|
|
|
|
$this->captchaSolved = true;
|
2007-11-12 07:42:25 +00:00
|
|
|
|
return true;
|
|
|
|
|
} else {
|
2016-04-25 20:58:18 +00:00
|
|
|
|
$this->clearCaptcha( $index );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$this->log( "bad form input" );
|
2015-04-02 16:27:37 +00:00
|
|
|
|
$this->captchaSolved = false;
|
2007-11-12 07:42:25 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$this->log( "new captcha session" );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Log the status and any triggering info for debugging or statistics
|
|
|
|
|
* @param string $message
|
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
protected function log( $message ) {
|
2018-09-03 00:12:31 +00:00
|
|
|
|
wfDebugLog( 'captcha', 'ConfirmEdit: ' . $message . '; ' . $this->trigger );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generate a captcha session ID and save the info in PHP's session storage.
|
|
|
|
|
* (Requires the user to have cookies enabled to get through the captcha.)
|
|
|
|
|
*
|
|
|
|
|
* A random ID is used so legit users can make edits in multiple tabs or
|
|
|
|
|
* windows without being unnecessarily hobbled by a serial order requirement.
|
|
|
|
|
* Pass the returned id value into the edit form as wpCaptchaId.
|
|
|
|
|
*
|
|
|
|
|
* @param array $info data to store
|
|
|
|
|
* @return string captcha ID key
|
|
|
|
|
*/
|
2016-04-25 20:58:18 +00:00
|
|
|
|
public function storeCaptcha( $info ) {
|
2009-07-19 15:13:01 +00:00
|
|
|
|
if ( !isset( $info['index'] ) ) {
|
2007-11-12 07:42:25 +00:00
|
|
|
|
// Assign random index if we're not udpating
|
|
|
|
|
$info['index'] = strval( mt_rand() );
|
|
|
|
|
}
|
2011-04-24 17:33:41 +00:00
|
|
|
|
CaptchaStore::get()->store( $info['index'], $info );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
return $info['index'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fetch this session's captcha info.
|
2016-04-25 20:58:18 +00:00
|
|
|
|
* @param string $index
|
|
|
|
|
* @return array|false array of info, or false if missing
|
2007-11-12 07:42:25 +00:00
|
|
|
|
*/
|
2016-04-25 20:58:18 +00:00
|
|
|
|
public function retrieveCaptcha( $index ) {
|
2011-04-24 17:33:41 +00:00
|
|
|
|
return CaptchaStore::get()->retrieve( $index );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clear out existing captcha info from the session, to ensure
|
|
|
|
|
* it can't be reused.
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param string $index
|
2007-11-12 07:42:25 +00:00
|
|
|
|
*/
|
2016-04-25 20:58:18 +00:00
|
|
|
|
public function clearCaptcha( $index ) {
|
|
|
|
|
CaptchaStore::get()->clear( $index );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Retrieve the current version of the page or section being edited...
|
Use the shared parse on API edit
ConfirmEdit was tripling the API save time, because it was parsing the
entire content twice to evaluate whether the addurl trigger is hit.
While I was here, I stopped using the deprecated non-Content hooks. The
new hook, EditEditFilterMergedContent, does not pass an EditPage object,
which means that Title or WikiPage objects need to be passed around
instead. Also, since EditPage::showEditForm() cannot be called with no
EditPage object, use a EditPage::showEditForm:fields hook instead.
If non-wikitext content is edited, assume that the regex trigger is not
hit.
For further architectural details, see the associated core change:
I4b4270dd868a . MW_EDITFILTERMERGED_SUPPORTS_API is a constant
introduced to detect the presence of the associated core change.
Also, in APIGetAllowedParams, set the allowed parameters even if we are
not on the help screen. This allows API users to submit their CAPTCHA
answer without it failing with an "unrecognized parameter" error.
Compatibility with MediaWiki 1.21 is retained, compatibility before that
is dropped.
Change-Id: I9529b7e8d3fc9301c754b28fda185aa3ab36f13e
2014-12-05 04:48:13 +00:00
|
|
|
|
* @param Title $title
|
2007-11-12 07:42:25 +00:00
|
|
|
|
* @param string $section
|
2017-09-01 04:47:36 +00:00
|
|
|
|
* @param int $flags Flags for Revision loading methods
|
2007-11-12 07:42:25 +00:00
|
|
|
|
* @return string
|
2019-02-06 12:04:20 +00:00
|
|
|
|
* @private
|
2007-11-12 07:42:25 +00:00
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
private function loadText( $title, $section, $flags = Revision::READ_LATEST ) {
|
2019-12-21 02:46:59 +00:00
|
|
|
|
$rev = Revision::newFromTitle( $title, 0, $flags );
|
2020-01-14 04:33:02 +00:00
|
|
|
|
if ( $rev === null ) {
|
2007-11-12 07:42:25 +00:00
|
|
|
|
return "";
|
|
|
|
|
}
|
2017-01-21 20:06:23 +00:00
|
|
|
|
|
|
|
|
|
$content = $rev->getContent();
|
|
|
|
|
$text = ContentHandler::getContentText( $content );
|
|
|
|
|
if ( $section !== '' ) {
|
2019-10-28 20:12:00 +00:00
|
|
|
|
return MediaWikiServices::getInstance()->getParser()
|
|
|
|
|
->getSection( $text, $section );
|
2017-01-21 20:06:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $text;
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extract a list of all recognized HTTP links in the text.
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param Title $title
|
|
|
|
|
* @param string $text
|
2020-02-03 14:36:31 +00:00
|
|
|
|
* @return string[]
|
2007-11-12 07:42:25 +00:00
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
private function findLinks( $title, $text ) {
|
2019-10-28 20:12:00 +00:00
|
|
|
|
$parser = MediaWikiServices::getInstance()->getParser();
|
2007-11-12 07:42:25 +00:00
|
|
|
|
$options = new ParserOptions();
|
2020-01-16 01:19:32 +00:00
|
|
|
|
$user = $parser->getUser();
|
|
|
|
|
$text = $parser->preSaveTransform( $text, $title, $user, $options );
|
2019-10-28 20:12:00 +00:00
|
|
|
|
$out = $parser->parse( $text, $title, $options );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
|
|
|
|
|
return array_keys( $out->getExternalLinks() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Show a page explaining what this wacky thing is.
|
|
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
|
public function showHelp() {
|
2015-04-02 22:56:48 +00:00
|
|
|
|
global $wgOut;
|
|
|
|
|
$wgOut->setPageTitle( wfMessage( 'captchahelp-title' )->text() );
|
|
|
|
|
$wgOut->addWikiMsg( 'captchahelp-text' );
|
2011-04-24 17:33:41 +00:00
|
|
|
|
if ( CaptchaStore::get()->cookiesNeeded() ) {
|
2015-04-02 22:56:48 +00:00
|
|
|
|
$wgOut->addWikiMsg( 'captchahelp-cookies-needed' );
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2014-01-10 23:16:34 +00:00
|
|
|
|
|
2016-05-03 16:42:00 +00:00
|
|
|
|
/**
|
|
|
|
|
* @return CaptchaAuthenticationRequest
|
|
|
|
|
*/
|
|
|
|
|
public function createAuthenticationRequest() {
|
|
|
|
|
$captchaData = $this->getCaptcha();
|
|
|
|
|
$id = $this->storeCaptcha( $captchaData );
|
|
|
|
|
return new CaptchaAuthenticationRequest( $id, $captchaData );
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-25 20:58:18 +00:00
|
|
|
|
/**
|
2018-05-18 18:29:00 +00:00
|
|
|
|
* Modify the appearance of the captcha field
|
2016-04-25 20:58:18 +00:00
|
|
|
|
* @param AuthenticationRequest[] $requests
|
|
|
|
|
* @param array $fieldInfo Field description as given by AuthenticationRequest::mergeFieldInfo
|
2017-09-09 18:10:12 +00:00
|
|
|
|
* @param array &$formDescriptor A form descriptor suitable for the HTMLForm constructor
|
2016-04-25 20:58:18 +00:00
|
|
|
|
* @param string $action One of the AuthManager::ACTION_* constants
|
|
|
|
|
*/
|
|
|
|
|
public function onAuthChangeFormFields(
|
|
|
|
|
array $requests, array $fieldInfo, array &$formDescriptor, $action
|
|
|
|
|
) {
|
|
|
|
|
$req = AuthenticationRequest::getRequestByClass( $requests,
|
|
|
|
|
CaptchaAuthenticationRequest::class );
|
|
|
|
|
if ( !$req ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$formDescriptor['captchaWord'] = [
|
|
|
|
|
'label-message' => null,
|
|
|
|
|
'autocomplete' => false,
|
|
|
|
|
'persistent' => false,
|
|
|
|
|
'required' => true,
|
|
|
|
|
] + $formDescriptor['captchaWord'];
|
|
|
|
|
}
|
2018-05-18 18:29:00 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check whether the user provided / IP making the request is allowed to skip captchas
|
|
|
|
|
* @param User $user
|
|
|
|
|
* @param Config $config
|
|
|
|
|
* @return bool
|
|
|
|
|
* @throws ConfigException
|
|
|
|
|
*/
|
|
|
|
|
public function canSkipCaptcha( $user, Config $config ) {
|
|
|
|
|
$allowConfirmEmail = $config->get( 'AllowConfirmedEmail' );
|
|
|
|
|
|
|
|
|
|
if ( $user->isAllowed( 'skipcaptcha' ) ) {
|
|
|
|
|
wfDebug( "ConfirmEdit: user group allows skipping captcha\n" );
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $this->isIPWhitelisted() ) {
|
|
|
|
|
wfDebug( "ConfirmEdit: user IP is whitelisted" );
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $allowConfirmEmail && $user->isEmailConfirmed() ) {
|
|
|
|
|
wfDebug( "ConfirmEdit: user has confirmed mail, skipping captcha\n" );
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2007-11-12 07:42:25 +00:00
|
|
|
|
}
|