2480c7250fc53a0869ca2558f7eba743a705eca9
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / recaptcha / RecaptchaHandler.class.php
1 <?php
2 namespace wcf\system\recaptcha;
3 use wcf\system\exception\SystemException;
4 use wcf\system\exception\UserInputException;
5 use wcf\system\request\RouteHandler;
6 use wcf\system\SingletonFactory;
7 use wcf\system\WCF;
8 use wcf\util\HTTPRequest;
9 use wcf\util\StringUtil;
10 use wcf\util\UserUtil;
11
12 /**
13 * Handles reCAPTCHA support.
14 *
15 * Based upon reCAPTCHA-plugin originally created in 2010 by Markus Bartz <roul@codingcorner.info>
16 * and released under the conditions of the GNU Lesser General Public License.
17 *
18 * @author Alexander Ebert
19 * @copyright 2001-2019 WoltLab GmbH
20 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
21 * @package WoltLabSuite\Core\System\Recaptcha
22 */
23 class RecaptchaHandler extends SingletonFactory {
24 /**
25 * list of supported languages
26 * @var string[]
27 * @see http://code.google.com/intl/de-DE/apis/recaptcha/docs/customization.html#i18n
28 */
29 protected $supportedLanguages = ['de', 'en', 'es', 'fr', 'nl', 'pt', 'ru', 'tr'];
30
31 /**
32 * language code
33 * @var string
34 */
35 protected $languageCode = '';
36
37 /**
38 * public key
39 * @var string
40 */
41 protected $publicKey = '';
42
43 /**
44 * private key
45 * @var string
46 */
47 protected $privateKey = '';
48
49 // reply codes (see <http://code.google.com/intl/de-DE/apis/recaptcha/docs/verify.html>)
50 const VALID_ANSWER = 'valid';
51 const ERROR_UNKNOWN = 'unknown';
52 const ERROR_INVALID_PUBLICKEY = 'invalid-site-public-key';
53 const ERROR_INVALID_PRIVATEKEY = 'invalid-site-private-key';
54 const ERROR_INVALID_COOKIE = 'invalid-request-cookie';
55 const ERROR_INCORRECT_SOLUTION = 'incorrect-captcha-sol';
56 const ERROR_INCORRECT_PARAMS = 'verify-params-incorrect';
57 const ERROR_INVALID_REFFERER = 'invalid-referrer';
58 const ERROR_NOT_REACHABLE = 'recaptcha-not-reachable';
59 const ERROR_TIMEOUT_OR_DUPLICATE = 'timeout-or-duplicate';
60
61 /**
62 * @inheritDoc
63 */
64 protected function init() {
65 // set appropriate language code, fallback to EN if language code is not known to reCAPTCHA-API
66 $this->languageCode = WCF::getLanguage()->getFixedLanguageCode();
67 if (!in_array($this->languageCode, $this->supportedLanguages)) {
68 $this->languageCode = 'en';
69 }
70
71 // WoltLab's V1 OEM keys
72 $this->publicKey = '6LfOlMYSAAAAADvo3s4puBAYDqI-6YK2ybe7BJE5';
73 $this->privateKey = '6LfOlMYSAAAAAKR3m_EFxmDv1xS8PCfeaSZ2LdG9';
74 }
75
76 /**
77 * Validates response against given challenge.
78 *
79 * @param string $challenge
80 * @param string $response
81 * @throws SystemException
82 * @throws UserInputException
83 */
84 public function validate($challenge, $response) {
85 // fail if challenge or response are empty to avoid sending api requests
86 if (empty($challenge) || empty($response)) {
87 throw new UserInputException('recaptchaString', 'false');
88 }
89
90 $response = $this->verify($challenge, $response);
91 switch ($response) {
92 case self::VALID_ANSWER:
93 break;
94
95 case self::ERROR_INCORRECT_SOLUTION:
96 throw new UserInputException('recaptchaString', 'false');
97 break;
98
99 case self::ERROR_NOT_REACHABLE:
100 // if reCaptcha server is unreachable mark captcha as done
101 // this should be better than block users until server is back.
102 // - RouL
103 break;
104
105 case self::ERROR_INVALID_COOKIE:
106 // do not throw a system exception, if validation fails
107 // while javascript is disabled. Otherwise, bots may produce
108 // a lot of log entries.
109 throw new UserInputException('recaptchaString', 'false');
110 break;
111
112 case self::ERROR_TIMEOUT_OR_DUPLICATE:
113 // User was to slow to fill the captcha.
114 throw new UserInputException('recaptchaString', 'false');
115 break;
116
117 default:
118 throw new SystemException('reCAPTCHA returned the following error: '.$response);
119 }
120
121 WCF::getSession()->register('recaptchaDone', true);
122 }
123
124 /**
125 * Queries server to verify successful response.
126 *
127 * @param string $challenge
128 * @param string $response
129 * @return string
130 */
131 protected function verify($challenge, $response) {
132 $request = new HTTPRequest('http://www.google.com/recaptcha/api/verify', ['timeout' => 10], [
133 'privatekey' => $this->privateKey,
134 'remoteip' => UserUtil::getIpAddress(),
135 'challenge' => $challenge,
136 'response' => $response
137 ]);
138
139 try {
140 $request->execute();
141 $reply = $request->getReply();
142 $reCaptchaResponse = explode("\n", $reply['body']);
143
144 if (StringUtil::trim($reCaptchaResponse[0]) === "true") {
145 return self::VALID_ANSWER;
146 }
147 else {
148 return StringUtil::trim($reCaptchaResponse[1]);
149 }
150 }
151 catch (SystemException $e) {
152 return self::ERROR_NOT_REACHABLE;
153 }
154 }
155
156 /**
157 * Assigns template variables for reCAPTCHA.
158 */
159 public function assignVariables() {
160 WCF::getTPL()->assign([
161 'recaptchaLanguageCode' => $this->languageCode,
162 'recaptchaPublicKey' => $this->publicKey,
163 'recaptchaUseSSL' => RouteHandler::secureConnection(), // @deprecated 2.1
164 'recaptchaLegacyMode' => true
165 ]);
166 }
167 }