2014-12-14 20:47:55 +00:00
|
|
|
<?php
|
2016-05-03 16:42:00 +00:00
|
|
|
|
|
|
|
use MediaWiki\Auth\AuthenticationRequest;
|
2022-04-08 16:40:15 +00:00
|
|
|
use MediaWiki\Extension\ConfirmEdit\Auth\CaptchaAuthenticationRequest;
|
|
|
|
use MediaWiki\Extension\ConfirmEdit\Hooks;
|
|
|
|
use MediaWiki\Extension\ConfirmEdit\SimpleCaptcha\SimpleCaptcha;
|
2020-06-07 10:36:46 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2016-05-03 16:42:00 +00:00
|
|
|
|
2014-12-14 20:47:55 +00:00
|
|
|
class ReCaptchaNoCaptcha extends SimpleCaptcha {
|
2016-04-25 20:58:18 +00:00
|
|
|
// used for renocaptcha-edit, renocaptcha-addurl, renocaptcha-badlogin, renocaptcha-createaccount,
|
|
|
|
// renocaptcha-create, renocaptcha-sendemail via getMessage()
|
|
|
|
protected static $messagePrefix = 'renocaptcha-';
|
|
|
|
|
2014-12-14 20:47:55 +00:00
|
|
|
private $error = null;
|
2019-07-14 10:21:35 +00:00
|
|
|
|
2014-12-14 20:47:55 +00:00
|
|
|
/**
|
|
|
|
* Get the captcha form.
|
2017-09-09 18:10:12 +00:00
|
|
|
* @param int $tabIndex
|
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 array
|
2014-12-14 20:47:55 +00:00
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
public function getFormInformation( $tabIndex = 1 ) {
|
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
|
|
|
global $wgReCaptchaSiteKey, $wgLang;
|
|
|
|
$lang = htmlspecialchars( urlencode( $wgLang->getCode() ) );
|
2014-12-14 20:47:55 +00:00
|
|
|
|
2016-05-09 23:41:17 +00:00
|
|
|
$output = Html::element( 'div', [
|
|
|
|
'class' => [
|
2014-12-14 20:47:55 +00:00
|
|
|
'g-recaptcha',
|
2019-02-06 12:04:20 +00:00
|
|
|
'mw-confirmedit-captcha-fail' => (bool)$this->error,
|
2016-05-09 23:41:17 +00:00
|
|
|
],
|
2014-12-14 20:47:55 +00:00
|
|
|
'data-sitekey' => $wgReCaptchaSiteKey
|
2016-05-09 23:41:17 +00:00
|
|
|
] );
|
2014-12-14 20:47:55 +00:00
|
|
|
$htmlUrlencoded = htmlspecialchars( urlencode( $wgReCaptchaSiteKey ) );
|
|
|
|
$output .= <<<HTML
|
|
|
|
<noscript>
|
2016-05-03 16:42:00 +00:00
|
|
|
<div>
|
2014-12-14 20:47:55 +00:00
|
|
|
<div style="width: 302px; height: 422px; position: relative;">
|
|
|
|
<div style="width: 302px; height: 422px; position: absolute;">
|
2020-04-17 15:18:38 +00:00
|
|
|
<iframe
|
2020-07-23 19:32:53 +00:00
|
|
|
src="https://www.recaptcha.net/recaptcha/api/fallback?k={$htmlUrlencoded}&hl={$lang}"
|
2020-04-17 15:18:38 +00:00
|
|
|
frameborder="0" scrolling="no"
|
|
|
|
style="width: 302px; height:422px; border-style: none;">
|
2014-12-14 20:47:55 +00:00
|
|
|
</iframe>
|
|
|
|
</div>
|
2016-05-03 16:42:00 +00:00
|
|
|
</div>
|
|
|
|
<div style="width: 300px; height: 60px; border-style: none;
|
|
|
|
bottom: 12px; left: 25px; margin: 0px; padding: 0px; right: 25px;
|
|
|
|
background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
|
|
|
|
<textarea id="g-recaptcha-response" name="g-recaptcha-response"
|
|
|
|
class="g-recaptcha-response"
|
|
|
|
style="width: 250px; height: 40px; border: 1px solid #c1c1c1;
|
|
|
|
margin: 10px 25px; padding: 0px; resize: none;" >
|
|
|
|
</textarea>
|
2014-12-14 20:47:55 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</noscript>
|
|
|
|
HTML;
|
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 [
|
|
|
|
'html' => $output,
|
|
|
|
'headitems' => [
|
|
|
|
// Insert reCAPTCHA script, in display language, if available.
|
|
|
|
// Language falls back to the browser's display language.
|
|
|
|
// See https://developers.google.com/recaptcha/docs/faq
|
2020-04-17 15:18:38 +00:00
|
|
|
"<script src=\"https://www.recaptcha.net/recaptcha/api.js?hl={$lang}\" async defer></script>"
|
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
|
|
|
]
|
|
|
|
];
|
2014-12-14 20:47:55 +00:00
|
|
|
}
|
|
|
|
|
2020-04-18 09:48:07 +00:00
|
|
|
/**
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
public static function getCSPUrls() {
|
|
|
|
return [ 'https://www.recaptcha.net/recaptcha/api.js' ];
|
|
|
|
}
|
|
|
|
|
2017-02-17 13:24:49 +00:00
|
|
|
/**
|
2017-09-09 18:10:12 +00:00
|
|
|
* @param Status|array|string $info
|
2017-02-17 13:24:49 +00:00
|
|
|
*/
|
2014-12-14 20:47:55 +00:00
|
|
|
protected function logCheckError( $info ) {
|
|
|
|
if ( $info instanceof Status ) {
|
2015-10-19 10:49:56 +00:00
|
|
|
$errors = $info->getErrorsArray();
|
2014-12-14 20:47:55 +00:00
|
|
|
$error = $errors[0][0];
|
|
|
|
} elseif ( is_array( $info ) ) {
|
|
|
|
$error = implode( ',', $info );
|
|
|
|
} else {
|
|
|
|
$error = $info;
|
|
|
|
}
|
2015-10-19 10:49:56 +00:00
|
|
|
|
2014-12-14 20:47:55 +00:00
|
|
|
wfDebugLog( 'captcha', 'Unable to validate response: ' . $error );
|
|
|
|
}
|
|
|
|
|
2017-02-17 13:24:49 +00:00
|
|
|
/**
|
|
|
|
* @param WebRequest $request
|
|
|
|
* @return array
|
|
|
|
*/
|
2016-05-17 17:55:28 +00:00
|
|
|
protected function getCaptchaParamsFromRequest( WebRequest $request ) {
|
2018-06-15 21:08:14 +00:00
|
|
|
// ReCaptchaNoCaptcha combines captcha ID + solution into a single value
|
2016-05-03 16:42:00 +00:00
|
|
|
// API is hardwired to return captchaWord, so use that if the standard isempty
|
2018-12-07 20:44:46 +00:00
|
|
|
// "captchaWord" is sent as "captchaword" by visual editor
|
2018-06-15 21:08:14 +00:00
|
|
|
$index = 'not used';
|
2020-04-11 01:27:12 +00:00
|
|
|
$response = $request->getVal(
|
|
|
|
'g-recaptcha-response',
|
|
|
|
$request->getVal( 'captchaWord', $request->getVal( 'captchaword' ) )
|
|
|
|
);
|
2016-05-17 17:55:28 +00:00
|
|
|
return [ $index, $response ];
|
2016-05-03 16:42:00 +00:00
|
|
|
}
|
|
|
|
|
2014-12-14 20:47:55 +00:00
|
|
|
/**
|
|
|
|
* Check, if the user solved the captcha.
|
|
|
|
*
|
|
|
|
* Based on reference implementation:
|
|
|
|
* https://github.com/google/recaptcha#php
|
|
|
|
*
|
2017-09-09 18:10:12 +00:00
|
|
|
* @param mixed $_ Not used (ReCaptcha v2 puts index and solution in a single string)
|
|
|
|
* @param string $word captcha solution
|
2017-02-17 13:24:49 +00:00
|
|
|
* @return bool
|
2014-12-14 20:47:55 +00:00
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
protected function passCaptcha( $_, $word ) {
|
2014-12-14 20:47:55 +00:00
|
|
|
global $wgRequest, $wgReCaptchaSecretKey, $wgReCaptchaSendRemoteIP;
|
|
|
|
|
2020-04-17 15:18:38 +00:00
|
|
|
$url = 'https://www.recaptcha.net/recaptcha/api/siteverify';
|
2014-12-14 20:47:55 +00:00
|
|
|
// Build data to append to request
|
2016-05-09 23:41:17 +00:00
|
|
|
$data = [
|
2014-12-14 20:47:55 +00:00
|
|
|
'secret' => $wgReCaptchaSecretKey,
|
2016-05-03 16:42:00 +00:00
|
|
|
'response' => $word,
|
2016-05-09 23:41:17 +00:00
|
|
|
];
|
2014-12-14 20:47:55 +00:00
|
|
|
if ( $wgReCaptchaSendRemoteIP ) {
|
|
|
|
$data['remoteip'] = $wgRequest->getIP();
|
|
|
|
}
|
|
|
|
$url = wfAppendQuery( $url, $data );
|
2020-06-07 10:36:46 +00:00
|
|
|
$request = MediaWikiServices::getInstance()->getHttpRequestFactory()
|
2020-10-24 23:28:23 +00:00
|
|
|
->create( $url, [ 'method' => 'POST' ], __METHOD__ );
|
2014-12-14 20:47:55 +00:00
|
|
|
$status = $request->execute();
|
|
|
|
if ( !$status->isOK() ) {
|
|
|
|
$this->error = 'http';
|
2015-10-14 12:42:38 +00:00
|
|
|
$this->logCheckError( $status );
|
2014-12-14 20:47:55 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$response = FormatJson::decode( $request->getContent(), true );
|
|
|
|
if ( !$response ) {
|
|
|
|
$this->error = 'json';
|
2015-10-14 12:42:38 +00:00
|
|
|
$this->logCheckError( $this->error );
|
2014-12-14 20:47:55 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ( isset( $response['error-codes'] ) ) {
|
|
|
|
$this->error = 'recaptcha-api';
|
|
|
|
$this->logCheckError( $response['error-codes'] );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $response['success'];
|
|
|
|
}
|
|
|
|
|
2017-02-17 13:24:49 +00:00
|
|
|
/**
|
2017-09-09 18:10:12 +00:00
|
|
|
* @param array &$resultArr
|
2017-02-17 13:24:49 +00:00
|
|
|
*/
|
2018-06-15 21:08:14 +00:00
|
|
|
protected function addCaptchaAPI( &$resultArr ) {
|
2016-04-25 20:58:18 +00:00
|
|
|
$resultArr['captcha'] = $this->describeCaptchaType();
|
2014-12-14 20:47:55 +00:00
|
|
|
$resultArr['captcha']['error'] = $this->error;
|
|
|
|
}
|
|
|
|
|
2017-02-17 13:24:49 +00:00
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
2016-04-25 20:58:18 +00:00
|
|
|
public function describeCaptchaType() {
|
|
|
|
global $wgReCaptchaSiteKey;
|
|
|
|
return [
|
|
|
|
'type' => 'recaptchanocaptcha',
|
2021-12-09 06:54:29 +00:00
|
|
|
'mime' => 'application/javascript',
|
2016-04-25 20:58:18 +00:00
|
|
|
'key' => $wgReCaptchaSiteKey,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2014-12-14 20:47:55 +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
|
2019-05-23 19:16:12 +00:00
|
|
|
* @return Message
|
2014-12-14 20:47:55 +00:00
|
|
|
*/
|
2016-04-25 20:58:18 +00:00
|
|
|
public function getMessage( $action ) {
|
|
|
|
$msg = parent::getMessage( $action );
|
2014-12-14 20:47:55 +00:00
|
|
|
if ( $this->error ) {
|
2016-04-25 20:58:18 +00:00
|
|
|
$msg = new RawMessage( '<div class="error">$1</div>', [ $msg ] );
|
2014-12-14 20:47:55 +00:00
|
|
|
}
|
2016-04-25 20:58:18 +00:00
|
|
|
return $msg;
|
2014-12-14 20:47:55 +00:00
|
|
|
}
|
|
|
|
|
2017-02-17 13:24:49 +00:00
|
|
|
/**
|
2019-11-19 03:28:05 +00:00
|
|
|
* @param ApiBase $module
|
2017-09-09 18:10:12 +00:00
|
|
|
* @param array &$params
|
2017-02-17 13:24:49 +00:00
|
|
|
* @param int $flags
|
|
|
|
* @return bool
|
|
|
|
*/
|
2019-11-19 03:28:05 +00:00
|
|
|
public function apiGetAllowedParams( ApiBase $module, &$params, $flags ) {
|
2014-12-14 20:47:55 +00:00
|
|
|
if ( $flags && $this->isAPICaptchaModule( $module ) ) {
|
2016-09-20 18:49:32 +00:00
|
|
|
$params['g-recaptcha-response'] = [
|
|
|
|
ApiBase::PARAM_HELP_MSG => 'renocaptcha-apihelp-param-g-recaptcha-response',
|
|
|
|
];
|
2014-12-14 20:47:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2016-05-03 16:42:00 +00:00
|
|
|
|
2020-04-14 23:29:18 +00:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2016-05-03 16:42:00 +00:00
|
|
|
public function getError() {
|
|
|
|
return $this->error;
|
|
|
|
}
|
|
|
|
|
2020-04-14 23:29:18 +00:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2016-05-03 16:42:00 +00:00
|
|
|
public function storeCaptcha( $info ) {
|
|
|
|
// ReCaptcha is stored by Google; the ID will be generated at that time as well, and
|
|
|
|
// the one returned here won't be used. Just pretend this worked.
|
|
|
|
return 'not used';
|
|
|
|
}
|
|
|
|
|
2020-04-14 23:29:18 +00:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2016-05-03 16:42:00 +00:00
|
|
|
public function retrieveCaptcha( $index ) {
|
|
|
|
// just pretend it worked
|
|
|
|
return [ 'index' => $index ];
|
|
|
|
}
|
|
|
|
|
2020-04-14 23:29:18 +00:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2016-05-03 16:42:00 +00:00
|
|
|
public function getCaptcha() {
|
|
|
|
// ReCaptcha is handled by frontend code + an external provider; nothing to do here.
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2017-02-17 13:24:49 +00:00
|
|
|
/**
|
|
|
|
* @return ReCaptchaNoCaptchaAuthenticationRequest
|
|
|
|
*/
|
2016-05-03 16:42:00 +00:00
|
|
|
public function createAuthenticationRequest() {
|
|
|
|
return new ReCaptchaNoCaptchaAuthenticationRequest();
|
|
|
|
}
|
|
|
|
|
2017-02-17 13:24:49 +00:00
|
|
|
/**
|
|
|
|
* @param array $requests
|
|
|
|
* @param array $fieldInfo
|
2017-09-09 18:10:12 +00:00
|
|
|
* @param array &$formDescriptor
|
2017-02-17 13:24:49 +00:00
|
|
|
* @param string $action
|
|
|
|
*/
|
2016-05-03 16:42:00 +00:00
|
|
|
public function onAuthChangeFormFields(
|
|
|
|
array $requests, array $fieldInfo, array &$formDescriptor, $action
|
|
|
|
) {
|
|
|
|
global $wgReCaptchaSiteKey;
|
|
|
|
|
|
|
|
$req = AuthenticationRequest::getRequestByClass( $requests,
|
|
|
|
CaptchaAuthenticationRequest::class, true );
|
|
|
|
if ( !$req ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ugly way to retrieve error information
|
2022-04-08 16:40:15 +00:00
|
|
|
$captcha = Hooks::getInstance();
|
2016-05-03 16:42:00 +00:00
|
|
|
|
|
|
|
$formDescriptor['captchaWord'] = [
|
|
|
|
'class' => HTMLReCaptchaNoCaptchaField::class,
|
|
|
|
'key' => $wgReCaptchaSiteKey,
|
|
|
|
'error' => $captcha->getError(),
|
|
|
|
] + $formDescriptor['captchaWord'];
|
|
|
|
}
|
2014-12-14 20:47:55 +00:00
|
|
|
}
|