diff --git a/libraries/plugins/auth/AuthenticationCookie.class.php b/libraries/plugins/auth/AuthenticationCookie.class.php index b3d8be9fc7..1ad499e1f3 100644 --- a/libraries/plugins/auth/AuthenticationCookie.class.php +++ b/libraries/plugins/auth/AuthenticationCookie.class.php @@ -370,19 +370,19 @@ class AuthenticationCookie extends AuthenticationPlugin ) { if (! empty($_POST["g-recaptcha-response"])) { - include_once 'libraries/plugins/auth/recaptcha/recaptchalib.php'; - $reCaptcha = new ReCaptcha( + include_once 'libraries/plugins/auth/recaptcha/autoload.php'; + $reCaptcha = new \ReCaptcha\ReCaptcha( $GLOBALS['cfg']['CaptchaLoginPrivateKey'] ); // verify captcha status. - $resp = $reCaptcha->verifyResponse( - $_SERVER["REMOTE_ADDR"], - $_POST["g-recaptcha-response"] + $resp = $reCaptcha->verify( + $_POST["g-recaptcha-response"], + $_SERVER["REMOTE_ADDR"] ); // Check if the captcha entered is valid, if not stop the login. - if ($resp == null || ! $resp->success) { + if ($resp == null || ! $resp->isSuccess()) { $conn_error = __('Entered captcha is wrong, try again!'); $_SESSION['last_valid_captcha'] = false; return false; diff --git a/libraries/plugins/auth/recaptcha/ReCaptcha/ReCaptcha.php b/libraries/plugins/auth/recaptcha/ReCaptcha/ReCaptcha.php new file mode 100644 index 0000000000..414c52159d --- /dev/null +++ b/libraries/plugins/auth/recaptcha/ReCaptcha/ReCaptcha.php @@ -0,0 +1,97 @@ +secret = $secret; + + if (!is_null($requestMethod)) { + $this->requestMethod = $requestMethod; + } else { + $this->requestMethod = new RequestMethod\Post(); + } + } + + /** + * Calls the reCAPTCHA siteverify API to verify whether the user passes + * CAPTCHA test. + * + * @param string $response The value of 'g-recaptcha-response' in the submitted form. + * @param string $remoteIp The end user's IP address. + * @return Response Response from the service. + */ + public function verify($response, $remoteIp = null) + { + // Discard empty solution submissions + if (empty($response)) { + $recaptchaResponse = new Response(false, array('missing-input-response')); + return $recaptchaResponse; + } + + $params = new RequestParameters($this->secret, $response, $remoteIp, self::VERSION); + $rawResponse = $this->requestMethod->submit($params); + return Response::fromJson($rawResponse); + } +} diff --git a/libraries/plugins/auth/recaptcha/ReCaptcha/RequestMethod.php b/libraries/plugins/auth/recaptcha/ReCaptcha/RequestMethod.php new file mode 100644 index 0000000000..fc4dde59c0 --- /dev/null +++ b/libraries/plugins/auth/recaptcha/ReCaptcha/RequestMethod.php @@ -0,0 +1,42 @@ + array( + 'header' => "Content-type: application/x-www-form-urlencoded\r\n", + 'method' => 'POST', + 'content' => $params->toQueryString(), + // Force the peer to validate (not needed in 5.6.0+, but still works + 'verify_peer' => true, + // Force the peer validation to use www.google.com + $peer_key => 'www.google.com', + ), + ); + $context = stream_context_create($options); + return file_get_contents(self::SITE_VERIFY_URL, false, $context); + } +} diff --git a/libraries/plugins/auth/recaptcha/ReCaptcha/RequestMethod/Socket.php b/libraries/plugins/auth/recaptcha/ReCaptcha/RequestMethod/Socket.php new file mode 100644 index 0000000000..e74fc49d62 --- /dev/null +++ b/libraries/plugins/auth/recaptcha/ReCaptcha/RequestMethod/Socket.php @@ -0,0 +1,104 @@ +handle = fsockopen($hostname, $port, $errno, $errstr, (is_null($timeout) ? ini_get("default_socket_timeout") : $timeout)); + + if ($this->handle != false && $errno === 0 && $errstr === '') { + return $this->handle; + } else { + return false; + } + } + + /** + * fwrite + * + * @see http://php.net/fwrite + * @param string $string + * @param int $length + * @return int | bool + */ + public function fwrite($string, $length = null) + { + return fwrite($this->handle, $string, (is_null($length) ? strlen($string) : $length)); + } + + /** + * fgets + * + * @see http://php.net/fgets + * @param int $length + */ + public function fgets($length = null) + { + return fgets($this->handle, $length); + } + + /** + * feof + * + * @see http://php.net/feof + * @return bool + */ + public function feof() + { + return feof($this->handle); + } + + /** + * fclose + * + * @see http://php.net/fclose + * @return bool + */ + public function fclose() + { + return fclose($this->handle); + } +} diff --git a/libraries/plugins/auth/recaptcha/ReCaptcha/RequestMethod/SocketPost.php b/libraries/plugins/auth/recaptcha/ReCaptcha/RequestMethod/SocketPost.php new file mode 100644 index 0000000000..4f0c61a3fd --- /dev/null +++ b/libraries/plugins/auth/recaptcha/ReCaptcha/RequestMethod/SocketPost.php @@ -0,0 +1,120 @@ +socket = $socket; + } else { + $this->socket = new Socket(); + } + } + + /** + * Submit the POST request with the specified parameters. + * + * @param RequestParameters $params Request parameters + * @return string Body of the reCAPTCHA response + */ + public function submit(RequestParameters $params) + { + $errno = 0; + $errstr = ''; + + if ($this->socket->fsockopen('ssl://' . self::RECAPTCHA_HOST, 443, $errno, $errstr, 30) !== false) { + $content = $params->toQueryString(); + + $request = "POST " . self::SITE_VERIFY_PATH . " HTTP/1.1\r\n"; + $request .= "Host: " . self::RECAPTCHA_HOST . "\r\n"; + $request .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $request .= "Content-length: " . strlen($content) . "\r\n"; + $request .= "Connection: close\r\n\r\n"; + $request .= $content . "\r\n\r\n"; + + $this->socket->fwrite($request); + $response = ''; + + while (!$this->socket->feof()) { + $response .= $this->socket->fgets(4096); + } + + $this->socket->fclose(); + + if (0 === strpos($response, 'HTTP/1.1 200 OK')) { + $parts = preg_split("#\n\s*\n#Uis", $response); + return $parts[1]; + } + + return self::BAD_RESPONSE; + } + + return self::BAD_REQUEST; + } +} diff --git a/libraries/plugins/auth/recaptcha/ReCaptcha/RequestParameters.php b/libraries/plugins/auth/recaptcha/ReCaptcha/RequestParameters.php new file mode 100644 index 0000000000..f446842839 --- /dev/null +++ b/libraries/plugins/auth/recaptcha/ReCaptcha/RequestParameters.php @@ -0,0 +1,103 @@ +secret = $secret; + $this->response = $response; + $this->remoteIp = $remoteIp; + $this->version = $version; + } + + /** + * Array representation. + * + * @return array Array formatted parameters. + */ + public function toArray() + { + $params = array('secret' => $this->secret, 'response' => $this->response); + + if (!is_null($this->remoteIp)) { + $params['remoteip'] = $this->remoteIp; + } + + if (!is_null($this->version)) { + $params['version'] = $this->version; + } + + return $params; + } + + /** + * Query string representation for HTTP request. + * + * @return string Query string formatted parameters. + */ + public function toQueryString() + { + return http_build_query($this->toArray()); + } +} diff --git a/libraries/plugins/auth/recaptcha/ReCaptcha/Response.php b/libraries/plugins/auth/recaptcha/ReCaptcha/Response.php new file mode 100644 index 0000000000..d2d8a8bf77 --- /dev/null +++ b/libraries/plugins/auth/recaptcha/ReCaptcha/Response.php @@ -0,0 +1,102 @@ +success = $success; + $this->errorCodes = $errorCodes; + } + + /** + * Is success? + * + * @return boolean + */ + public function isSuccess() + { + return $this->success; + } + + /** + * Get error codes. + * + * @return array + */ + public function getErrorCodes() + { + return $this->errorCodes; + } +} diff --git a/libraries/plugins/auth/recaptcha/autoload.php b/libraries/plugins/auth/recaptcha/autoload.php new file mode 100644 index 0000000000..70c3c1c767 --- /dev/null +++ b/libraries/plugins/auth/recaptcha/autoload.php @@ -0,0 +1,38 @@ +" . self::$_signupUrl . ""); - } - $this->_secret=$secret; - } - - /** - * Encodes the given data into a query string format. - * - * @param array $data array of string elements to be encoded. - * - * @return string - encoded request. - */ - private function _encodeQS($data) - { - $req = ""; - foreach ($data as $key => $value) { - $req .= $key . '=' . urlencode(stripslashes($value)) . '&'; - } - - // Cut the last '&' - $req=substr($req, 0, strlen($req)-1); - return $req; - } - - /** - * Submits an HTTP GET to a reCAPTCHA server. - * - * @param string $path url path to recaptcha server. - * @param array $data array of parameters to be sent. - * - * @return array response - */ - private function _submitHTTPGet($path, $data) - { - $req = $this->_encodeQS($data); - $response = file_get_contents($path . $req); - return $response; - } - - /** - * Calls the reCAPTCHA siteverify API to verify whether the user passes - * CAPTCHA test. - * - * @param string $remoteIp IP address of end user. - * @param string $response response string from recaptcha verification. - * - * @return ReCaptchaResponse - */ - public function verifyResponse($remoteIp, $response) - { - // Discard empty solution submissions - if ($response == null || strlen($response) == 0) { - $recaptchaResponse = new ReCaptchaResponse(); - $recaptchaResponse->success = false; - $recaptchaResponse->errorCodes = 'missing-input'; - return $recaptchaResponse; - } - - $getResponse = $this->_submitHttpGet( - self::$_siteVerifyUrl, - array ( - 'secret' => $this->_secret, - 'remoteip' => $remoteIp, - 'v' => self::$_version, - 'response' => $response - ) - ); - $answers = json_decode($getResponse, true); - $recaptchaResponse = new ReCaptchaResponse(); - - if (trim($answers ['success']) == true) { - $recaptchaResponse->success = true; - } else { - $recaptchaResponse->success = false; - $recaptchaResponse->errorCodes = $answers ['error-codes']; - } - - return $recaptchaResponse; - } -} - -?>