Commit dfabd40c authored by Matija Obreza's avatar Matija Obreza

reCaptcha revisited

parent 3587ea78
......@@ -314,16 +314,6 @@
<scope>runtime</scope>
</dependency>
<!--Jetty -->
<dependency>
<groupId>net.tanesha.recaptcha4j</groupId>
<artifactId>recaptcha4j</artifactId>
<version>0.0.7</version>
</dependency>
<dependency>
<groupId>net.sf.opencsv</groupId>
<artifactId>opencsv</artifactId>
......
/*
* Copyright 2007 Soren Davidsen, Tanesha Networks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.tanesha.recaptcha;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.Properties;
import net.tanesha.recaptcha.http.HttpLoader;
import net.tanesha.recaptcha.http.SimpleHttpLoader;
public class ReCaptchaImpl implements ReCaptcha {
public static final String PROPERTY_THEME = "theme";
public static final String PROPERTY_TABINDEX = "tabindex";
public static final String HTTP_SERVER = "http://www.google.com/recaptcha/api";
public static final String HTTPS_SERVER = "https://www.google.com/recaptcha/api";
// HTTPS Verify url
public static final String VERIFY_URL = "https://www.google.com/recaptcha/api/verify";
private String privateKey;
private String publicKey;
private String recaptchaServer = HTTP_SERVER;
private boolean includeNoscript = false;
private HttpLoader httpLoader = new SimpleHttpLoader();
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
public void setRecaptchaServer(String recaptchaServer) {
this.recaptchaServer = recaptchaServer;
}
public void setIncludeNoscript(boolean includeNoscript) {
this.includeNoscript = includeNoscript;
}
public void setHttpLoader(HttpLoader httpLoader) {
this.httpLoader = httpLoader;
}
@Override
public ReCaptchaResponse checkAnswer(String remoteAddr, String challenge, String response) {
final String postParameters = "privatekey=" + URLEncoder.encode(privateKey) + "&remoteip=" + URLEncoder.encode(remoteAddr) + "&challenge="
+ URLEncoder.encode(challenge) + "&response=" + URLEncoder.encode(response);
final String message = httpLoader.httpPost(VERIFY_URL, postParameters);
if (message == null) {
return new ReCaptchaResponse(false, "Null read from server.");
}
final String[] a = message.split("\r?\n");
if (a.length < 1) {
return new ReCaptchaResponse(false, "No answer returned from recaptcha: " + message);
}
final boolean valid = "true".equals(a[0]);
String errorMessage = null;
if (!valid) {
if (a.length > 1) {
errorMessage = a[1];
} else {
errorMessage = "recaptcha4j-missing-error-message";
}
}
return new ReCaptchaResponse(valid, errorMessage);
}
@Override
public String createRecaptchaHtml(String errorMessage, Properties options) {
final String errorPart = errorMessage == null ? "" : "&amp;error=" + URLEncoder.encode(errorMessage);
String message = fetchJSOptions(options);
message += "<script type=\"text/javascript\" src=\"" + recaptchaServer + "/challenge?k=" + publicKey + errorPart + "\"></script>\r\n";
if (includeNoscript) {
final String noscript = "<noscript>\r\n" + " <iframe src=\"" + recaptchaServer + "/noscript?k=" + publicKey + errorPart
+ "\" height=\"300\" width=\"500\" frameborder=\"0\"></iframe><br>\r\n"
+ " <textarea name=\"recaptcha_challenge_field\" rows=\"3\" cols=\"40\"></textarea>\r\n"
+ " <input type=\"hidden\" name=\"recaptcha_response_field\" value=\"manual_challenge\">\r\n" + "</noscript>";
message += noscript;
}
return message;
}
@Override
public String createRecaptchaHtml(String errorMessage, String theme, Integer tabindex) {
final Properties options = new Properties();
if (theme != null) {
options.setProperty(PROPERTY_THEME, theme);
}
if (tabindex != null) {
options.setProperty(PROPERTY_TABINDEX, String.valueOf(tabindex));
}
return createRecaptchaHtml(errorMessage, options);
}
/**
* Produces javascript array with the RecaptchaOptions encoded.
*
* @param properties
* @return
*/
private String fetchJSOptions(Properties properties) {
if (properties == null || properties.size() == 0) {
return "";
}
String jsOptions = "<script type=\"text/javascript\">\r\n" + "var RecaptchaOptions = {";
for (final Enumeration e = properties.keys(); e.hasMoreElements();) {
final String property = (String) e.nextElement();
jsOptions += property + ":'" + properties.getProperty(property) + "'";
if (e.hasMoreElements()) {
jsOptions += ",";
}
}
jsOptions += "};\r\n</script>\r\n";
return jsOptions;
}
}
......@@ -16,10 +16,19 @@
package org.genesys2.server.servlet.controller;
import java.io.IOException;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.genesys2.server.model.UserRole;
import org.genesys2.server.model.impl.User;
import org.genesys2.server.security.lockout.AccountLockoutManager;
import org.genesys2.server.service.*;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.CropService;
import org.genesys2.server.service.EMailVerificationService;
import org.genesys2.server.service.OrganizationService;
import org.genesys2.server.service.StatisticsService;
import org.genesys2.server.service.UserService;
import org.genesys2.server.servlet.filter.LocaleURLFilter;
import org.genesys2.util.ReCaptchaUtil;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -36,10 +45,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Locale;
/**
* Controller which simply handles *.html requests
*/
......@@ -61,17 +66,11 @@ public class HtmlController extends BaseController {
@Autowired
private ContentService contentService;
@Autowired
private AccountLockoutManager lockoutManager;
@Value("${captcha.privateKey}")
private String captchaPrivateKey;
@Value("${captcha.publicKey}")
private String captchaPublicKey;
@Value("${captcha.siteKey}")
private String captchaSiteKey;
@Value("${captcha.privateKey}")
private String captchaPrivateKey;
@Autowired
private StatisticsService statisticsService;
......@@ -119,7 +118,7 @@ public class HtmlController extends BaseController {
@RequestMapping(value = "registration")
public String registration(ModelMap model) {
model.addAttribute("captchaSiteKey", captchaSiteKey);
model.addAttribute("captchaSiteKey", captchaPublicKey);
model.addAttribute("blurp", contentService.getGlobalArticle("registration", getLocale()));
return "/registration";
}
......@@ -127,13 +126,15 @@ public class HtmlController extends BaseController {
@RequestMapping(value = "new-user")
public String addUser(@ModelAttribute User user, BindingResult bindingResult, HttpServletRequest req,
@RequestParam(value = "g-recaptcha-response", required = false) String response) throws IOException {
user.getRoles().add(UserRole.USER);
validator.validate(user, bindingResult);
// Validate the reCAPTCHA
if (!ReCaptchaUtil.isValid(response)) {
if (!ReCaptchaUtil.isValid(response, req.getRemoteAddr(), captchaPrivateKey)) {
_logger.warn("Invalid captcha.");
final FieldError fieldError = new FieldError("comment", "captcha", response, false, new String[] { "errors.badCaptcha" }, null, "Please try again.");
final FieldError fieldError = new FieldError("comment", "captcha", response, false, new String[] { "errors.badCaptcha" }, null,
"Please try again.");
bindingResult.addError(fieldError);
}
......
......@@ -16,6 +16,12 @@
package org.genesys2.server.servlet.controller;
import java.io.IOException;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.genesys2.server.model.impl.User;
import org.genesys2.server.service.ContentService;
......@@ -39,11 +45,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Controller to manage request processing.
*
......@@ -119,7 +120,7 @@ public class RequestController extends BaseController {
}
// Validate the reCAPTCHA
if (currentUser == null && !ReCaptchaUtil.isValid(response)) {
if (currentUser == null && !ReCaptchaUtil.isValid(response, req.getRemoteAddr(), captchaPrivateKey)) {
_logger.warn("Recaptcha not valid. Stopping here.");
return start(model);
}
......@@ -156,7 +157,8 @@ public class RequestController extends BaseController {
}
@RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.GET)
public String validateClientRequest(ModelMap model, @PathVariable("tokenUuid") String tokenUuid, @RequestParam(value = "key", required = false) String key) {
public String validateClientRequest(ModelMap model, @PathVariable("tokenUuid") String tokenUuid,
@RequestParam(value = "key", required = false) String key) {
if (!model.containsAttribute("blurp")) {
model.addAttribute("blurp", contentService.getGlobalArticle("request-validate", getLocale()));
}
......@@ -166,7 +168,8 @@ public class RequestController extends BaseController {
}
@RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.POST)
public String validateClientRequest2(ModelMap model, @PathVariable("tokenUuid") String tokenUuid, @RequestParam(value = "key", required = true) String key) {
public String validateClientRequest2(ModelMap model, @PathVariable("tokenUuid") String tokenUuid,
@RequestParam(value = "key", required = true) String key) {
try {
requestService.validateClientRequest(tokenUuid, key);
......
......@@ -16,68 +16,79 @@
package org.genesys2.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.tanesha.recaptcha.ReCaptcha;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* {@link ReCaptcha} wrapper
*
* @author matijaobreza
*
*/
public class ReCaptchaUtil {
private final static Log LOG = LogFactory.getLog(ReCaptchaUtil.class);
private static final String URL = "https://www.google.com/recaptcha/api/siteverify";
private static final String captchaSecretKey = "6LfQ1g4TAAAAALNIQ_5tRbzEErflan1qdS9wAHXR";
private final static Log LOG = LogFactory.getLog(ReCaptchaUtil.class);
private static final String URL = "https://www.google.com/recaptcha/api/siteverify";
public static boolean isValid(String reCaptchaResponse, String remoteAddr, String captchaPrivateKey) throws IOException {
if (reCaptchaResponse == null || "".equals(reCaptchaResponse)) {
return false;
}
boolean isLocalRequest = false;
public static boolean isValid(String reCaptchaResponse) throws IOException {
if (reCaptchaResponse == null || "".equals(reCaptchaResponse)) {
return false;
}
URL url = new URL(URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
URL url = new URL(URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// add reuqest header
connection.setRequestMethod("POST");
// add reuqest header
connection.setRequestMethod("POST");
String postParams = "secret=" + captchaPrivateKey + "&response=" + reCaptchaResponse;
String postParams = "secret=" + captchaSecretKey + "&response=" + reCaptchaResponse;
// Send post request
connection.setDoOutput(true);
DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
dataOutputStream.writeBytes(postParams);
dataOutputStream.flush();
dataOutputStream.close();
// Send post request
connection.setDoOutput(true);
DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
dataOutputStream.writeBytes(postParams);
dataOutputStream.flush();
dataOutputStream.close();
try {
final InetAddress remoteInetAddr = InetAddress.getByName(remoteAddr);
isLocalRequest = remoteInetAddr.isLinkLocalAddress() || remoteInetAddr.isAnyLocalAddress() || remoteInetAddr.isLoopbackAddress();
LOG.warn("Remote addr: " + remoteAddr + " " + remoteInetAddr + " isLocal=" + isLocalRequest);
} catch (final UnknownHostException e1) {
LOG.warn(e1.getMessage());
}
int responseCode = connection.getResponseCode();
LOG.info("Send recaptcha post request to --> " + url + "\nPost parameters : " + postParams + "\n Response Code : " + responseCode);
int responseCode = connection.getResponseCode();
LOG.info("Send recaptcha post request to --> " + url + "\nPost parameters : " + postParams
+ "\n Response Code : " + responseCode);
if (isLocalRequest) {
LOG.info("Ignoring localhost re-captcha.");
// return true;
}
BufferedReader in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(response.toString());
ObjectMapper objectMapper = new ObjectMapper();
System.err.println(response.toString());
JsonNode jsonNode = objectMapper.readTree(response.toString());
return jsonNode.findValue("success").asBoolean();
}
return jsonNode.findValue("success").asBoolean();
}
}
......@@ -42,8 +42,8 @@ c3p0.maxPoolSize=5
c3p0.maxIdleTime=10
# reCAPTCHA API: These are our keys for localhost
captcha.privateKey=6Lf7oucSAAAAAExAgGEZeO2p1rCXfDmd9e
captcha.publicKey=6Lf7oucSAAAAAGaS7ObroY2bNgCqMTmpyFVu7wMW
captcha.privateKey=6Lfb5w4TAAAAADoSaBZsxx832HH5YHdCdEiS2v0s
captcha.publicKey=6Lfb5w4TAAAAAI6pD_4l5uIwXEaUJ6KQmhU9cyjx
# paths
download.files.dir=./data/
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment