Commit | Line | Data |
---|---|---|
96714cab | 1 | <?php |
a9229942 | 2 | |
96714cab | 3 | namespace wcf\system\captcha; |
a9229942 | 4 | |
5e0b820a TD |
5 | use GuzzleHttp\ClientInterface; |
6 | use GuzzleHttp\Psr7\Request; | |
7 | use Psr\Http\Client\ClientExceptionInterface; | |
b763de7a | 8 | use wcf\system\exception\UserInputException; |
5e0b820a | 9 | use wcf\system\io\HttpFactory; |
96714cab | 10 | use wcf\system\WCF; |
b763de7a TD |
11 | use wcf\util\JSON; |
12 | use wcf\util\UserUtil; | |
96714cab MS |
13 | |
14 | /** | |
15 | * Captcha handler for reCAPTCHA. | |
a9229942 TD |
16 | * |
17 | * @author Tim Duesterhus, Matthias Schmidt | |
18 | * @copyright 2001-2020 WoltLab GmbH | |
19 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
96714cab | 20 | */ |
a9229942 TD |
21 | class RecaptchaHandler implements ICaptchaHandler |
22 | { | |
23 | /** | |
24 | * recaptcha challenge | |
25 | * @var string | |
26 | */ | |
27 | public $challenge = ''; | |
28 | ||
29 | /** | |
30 | * response to the challenge | |
31 | * @var string | |
32 | */ | |
33 | public $response = ''; | |
34 | ||
35 | /** | |
36 | * ACP option override | |
37 | * @var bool | |
38 | */ | |
39 | public static $forceIsAvailable = false; | |
40 | ||
41 | /** | |
42 | * @inheritDoc | |
43 | */ | |
44 | public function getFormElement() | |
45 | { | |
46 | if (WCF::getSession()->getVar('recaptchaDone')) { | |
47 | return ''; | |
48 | } | |
49 | ||
50 | WCF::getTPL()->assign([ | |
51 | 'recaptchaLegacyMode' => true, | |
52 | ]); | |
53 | ||
b54fdcc0 | 54 | return WCF::getTPL()->fetch('shared_recaptcha'); |
a9229942 TD |
55 | } |
56 | ||
57 | /** | |
58 | * @inheritDoc | |
59 | */ | |
60 | public function isAvailable() | |
61 | { | |
62 | if (!RECAPTCHA_PUBLICKEY || !RECAPTCHA_PRIVATEKEY) { | |
63 | // OEM keys are no longer supported, disable reCAPTCHA | |
64 | if (self::$forceIsAvailable) { | |
65 | // work-around for the ACP option selection | |
66 | return true; | |
67 | } | |
68 | ||
69 | return false; | |
70 | } | |
71 | ||
72 | return true; | |
73 | } | |
74 | ||
75 | /** | |
76 | * @inheritDoc | |
77 | */ | |
78 | public function readFormParameters() | |
79 | { | |
80 | if (isset($_POST['recaptcha-type'])) { | |
81 | $this->challenge = $_POST['recaptcha-type']; | |
27454d07 | 82 | } elseif (isset($_POST['parameters']['recaptcha-type'])) { |
1bb8616e | 83 | $this->challenge = $_POST['parameters']['recaptcha-type']; |
a9229942 TD |
84 | } |
85 | if (isset($_POST['g-recaptcha-response'])) { | |
86 | $this->response = $_POST['g-recaptcha-response']; | |
27454d07 | 87 | } elseif (isset($_POST['parameters']['g-recaptcha-response'])) { |
1bb8616e | 88 | $this->response = $_POST['parameters']['g-recaptcha-response']; |
a9229942 TD |
89 | } |
90 | } | |
91 | ||
92 | /** | |
93 | * @inheritDoc | |
94 | */ | |
95 | public function reset() | |
96 | { | |
97 | WCF::getSession()->unregister('recaptchaDone'); | |
98 | } | |
99 | ||
100 | /** | |
101 | * @inheritDoc | |
102 | */ | |
103 | public function validate() | |
104 | { | |
105 | if (WCF::getSession()->getVar('recaptchaDone')) { | |
106 | return; | |
107 | } | |
108 | ||
109 | // fail if response is empty to avoid sending api requests | |
110 | if (empty($this->response)) { | |
111 | throw new UserInputException('recaptchaString', 'false'); | |
112 | } | |
113 | ||
114 | $type = $this->challenge ?: 'v2'; | |
115 | ||
116 | if ($type === 'v2') { | |
117 | $key = RECAPTCHA_PRIVATEKEY; | |
118 | } elseif ($type === 'invisible') { | |
119 | $key = RECAPTCHA_PRIVATEKEY_INVISIBLE; | |
120 | } else { | |
067f2d1d | 121 | // The bot modified the `recaptcha-type` form field. |
122 | throw new UserInputException('recaptchaString', 'false'); | |
a9229942 TD |
123 | } |
124 | ||
5e0b820a TD |
125 | $request = new Request( |
126 | 'GET', | |
a9229942 | 127 | \sprintf( |
ddd2a38e | 128 | 'https://www.google.com/recaptcha/api/siteverify?%s', |
5e0b820a TD |
129 | \http_build_query([ |
130 | 'secret' => $key, | |
131 | 'response' => $this->response, | |
132 | 'remoteip' => UserUtil::getIpAddress(), | |
133 | ], '', '&') | |
134 | ) | |
a9229942 TD |
135 | ); |
136 | ||
137 | try { | |
5e0b820a TD |
138 | $response = $this->getHttpClient()->send($request); |
139 | $data = JSON::decode((string)$response->getBody()); | |
a9229942 TD |
140 | |
141 | if ($data['success']) { | |
142 | // yeah | |
143 | } else { | |
144 | throw new UserInputException('recaptchaString', 'false'); | |
145 | } | |
5e0b820a | 146 | } catch (ClientExceptionInterface $e) { |
a9229942 TD |
147 | // log error, but accept captcha |
148 | \wcf\functions\exception\logThrowable($e); | |
149 | } | |
150 | ||
151 | WCF::getSession()->register('recaptchaDone', true); | |
152 | } | |
5e0b820a TD |
153 | |
154 | private function getHttpClient(): ClientInterface | |
155 | { | |
156 | return HttpFactory::makeClientWithTimeout(5); | |
157 | } | |
96714cab | 158 | } |