#930 Create registration captcha manager

- Introduce registration captcha manager, rename login captcha manager accordingly
- Integrate reg. captcha manager into /register command

Open points:
- Refactor common captcha functionality into abstract superclass
- If captcha before /register necessary, show appropriate message to player immediately
- Unit tests
This commit is contained in:
ljacqu 2017-12-01 21:12:35 +01:00
parent 67a6a42dfe
commit 33904c09e9
11 changed files with 220 additions and 69 deletions

View File

@ -1,7 +1,8 @@
package fr.xephi.authme.command.executable.captcha; package fr.xephi.authme.command.executable.captcha;
import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.CaptchaManager; import fr.xephi.authme.data.LoginCaptchaManager;
import fr.xephi.authme.data.RegistrationCaptchaManager;
import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
@ -17,7 +18,10 @@ public class CaptchaCommand extends PlayerCommand {
private PlayerCache playerCache; private PlayerCache playerCache;
@Inject @Inject
private CaptchaManager captchaManager; private LoginCaptchaManager loginCaptchaManager;
@Inject
private RegistrationCaptchaManager registrationCaptchaManager;
@Inject @Inject
private CommonService commonService; private CommonService commonService;
@ -27,25 +31,40 @@ public class CaptchaCommand extends PlayerCommand {
@Override @Override
public void runCommand(Player player, List<String> arguments) { public void runCommand(Player player, List<String> arguments) {
final String playerName = player.getName().toLowerCase(); final String name = player.getName();
if (playerCache.isAuthenticated(playerName)) { if (playerCache.isAuthenticated(name)) {
commonService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); commonService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR);
} else if (!captchaManager.isCaptchaRequired(playerName)) { } else if (loginCaptchaManager.isCaptchaRequired(name)) {
commonService.send(player, MessageKey.USAGE_LOGIN); checkLoginCaptcha(player, arguments.get(0));
} else if (registrationCaptchaManager.isCaptchaRequired(name)) {
checkRegisterCaptcha(player, arguments.get(0));
} else { } else {
checkCaptcha(player, arguments.get(0)); MessageKey errorMessage = playerCache.isAuthenticated(name)
? MessageKey.ALREADY_LOGGED_IN_ERROR : MessageKey.USAGE_LOGIN;
commonService.send(player, errorMessage);
} }
} }
private void checkCaptcha(Player player, String captchaCode) { private void checkLoginCaptcha(Player player, String captchaCode) {
final boolean isCorrectCode = captchaManager.checkCode(player.getName(), captchaCode); final boolean isCorrectCode = loginCaptchaManager.checkCode(player.getName(), captchaCode);
if (isCorrectCode) { if (isCorrectCode) {
commonService.send(player, MessageKey.CAPTCHA_SUCCESS); commonService.send(player, MessageKey.CAPTCHA_SUCCESS);
commonService.send(player, MessageKey.LOGIN_MESSAGE); commonService.send(player, MessageKey.LOGIN_MESSAGE);
limboService.unmuteMessageTask(player); limboService.unmuteMessageTask(player);
} else { } else {
String newCode = captchaManager.generateCode(player.getName()); String newCode = loginCaptchaManager.generateCode(player.getName());
commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode);
}
}
private void checkRegisterCaptcha(Player player, String captchaCode) {
final boolean isCorrectCode = registrationCaptchaManager.checkCode(player.getName(), captchaCode);
if (isCorrectCode) {
commonService.send(player, MessageKey.CAPTCHA_SUCCESS);
commonService.send(player, MessageKey.REGISTER_MESSAGE);
} else {
String newCode = registrationCaptchaManager.generateCode(player.getName());
commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode); commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode);
} }
} }

View File

@ -2,6 +2,7 @@ package fr.xephi.authme.command.executable.register;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.RegistrationCaptchaManager;
import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management; import fr.xephi.authme.process.Management;
@ -45,8 +46,15 @@ public class RegisterCommand extends PlayerCommand {
@Inject @Inject
private ValidationService validationService; private ValidationService validationService;
@Inject
private RegistrationCaptchaManager registrationCaptchaManager;
@Override @Override
public void runCommand(Player player, List<String> arguments) { public void runCommand(Player player, List<String> arguments) {
if (!isCaptchaFulfilled(player)) {
return; // isCaptchaFulfilled handles informing the player on failure
}
if (commonService.getProperty(SecuritySettings.PASSWORD_HASH) == HashAlgorithm.TWO_FACTOR) { if (commonService.getProperty(SecuritySettings.PASSWORD_HASH) == HashAlgorithm.TWO_FACTOR) {
//for two factor auth we don't need to check the usage //for two factor auth we don't need to check the usage
management.performRegister(RegistrationMethod.TWO_FACTOR_REGISTRATION, management.performRegister(RegistrationMethod.TWO_FACTOR_REGISTRATION,
@ -74,7 +82,16 @@ public class RegisterCommand extends PlayerCommand {
@Override @Override
public MessageKey getArgumentsMismatchMessage() { public MessageKey getArgumentsMismatchMessage() {
return MessageKey.USAGE_LOGIN; return MessageKey.USAGE_REGISTER;
}
private boolean isCaptchaFulfilled(Player player) {
if (registrationCaptchaManager.isCaptchaRequired(player.getName())) {
String code = registrationCaptchaManager.getCaptchaCodeOrGenerateNew(player.getName());
commonService.send(player, MessageKey.CAPTCHA_FOR_REGISTRATION_REQUIRED, code);
return false;
}
return true;
} }
private void handlePasswordRegistration(Player player, List<String> arguments) { private void handlePasswordRegistration(Player player, List<String> arguments) {

View File

@ -12,9 +12,9 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* Manager for the handling of captchas. * Manager for the handling of captchas after too many failed login attempts.
*/ */
public class CaptchaManager implements SettingsDependent, HasCleanup { public class LoginCaptchaManager implements SettingsDependent, HasCleanup {
private final TimedCounter<String> playerCounts; private final TimedCounter<String> playerCounts;
private final ConcurrentHashMap<String, String> captchaCodes; private final ConcurrentHashMap<String, String> captchaCodes;
@ -24,7 +24,7 @@ public class CaptchaManager implements SettingsDependent, HasCleanup {
private int captchaLength; private int captchaLength;
@Inject @Inject
CaptchaManager(Settings settings) { LoginCaptchaManager(Settings settings) {
this.captchaCodes = new ConcurrentHashMap<>(); this.captchaCodes = new ConcurrentHashMap<>();
long countTimeout = settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET); long countTimeout = settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET);
this.playerCounts = new TimedCounter<>(countTimeout, TimeUnit.MINUTES); this.playerCounts = new TimedCounter<>(countTimeout, TimeUnit.MINUTES);
@ -36,7 +36,7 @@ public class CaptchaManager implements SettingsDependent, HasCleanup {
* *
* @param name the player's name * @param name the player's name
*/ */
public void increaseCount(String name) { public void increaseLoginFailureCount(String name) {
if (isEnabled) { if (isEnabled) {
String playerLower = name.toLowerCase(); String playerLower = name.toLowerCase();
playerCounts.increment(playerLower); playerCounts.increment(playerLower);
@ -44,7 +44,7 @@ public class CaptchaManager implements SettingsDependent, HasCleanup {
} }
/** /**
* Returns whether the given player is required to solve a captcha. * Returns whether the given player is required to solve a captcha before he can use /login again.
* *
* @param name the name of the player to verify * @param name the name of the player to verify
* @return true if the player has to solve a captcha, false otherwise * @return true if the player has to solve a captcha, false otherwise
@ -84,12 +84,13 @@ public class CaptchaManager implements SettingsDependent, HasCleanup {
* @return true if the code matches or if no captcha is required for the player, false otherwise * @return true if the code matches or if no captcha is required for the player, false otherwise
*/ */
public boolean checkCode(String name, String code) { public boolean checkCode(String name, String code) {
String savedCode = captchaCodes.get(name.toLowerCase()); final String nameLowerCase = name.toLowerCase();
String savedCode = captchaCodes.get(nameLowerCase);
if (savedCode == null) { if (savedCode == null) {
return true; return true;
} else if (savedCode.equalsIgnoreCase(code)) { } else if (savedCode.equalsIgnoreCase(code)) {
captchaCodes.remove(name.toLowerCase()); captchaCodes.remove(nameLowerCase);
playerCounts.remove(name.toLowerCase()); playerCounts.remove(nameLowerCase);
return true; return true;
} }
return false; return false;
@ -100,7 +101,7 @@ public class CaptchaManager implements SettingsDependent, HasCleanup {
* *
* @param name the player's name * @param name the player's name
*/ */
public void resetCounts(String name) { public void resetLoginFailureCount(String name) {
if (isEnabled) { if (isEnabled) {
captchaCodes.remove(name.toLowerCase()); captchaCodes.remove(name.toLowerCase());
playerCounts.remove(name.toLowerCase()); playerCounts.remove(name.toLowerCase());
@ -109,7 +110,7 @@ public class CaptchaManager implements SettingsDependent, HasCleanup {
@Override @Override
public void reload(Settings settings) { public void reload(Settings settings) {
this.isEnabled = settings.getProperty(SecuritySettings.USE_CAPTCHA); this.isEnabled = settings.getProperty(SecuritySettings.ENABLE_LOGIN_FAILURE_CAPTCHA);
this.threshold = settings.getProperty(SecuritySettings.MAX_LOGIN_TRIES_BEFORE_CAPTCHA); this.threshold = settings.getProperty(SecuritySettings.MAX_LOGIN_TRIES_BEFORE_CAPTCHA);
this.captchaLength = settings.getProperty(SecuritySettings.CAPTCHA_LENGTH); this.captchaLength = settings.getProperty(SecuritySettings.CAPTCHA_LENGTH);
long countTimeout = settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET); long countTimeout = settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET);

View File

@ -0,0 +1,98 @@
package fr.xephi.authme.data;
import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.util.expiring.ExpiringSet;
import javax.inject.Inject;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* Captcha handler for registration.
*/
public class RegistrationCaptchaManager implements SettingsDependent, HasCleanup {
private static final int MINUTES_VALID_FOR_REGISTRATION = 30;
private final Map<String, String> captchaCodes;
private final ExpiringSet<String> verifiedNamesForRegistration;
private boolean isEnabledForRegistration;
private int captchaLength;
@Inject
RegistrationCaptchaManager(Settings settings) {
this.captchaCodes = new ConcurrentHashMap<>();
this.verifiedNamesForRegistration = new ExpiringSet<>(MINUTES_VALID_FOR_REGISTRATION, TimeUnit.MINUTES);
reload(settings);
}
/**
* Returns whether the given player is required to solve a captcha before he can register.
*
* @param name the name of the player to verify
* @return true if the player has to solve a captcha, false otherwise
*/
public boolean isCaptchaRequired(String name) {
return isEnabledForRegistration && !verifiedNamesForRegistration.contains(name.toLowerCase());
}
/**
* Returns the stored captcha for the player or generates and saves a new one.
*
* @param name the player's name
* @return the code the player is required to enter
*/
public String getCaptchaCodeOrGenerateNew(String name) {
String code = captchaCodes.get(name.toLowerCase());
return code == null ? generateCode(name) : code;
}
/**
* Generates a code for the player and returns it.
*
* @param name the name of the player to generate a code for
* @return the generated code
*/
public String generateCode(String name) {
String code = RandomStringUtils.generate(captchaLength);
captchaCodes.put(name.toLowerCase(), code);
return code;
}
/**
* Checks the given code against the existing one and resets the player's auth failure count upon success.
*
* @param name the name of the player to check
* @param code the supplied code
* @return true if the code matches or if no captcha is required for the player, false otherwise
*/
public boolean checkCode(String name, String code) {
final String nameLowerCase = name.toLowerCase();
String savedCode = captchaCodes.get(nameLowerCase);
if (savedCode == null) {
return true;
} else if (savedCode.equalsIgnoreCase(code)) {
captchaCodes.remove(nameLowerCase);
verifiedNamesForRegistration.add(nameLowerCase);
return true;
}
return false;
}
@Override
public void reload(Settings settings) {
this.isEnabledForRegistration = settings.getProperty(SecuritySettings.ENABLE_CAPTCHA_FOR_REGISTRATION);
this.captchaLength = settings.getProperty(SecuritySettings.CAPTCHA_LENGTH);
}
@Override
public void performCleanup() {
verifiedNamesForRegistration.removeExpiredEntries();
}
}

View File

@ -134,6 +134,9 @@ public enum MessageKey {
/** Captcha code solved correctly! */ /** Captcha code solved correctly! */
CAPTCHA_SUCCESS("valid_captcha"), CAPTCHA_SUCCESS("valid_captcha"),
/** To register you have to solve a captcha code first, please use the command: /captcha &lt;theCaptcha&gt; */
CAPTCHA_FOR_REGISTRATION_REQUIRED("captcha_for_registration", "<theCaptcha>"),
/** A VIP player has joined the server when it was full! */ /** A VIP player has joined the server when it was full! */
KICK_FOR_VIP("kick_forvip"), KICK_FOR_VIP("kick_forvip"),

View File

@ -2,7 +2,7 @@ package fr.xephi.authme.process.login;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.CaptchaManager; import fr.xephi.authme.data.LoginCaptchaManager;
import fr.xephi.authme.data.TempbanManager; import fr.xephi.authme.data.TempbanManager;
import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.data.auth.PlayerCache;
@ -61,7 +61,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
private PasswordSecurity passwordSecurity; private PasswordSecurity passwordSecurity;
@Inject @Inject
private CaptchaManager captchaManager; private LoginCaptchaManager loginCaptchaManager;
@Inject @Inject
private TempbanManager tempbanManager; private TempbanManager tempbanManager;
@ -163,15 +163,15 @@ public class AsynchronousLogin implements AsynchronousProcess {
final String name = player.getName().toLowerCase(); final String name = player.getName().toLowerCase();
// If captcha is required send a message to the player and deny to log in // If captcha is required send a message to the player and deny to log in
if (captchaManager.isCaptchaRequired(name)) { if (loginCaptchaManager.isCaptchaRequired(name)) {
service.send(player, MessageKey.USAGE_CAPTCHA, captchaManager.getCaptchaCodeOrGenerateNew(name)); service.send(player, MessageKey.USAGE_CAPTCHA, loginCaptchaManager.getCaptchaCodeOrGenerateNew(name));
return false; return false;
} }
final String ip = PlayerUtils.getPlayerIp(player); final String ip = PlayerUtils.getPlayerIp(player);
// Increase the counts here before knowing the result of the login. // Increase the counts here before knowing the result of the login.
captchaManager.increaseCount(name); loginCaptchaManager.increaseLoginFailureCount(name);
tempbanManager.increaseCount(ip, name); tempbanManager.increaseCount(ip, name);
if (passwordSecurity.comparePassword(password, auth.getPassword(), player.getName())) { if (passwordSecurity.comparePassword(password, auth.getPassword(), player.getName())) {
@ -202,10 +202,10 @@ public class AsynchronousLogin implements AsynchronousProcess {
service.send(player, MessageKey.WRONG_PASSWORD); service.send(player, MessageKey.WRONG_PASSWORD);
// If the authentication fails check if Captcha is required and send a message to the player // If the authentication fails check if Captcha is required and send a message to the player
if (captchaManager.isCaptchaRequired(player.getName())) { if (loginCaptchaManager.isCaptchaRequired(player.getName())) {
limboService.muteMessageTask(player); limboService.muteMessageTask(player);
service.send(player, MessageKey.USAGE_CAPTCHA, service.send(player, MessageKey.USAGE_CAPTCHA,
captchaManager.getCaptchaCodeOrGenerateNew(player.getName())); loginCaptchaManager.getCaptchaCodeOrGenerateNew(player.getName()));
} else if (emailService.hasAllInformation() && !Utils.isEmailEmpty(auth.getEmail())) { } else if (emailService.hasAllInformation() && !Utils.isEmailEmpty(auth.getEmail())) {
service.send(player, MessageKey.FORGOT_PASSWORD_MESSAGE); service.send(player, MessageKey.FORGOT_PASSWORD_MESSAGE);
} }
@ -232,7 +232,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
// Successful login, so reset the captcha & temp ban count // Successful login, so reset the captcha & temp ban count
final String name = player.getName(); final String name = player.getName();
captchaManager.resetCounts(name); loginCaptchaManager.resetLoginFailureCount(name);
tempbanManager.resetCount(ip, name); tempbanManager.resetCount(ip, name);
player.setNoDamageTicks(0); player.setNoDamageTicks(0);

View File

@ -29,7 +29,7 @@ public final class SecuritySettings implements SettingsHolder {
newProperty("Security.console.logConsole", true); newProperty("Security.console.logConsole", true);
@Comment("Enable captcha when a player uses wrong password too many times") @Comment("Enable captcha when a player uses wrong password too many times")
public static final Property<Boolean> USE_CAPTCHA = public static final Property<Boolean> ENABLE_LOGIN_FAILURE_CAPTCHA =
newProperty("Security.captcha.useCaptcha", false); newProperty("Security.captcha.useCaptcha", false);
@Comment("Max allowed tries before a captcha is required") @Comment("Max allowed tries before a captcha is required")
@ -44,6 +44,10 @@ public final class SecuritySettings implements SettingsHolder {
public static final Property<Integer> CAPTCHA_COUNT_MINUTES_BEFORE_RESET = public static final Property<Integer> CAPTCHA_COUNT_MINUTES_BEFORE_RESET =
newProperty("Security.captcha.captchaCountReset", 60); newProperty("Security.captcha.captchaCountReset", 60);
@Comment("Require captcha before a player may register?")
public static final Property<Boolean> ENABLE_CAPTCHA_FOR_REGISTRATION =
newProperty("Security.captcha.requireForRegistration", false);
@Comment("Minimum length of password") @Comment("Minimum length of password")
public static final Property<Integer> MIN_PASSWORD_LENGTH = public static final Property<Integer> MIN_PASSWORD_LENGTH =
newProperty("settings.security.minPasswordLength", 5); newProperty("settings.security.minPasswordLength", 5);

View File

@ -91,9 +91,10 @@ change_password_expired: 'You cannot change your password using this command any
email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha # Captcha
usage_captcha: '&3To login you have to solve a captcha code, please use the command: /captcha <theCaptcha>' usage_captcha: '&3To log in you have to solve a captcha code, please use the command: /captcha <theCaptcha>'
wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!' wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!'
valid_captcha: '&2Captcha code solved correctly!' valid_captcha: '&2Captcha code solved correctly!'
captcha_for_registration: 'To register you have to solve a captcha first, please use the command: /captcha <theCaptcha>'
# Verification code # Verification code
verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.'
@ -101,7 +102,7 @@ usage_verification_code: '&cUsage: /verification <code>'
incorrect_verification_code: '&cIncorrect code, please type "/verification <code>" into the chat, using the code you received by email' incorrect_verification_code: '&cIncorrect code, please type "/verification <code>" into the chat, using the code you received by email'
verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!'
verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' verification_code_already_verified: '&2You can already execute every sensitive command within the current session!'
verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' verification_code_expired: '&3Your code has expired! Execute another sensitive command to get a new code!'
verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!'
# Time units # Time units

View File

@ -1,6 +1,7 @@
package fr.xephi.authme.command.executable.captcha; package fr.xephi.authme.command.executable.captcha;
import fr.xephi.authme.data.CaptchaManager; import fr.xephi.authme.data.LoginCaptchaManager;
import fr.xephi.authme.data.RegistrationCaptchaManager;
import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
@ -29,7 +30,10 @@ public class CaptchaCommandTest {
private CaptchaCommand command; private CaptchaCommand command;
@Mock @Mock
private CaptchaManager captchaManager; private LoginCaptchaManager loginCaptchaManager;
@Mock
private RegistrationCaptchaManager registrationCaptchaManager;
@Mock @Mock
private PlayerCache playerCache; private PlayerCache playerCache;
@ -60,15 +64,15 @@ public class CaptchaCommandTest {
String name = "bobby"; String name = "bobby";
Player player = mockPlayerWithName(name); Player player = mockPlayerWithName(name);
given(playerCache.isAuthenticated(name)).willReturn(false); given(playerCache.isAuthenticated(name)).willReturn(false);
given(captchaManager.isCaptchaRequired(name)).willReturn(false); given(loginCaptchaManager.isCaptchaRequired(name)).willReturn(false);
// when // when
command.executeCommand(player, Collections.singletonList("1234")); command.executeCommand(player, Collections.singletonList("1234"));
// then // then
verify(commandService).send(player, MessageKey.USAGE_LOGIN); verify(commandService).send(player, MessageKey.USAGE_LOGIN);
verify(captchaManager).isCaptchaRequired(name); verify(loginCaptchaManager).isCaptchaRequired(name);
verifyNoMoreInteractions(captchaManager); verifyNoMoreInteractions(loginCaptchaManager);
} }
@Test @Test
@ -77,17 +81,17 @@ public class CaptchaCommandTest {
String name = "smith"; String name = "smith";
Player player = mockPlayerWithName(name); Player player = mockPlayerWithName(name);
given(playerCache.isAuthenticated(name)).willReturn(false); given(playerCache.isAuthenticated(name)).willReturn(false);
given(captchaManager.isCaptchaRequired(name)).willReturn(true); given(loginCaptchaManager.isCaptchaRequired(name)).willReturn(true);
String captchaCode = "3991"; String captchaCode = "3991";
given(captchaManager.checkCode(name, captchaCode)).willReturn(true); given(loginCaptchaManager.checkCode(name, captchaCode)).willReturn(true);
// when // when
command.executeCommand(player, Collections.singletonList(captchaCode)); command.executeCommand(player, Collections.singletonList(captchaCode));
// then // then
verify(captchaManager).isCaptchaRequired(name); verify(loginCaptchaManager).isCaptchaRequired(name);
verify(captchaManager).checkCode(name, captchaCode); verify(loginCaptchaManager).checkCode(name, captchaCode);
verifyNoMoreInteractions(captchaManager); verifyNoMoreInteractions(loginCaptchaManager);
verify(commandService).send(player, MessageKey.CAPTCHA_SUCCESS); verify(commandService).send(player, MessageKey.CAPTCHA_SUCCESS);
verify(commandService).send(player, MessageKey.LOGIN_MESSAGE); verify(commandService).send(player, MessageKey.LOGIN_MESSAGE);
verify(limboService).unmuteMessageTask(player); verify(limboService).unmuteMessageTask(player);
@ -100,20 +104,20 @@ public class CaptchaCommandTest {
String name = "smith"; String name = "smith";
Player player = mockPlayerWithName(name); Player player = mockPlayerWithName(name);
given(playerCache.isAuthenticated(name)).willReturn(false); given(playerCache.isAuthenticated(name)).willReturn(false);
given(captchaManager.isCaptchaRequired(name)).willReturn(true); given(loginCaptchaManager.isCaptchaRequired(name)).willReturn(true);
String captchaCode = "2468"; String captchaCode = "2468";
given(captchaManager.checkCode(name, captchaCode)).willReturn(false); given(loginCaptchaManager.checkCode(name, captchaCode)).willReturn(false);
String newCode = "1337"; String newCode = "1337";
given(captchaManager.generateCode(name)).willReturn(newCode); given(loginCaptchaManager.generateCode(name)).willReturn(newCode);
// when // when
command.executeCommand(player, Collections.singletonList(captchaCode)); command.executeCommand(player, Collections.singletonList(captchaCode));
// then // then
verify(captchaManager).isCaptchaRequired(name); verify(loginCaptchaManager).isCaptchaRequired(name);
verify(captchaManager).checkCode(name, captchaCode); verify(loginCaptchaManager).checkCode(name, captchaCode);
verify(captchaManager).generateCode(name); verify(loginCaptchaManager).generateCode(name);
verifyNoMoreInteractions(captchaManager); verifyNoMoreInteractions(loginCaptchaManager);
verify(commandService).send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode); verify(commandService).send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode);
verifyNoMoreInteractions(commandService); verifyNoMoreInteractions(commandService);
} }

View File

@ -1,6 +1,7 @@
package fr.xephi.authme.command.executable.register; package fr.xephi.authme.command.executable.register;
import fr.xephi.authme.TestHelper; import fr.xephi.authme.TestHelper;
import fr.xephi.authme.data.RegistrationCaptchaManager;
import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management; import fr.xephi.authme.process.Management;
@ -60,6 +61,9 @@ public class RegisterCommandTest {
@Mock @Mock
private ValidationService validationService; private ValidationService validationService;
@Mock
private RegistrationCaptchaManager registrationCaptchaManager;
@BeforeClass @BeforeClass
public static void setup() { public static void setup() {
TestHelper.setupLogger(); TestHelper.setupLogger();

View File

@ -12,25 +12,25 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
* Test for {@link CaptchaManager}. * Test for {@link LoginCaptchaManager}.
*/ */
public class CaptchaManagerTest { public class LoginCaptchaManagerTest {
@Test @Test
public void shouldAddCounts() { public void shouldAddCounts() {
// given // given
Settings settings = mockSettings(3, 4); Settings settings = mockSettings(3, 4);
CaptchaManager manager = new CaptchaManager(settings); LoginCaptchaManager manager = new LoginCaptchaManager(settings);
String player = "tester"; String player = "tester";
// when // when
for (int i = 0; i < 2; ++i) { for (int i = 0; i < 2; ++i) {
manager.increaseCount(player); manager.increaseLoginFailureCount(player);
} }
// then // then
assertThat(manager.isCaptchaRequired(player), equalTo(false)); assertThat(manager.isCaptchaRequired(player), equalTo(false));
manager.increaseCount(player); manager.increaseLoginFailureCount(player);
assertThat(manager.isCaptchaRequired(player.toUpperCase()), equalTo(true)); assertThat(manager.isCaptchaRequired(player.toUpperCase()), equalTo(true));
assertThat(manager.isCaptchaRequired("otherPlayer"), equalTo(false)); assertThat(manager.isCaptchaRequired("otherPlayer"), equalTo(false));
} }
@ -40,7 +40,7 @@ public class CaptchaManagerTest {
// given // given
String player = "Miner"; String player = "Miner";
Settings settings = mockSettings(1, 4); Settings settings = mockSettings(1, 4);
CaptchaManager manager = new CaptchaManager(settings); LoginCaptchaManager manager = new LoginCaptchaManager(settings);
String captchaCode = manager.getCaptchaCodeOrGenerateNew(player); String captchaCode = manager.getCaptchaCodeOrGenerateNew(player);
// when // when
@ -60,7 +60,7 @@ public class CaptchaManagerTest {
// given // given
String player = "Tester"; String player = "Tester";
Settings settings = mockSettings(1, 5); Settings settings = mockSettings(1, 5);
CaptchaManager manager = new CaptchaManager(settings); LoginCaptchaManager manager = new LoginCaptchaManager(settings);
// when // when
String code1 = manager.getCaptchaCodeOrGenerateNew(player); String code1 = manager.getCaptchaCodeOrGenerateNew(player);
@ -78,18 +78,18 @@ public class CaptchaManagerTest {
// given // given
String player = "plaYer"; String player = "plaYer";
Settings settings = mockSettings(2, 3); Settings settings = mockSettings(2, 3);
CaptchaManager manager = new CaptchaManager(settings); LoginCaptchaManager manager = new LoginCaptchaManager(settings);
// when // when
manager.increaseCount(player); manager.increaseLoginFailureCount(player);
manager.increaseCount(player); manager.increaseLoginFailureCount(player);
// then // then
assertThat(manager.isCaptchaRequired(player), equalTo(true)); assertThat(manager.isCaptchaRequired(player), equalTo(true));
assertHasCount(manager, player, 2); assertHasCount(manager, player, 2);
// when 2 // when 2
manager.resetCounts(player); manager.resetLoginFailureCount(player);
// then 2 // then 2
assertThat(manager.isCaptchaRequired(player), equalTo(false)); assertThat(manager.isCaptchaRequired(player), equalTo(false));
@ -101,11 +101,11 @@ public class CaptchaManagerTest {
// given // given
String player = "someone_"; String player = "someone_";
Settings settings = mockSettings(1, 3); Settings settings = mockSettings(1, 3);
given(settings.getProperty(SecuritySettings.USE_CAPTCHA)).willReturn(false); given(settings.getProperty(SecuritySettings.ENABLE_LOGIN_FAILURE_CAPTCHA)).willReturn(false);
CaptchaManager manager = new CaptchaManager(settings); LoginCaptchaManager manager = new LoginCaptchaManager(settings);
// when // when
manager.increaseCount(player); manager.increaseLoginFailureCount(player);
// then // then
assertThat(manager.isCaptchaRequired(player), equalTo(false)); assertThat(manager.isCaptchaRequired(player), equalTo(false));
@ -117,11 +117,11 @@ public class CaptchaManagerTest {
// given // given
String player = "Robert001"; String player = "Robert001";
Settings settings = mockSettings(1, 5); Settings settings = mockSettings(1, 5);
CaptchaManager manager = new CaptchaManager(settings); LoginCaptchaManager manager = new LoginCaptchaManager(settings);
given(settings.getProperty(SecuritySettings.USE_CAPTCHA)).willReturn(false); given(settings.getProperty(SecuritySettings.ENABLE_LOGIN_FAILURE_CAPTCHA)).willReturn(false);
// when // when
manager.increaseCount(player); manager.increaseLoginFailureCount(player);
// assumptions // assumptions
assertThat(manager.isCaptchaRequired(player), equalTo(true)); assertThat(manager.isCaptchaRequired(player), equalTo(true));
assertHasCount(manager, player, 1); assertHasCount(manager, player, 1);
@ -135,16 +135,16 @@ public class CaptchaManagerTest {
private static Settings mockSettings(int maxTries, int captchaLength) { private static Settings mockSettings(int maxTries, int captchaLength) {
Settings settings = mock(Settings.class); Settings settings = mock(Settings.class);
given(settings.getProperty(SecuritySettings.USE_CAPTCHA)).willReturn(true); given(settings.getProperty(SecuritySettings.ENABLE_LOGIN_FAILURE_CAPTCHA)).willReturn(true);
given(settings.getProperty(SecuritySettings.MAX_LOGIN_TRIES_BEFORE_CAPTCHA)).willReturn(maxTries); given(settings.getProperty(SecuritySettings.MAX_LOGIN_TRIES_BEFORE_CAPTCHA)).willReturn(maxTries);
given(settings.getProperty(SecuritySettings.CAPTCHA_LENGTH)).willReturn(captchaLength); given(settings.getProperty(SecuritySettings.CAPTCHA_LENGTH)).willReturn(captchaLength);
given(settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET)).willReturn(30); given(settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET)).willReturn(30);
return settings; return settings;
} }
private static void assertHasCount(CaptchaManager manager, String player, Integer count) { private static void assertHasCount(LoginCaptchaManager manager, String player, Integer count) {
TimedCounter<String> playerCounts = ReflectionTestUtils TimedCounter<String> playerCounts = ReflectionTestUtils
.getFieldValue(CaptchaManager.class, manager, "playerCounts"); .getFieldValue(LoginCaptchaManager.class, manager, "playerCounts");
assertThat(playerCounts.get(player.toLowerCase()), equalTo(count)); assertThat(playerCounts.get(player.toLowerCase()), equalTo(count));
} }
} }