diff --git a/docs/config.md b/docs/config.md index 212d83a7..54a87418 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,5 +1,5 @@ - + ## AuthMe Configuration The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder, @@ -102,10 +102,14 @@ settings: # Message language, available languages: # https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md messagesLanguage: 'en' + # Log level: INFO, FINE, DEBUG. Use INFO for general messages, + # FINE for some additional detailed ones (like password failed), + # and DEBUG for debugging + logLevel: 'FINE' + # By default we schedule async tasks when talking to the database. If you want + # typical communication with the database to happen synchronously, set this to false + useAsyncTasks: true restrictions: - # Keeps collisions disabled for logged players - # Works only with MC 1.9 - keepCollisionsDisabled: false # Can not authenticated players chat? # Keep in mind that this feature also blocks all commands not # listed in the list below. @@ -158,7 +162,7 @@ settings: # Should unregistered players be kicked immediately? kickNonRegistered: false # Should players be kicked on wrong password? - kickOnWrongPassword: false + kickOnWrongPassword: true # Should not logged in players be teleported to the spawn? # After the authentication they will be teleported back to # their normal position. @@ -176,14 +180,10 @@ settings: # How far can unregistered players walk? # Set to 0 for unlimited radius allowedMovementRadius: 100 - # Enable double check of password when you register - # when it's true, registration requires that kind of command: - # /register - enablePasswordConfirmation: true # Should we protect the player inventory before logging in? Requires ProtocolLib. ProtectInventoryBeforeLogIn: true # Should we deny the tabcomplete feature before logging in? Requires ProtocolLib. - DenyTabCompleteBeforeLogin: true + DenyTabCompleteBeforeLogin: false # Should we display all other accounts from a player when he joins? # permission: /authme.admin.accounts displayOtherAccounts: true @@ -204,13 +204,6 @@ settings: # Command to run when a user has more accounts than the configured threshold. # Available variables: %playername%, %playerip% otherAccountsCmd: 'say The player %playername% with ip %playerip% has multiple accounts!' - # Log level: INFO, FINE, DEBUG. Use INFO for general messages, - # FINE for some additional detailed ones (like password failed), - # and DEBUG for debugging - logLevel: 'FINE' - # By default we schedule async tasks when talking to the database. If you want - # typical communication with the database to happen synchronously, set this to false - useAsyncTasks: true GameMode: # Force survival gamemode when player joins? ForceSurvivalMode: false @@ -240,9 +233,10 @@ settings: # Otherwise your group will be wiped and the player will join in the default group []! # Example unLoggedinGroup: NotLogged unLoggedinGroup: 'unLoggedinGroup' - # Possible values: MD5, SHA1, SHA256, WHIRLPOOL, XAUTH, MD5VB, PHPBB, - # MYBB, IPB3, PHPFUSION, SMF, XENFORO, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512, - # DOUBLEMD5, PBKDF2, PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (for developers only) + # Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL, + # MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB, + # PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (for developers only). See full list at + # https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/hash_algorithms.md passwordHash: 'SHA256' # Salt length for the SALTED2MD5 MD5(MD5(password)+salt) doubleMD5SaltLength: 8 @@ -252,6 +246,8 @@ settings: # legacyHashes: # - 'SHA1' legacyHashes: [] + # Number of rounds to use if passwordHash is set to PBKDF2. Default is 10000 + pbkdf2Rounds: 10000 # Prevent unsafe passwords from being used; put them in lowercase! # You should always set 'help' as unsafePassword due to possible conflicts. # unsafePasswords: @@ -275,27 +271,14 @@ settings: # Only registered and logged in players can play. # See restrictions for exceptions force: true - # Do we replace password registration by an email registration method? - enableEmailRegistrationSystem: false - # Enable double check of email when you register - # when it's true, registration requires that kind of command: - # /register - doubleEmailCheck: false + # Type of registration: PASSWORD, PASSWORD_WITH_CONFIRMATION, EMAIL + # EMAIL_WITH_CONFIRMATION, PASSWORD_WITH_EMAIL + type: 'PASSWORD_WITH_CONFIRMATION' # Do we force kick a player after a successful registration? # Do not use with login feature below forceKickAfterRegister: false # Does AuthMe need to enforce a /login after a successful registration? forceLoginAfterRegister: false - # Force these commands after /login, without any '/', use %p to replace with player name - forceCommands: [] - # Force these commands after /login as service console, without any '/'. - # Use %p to replace with player name - forceCommandsAsConsole: [] - # Force these commands after /register, without any '/', use %p to replace with player name - forceRegisterCommands: [] - # Force these commands after /register as a server console, without any '/'. - # Use %p to replace with player name - forceRegisterCommandsAsConsole: [] # Enable to display the welcome message (welcome.txt) after a login # You can use colors in this welcome.txt + some replaced strings: # {PLAYER}: player name, {ONLINE}: display number of online players, @@ -318,7 +301,7 @@ settings: applyBlindEffect: false # Do we need to prevent people to login with another case? # If Xephi is registered, then Xephi can login, but not XEPHI/xephi/XePhI - preventOtherCase: false + preventOtherCase: true permission: # Take care with this option; if you want # to use group switching of AuthMe @@ -363,7 +346,7 @@ Hooks: # Send player to this BungeeCord server after register/login sendPlayerTo: '' # Do we need to disable Essentials SocialSpy on join? - disableSocialSpy: true + disableSocialSpy: false # Do we need to force /motd Essentials command on join? useEssentialsMotd: false GroupOptions: @@ -464,4 +447,4 @@ To change settings on a running server, save your changes to config.yml and use --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Nov 13 13:34:49 CET 2016 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Thu Dec 15 22:27:25 CET 2016 diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index 82a2483b..d4f34b2c 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -4,10 +4,11 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.process.Management; +import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.service.ValidationService; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -33,13 +34,15 @@ public class NewAPI { private final Management management; private final ValidationService validationService; private final PlayerCache playerCache; + private final RegistrationExecutorProvider registrationExecutorProvider; /* * Constructor for NewAPI. */ @Inject NewAPI(AuthMe plugin, PluginHookService pluginHookService, DataSource dataSource, PasswordSecurity passwordSecurity, - Management management, ValidationService validationService, PlayerCache playerCache) { + Management management, ValidationService validationService, PlayerCache playerCache, + RegistrationExecutorProvider registrationExecutorProvider) { this.plugin = plugin; this.pluginHookService = pluginHookService; this.dataSource = dataSource; @@ -47,6 +50,7 @@ public class NewAPI { this.management = management; this.validationService = validationService; this.playerCache = playerCache; + this.registrationExecutorProvider = registrationExecutorProvider; NewAPI.singleton = this; } @@ -199,7 +203,8 @@ public class NewAPI { * @param autoLogin Should the player be authenticated automatically after the registration? */ public void forceRegister(Player player, String password, boolean autoLogin) { - management.performRegister(player, password, null, autoLogin); + management.performRegister(player, + registrationExecutorProvider.getPasswordRegisterExecutor(player, password, autoLogin)); } /** diff --git a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java index cc18adbf..cc5a0315 100644 --- a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java @@ -5,21 +5,23 @@ import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.mail.SendMailSSL; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; +import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.settings.properties.RegistrationArgumentType; +import fr.xephi.authme.settings.properties.RegistrationArgumentType.Execution; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.util.RandomStringUtils; import org.bukkit.entity.Player; import javax.inject.Inject; import java.util.List; -import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH; -import static fr.xephi.authme.settings.properties.RegistrationSettings.ENABLE_CONFIRM_EMAIL; -import static fr.xephi.authme.settings.properties.RegistrationSettings.USE_EMAIL_REGISTRATION; -import static fr.xephi.authme.settings.properties.RestrictionSettings.ENABLE_PASSWORD_CONFIRMATION; +import static fr.xephi.authme.settings.properties.RegistrationArgumentType.EMAIL_WITH_CONFIRMATION; +import static fr.xephi.authme.settings.properties.RegistrationArgumentType.PASSWORD_WITH_CONFIRMATION; +import static fr.xephi.authme.settings.properties.RegistrationArgumentType.PASSWORD_WITH_EMAIL; +import static fr.xephi.authme.settings.properties.RegistrationSettings.REGISTRATION_TYPE; /** * Command for /register. @@ -38,22 +40,26 @@ public class RegisterCommand extends PlayerCommand { @Inject private ValidationService validationService; + @Inject + private RegistrationExecutorProvider registrationExecutorProvider; + @Override public void runCommand(Player player, List arguments) { if (commonService.getProperty(SecuritySettings.PASSWORD_HASH) == HashAlgorithm.TWO_FACTOR) { //for two factor auth we don't need to check the usage - management.performRegister(player, "", "", true); + management.performRegister(player, + registrationExecutorProvider.getTwoFactorRegisterExecutor(player)); return; } // Ensure that there is 1 argument, or 2 if confirmation is required - final boolean useConfirmation = isConfirmationRequired(); - if (arguments.isEmpty() || useConfirmation && arguments.size() < 2) { + RegistrationArgumentType registerType = commonService.getProperty(REGISTRATION_TYPE); + if (registerType.getRequiredNumberOfArgs() > arguments.size()) { commonService.send(player, MessageKey.USAGE_REGISTER); return; } - if (commonService.getProperty(USE_EMAIL_REGISTRATION)) { + if (registerType.getExecution() == Execution.EMAIL) { handleEmailRegistration(player, arguments); } else { handlePasswordRegistration(player, arguments); @@ -66,10 +72,23 @@ public class RegisterCommand extends PlayerCommand { } private void handlePasswordRegistration(Player player, List arguments) { - if (commonService.getProperty(ENABLE_PASSWORD_CONFIRMATION) && !arguments.get(0).equals(arguments.get(1))) { + RegistrationArgumentType registrationType = commonService.getProperty(REGISTRATION_TYPE); + if (registrationType == PASSWORD_WITH_CONFIRMATION && !arguments.get(0).equals(arguments.get(1))) { commonService.send(player, MessageKey.PASSWORD_MATCH_ERROR); + } else if (registrationType == PASSWORD_WITH_EMAIL) { + handlePasswordWithEmailRegistration(player, arguments); } else { - management.performRegister(player, arguments.get(0), "", true); + management.performRegister(player, registrationExecutorProvider + .getPasswordRegisterExecutor(player, arguments.get(0))); + } + } + + private void handlePasswordWithEmailRegistration(Player player, List arguments) { + if (validationService.validateEmail(arguments.get(1))) { + management.performRegister(player, registrationExecutorProvider + .getPasswordRegisterExecutor(player, arguments.get(0), arguments.get(1))); + } else { + commonService.send(player, MessageKey.INVALID_EMAIL); } } @@ -84,22 +103,10 @@ public class RegisterCommand extends PlayerCommand { final String email = arguments.get(0); if (!validationService.validateEmail(email)) { commonService.send(player, MessageKey.INVALID_EMAIL); - } else if (commonService.getProperty(ENABLE_CONFIRM_EMAIL) && !email.equals(arguments.get(1))) { + } else if (commonService.getProperty(REGISTRATION_TYPE) == EMAIL_WITH_CONFIRMATION && !email.equals(arguments.get(1))) { commonService.send(player, MessageKey.USAGE_REGISTER); } else { - String thePass = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH)); - management.performRegister(player, thePass, email, true); + management.performRegister(player, registrationExecutorProvider.getEmailRegisterExecutor(player, email)); } } - - /** - * Return whether the password or email has to be confirmed. - * - * @return True if the confirmation is needed, false otherwise - */ - private boolean isConfirmationRequired() { - return commonService.getProperty(USE_EMAIL_REGISTRATION) - ? commonService.getProperty(ENABLE_CONFIRM_EMAIL) - : commonService.getProperty(ENABLE_PASSWORD_CONFIRMATION); - } } diff --git a/src/main/java/fr/xephi/authme/process/Management.java b/src/main/java/fr/xephi/authme/process/Management.java index be32e99e..8aea8341 100644 --- a/src/main/java/fr/xephi/authme/process/Management.java +++ b/src/main/java/fr/xephi/authme/process/Management.java @@ -8,6 +8,7 @@ import fr.xephi.authme.process.login.AsynchronousLogin; import fr.xephi.authme.process.logout.AsynchronousLogout; import fr.xephi.authme.process.quit.AsynchronousQuit; import fr.xephi.authme.process.register.AsyncRegister; +import fr.xephi.authme.process.register.executors.RegistrationExecutor; import fr.xephi.authme.process.unregister.AsynchronousUnregister; import fr.xephi.authme.service.BukkitService; import org.bukkit.command.CommandSender; @@ -59,8 +60,8 @@ public class Management { runTask(() -> asynchronousLogout.logout(player)); } - public void performRegister(Player player, String password, String email, boolean autoLogin) { - runTask(() -> asyncRegister.register(player, password, email, autoLogin)); + public void performRegister(Player player, RegistrationExecutor registrationExecutor) { + runTask(() -> asyncRegister.register(player, registrationExecutor)); } public void performUnregister(Player player, String password) { diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java index fb5c6c9c..e2735353 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java @@ -9,6 +9,7 @@ import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RegistrationArgumentType.Execution; import org.bukkit.entity.Player; import javax.inject.Inject; @@ -63,7 +64,7 @@ public class AsyncAddEmail implements AsynchronousProcess { private void sendUnloggedMessage(Player player) { if (dataSource.isAuthAvailable(player.getName())) { service.send(player, MessageKey.LOGIN_MESSAGE); - } else if (service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)) { + } else if (service.getProperty(RegistrationSettings.REGISTRATION_TYPE).getExecution() == Execution.EMAIL) { service.send(player, MessageKey.REGISTER_EMAIL_MESSAGE); } else { service.send(player, MessageKey.REGISTER_MESSAGE); diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java index d876ab58..e41f9b17 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java @@ -8,6 +8,7 @@ import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RegistrationArgumentType.Execution; import org.bukkit.entity.Player; import javax.inject.Inject; @@ -66,7 +67,7 @@ public class AsyncChangeEmail implements AsynchronousProcess { private void outputUnloggedMessage(Player player) { if (dataSource.isAuthAvailable(player.getName())) { service.send(player, MessageKey.LOGIN_MESSAGE); - } else if (service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)) { + } else if (service.getProperty(RegistrationSettings.REGISTRATION_TYPE).getExecution() == Execution.EMAIL) { service.send(player, MessageKey.REGISTER_EMAIL_MESSAGE); } else { service.send(player, MessageKey.REGISTER_MESSAGE); diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index 02ef0ce1..d32f6ead 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -3,28 +3,14 @@ package fr.xephi.authme.process.register; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.mail.SendMailSSL; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.AsynchronousProcess; +import fr.xephi.authme.process.register.executors.RegistrationExecutor; import fr.xephi.authme.service.CommonService; -import fr.xephi.authme.process.SyncProcessManager; -import fr.xephi.authme.process.login.AsynchronousLogin; -import fr.xephi.authme.security.HashAlgorithm; -import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.security.crypts.TwoFactor; -import fr.xephi.authme.settings.properties.EmailSettings; -import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.util.PlayerUtils; -import fr.xephi.authme.util.StringUtils; -import fr.xephi.authme.service.ValidationService; -import fr.xephi.authme.service.ValidationService.ValidationResult; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import javax.inject.Inject; @@ -37,38 +23,31 @@ import static fr.xephi.authme.permission.PlayerStatePermission.ALLOW_MULTIPLE_AC */ public class AsyncRegister implements AsynchronousProcess { - /** - * Number of ticks to wait before running the login action when it is run synchronously. - * A small delay is necessary or the database won't return the newly saved PlayerAuth object - * and the login process thinks the user is not registered. - */ - private static final int SYNC_LOGIN_DELAY = 5; - @Inject private DataSource database; @Inject private PlayerCache playerCache; @Inject - private PasswordSecurity passwordSecurity; - @Inject private CommonService service; @Inject - private SyncProcessManager syncProcessManager; - @Inject private PermissionsManager permissionsManager; - @Inject - private ValidationService validationService; - @Inject - private SendMailSSL sendMailSsl; - @Inject - private AsynchronousLogin asynchronousLogin; - @Inject - private BukkitService bukkitService; AsyncRegister() { } - private boolean preRegisterCheck(Player player, String password) { + /** + * Performs the registration process for the given player. + * + * @param player the player to register + * @param executor the registration executor to perform the registration with + */ + public void register(Player player, RegistrationExecutor executor) { + if (preRegisterCheck(player) && executor.isRegistrationAdmitted()) { + executeRegistration(player, executor); + } + } + + private boolean preRegisterCheck(Player player) { final String name = player.getName().toLowerCase(); if (playerCache.isAuthenticated(name)) { service.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); @@ -76,23 +55,36 @@ public class AsyncRegister implements AsynchronousProcess { } else if (!service.getProperty(RegistrationSettings.IS_ENABLED)) { service.send(player, MessageKey.REGISTRATION_DISABLED); return false; - } - - //check the password safety only if it's not a automatically generated password - if (service.getProperty(SecuritySettings.PASSWORD_HASH) != HashAlgorithm.TWO_FACTOR) { - ValidationResult passwordValidation = validationService.validatePassword(password, player.getName()); - if (passwordValidation.hasError()) { - service.send(player, passwordValidation.getMessageKey(), passwordValidation.getArgs()); - return false; - } - } - - //check this in both possibilities so don't use 'else if' - if (database.isAuthAvailable(name)) { + } else if (database.isAuthAvailable(name)) { service.send(player, MessageKey.NAME_ALREADY_REGISTERED); return false; } + return isPlayerIpAllowedToRegister(player); + } + + /** + * Executes the registration. + * + * @param player the player to register + * @param executor the executor to perform the registration process with + */ + private void executeRegistration(Player player, RegistrationExecutor executor) { + PlayerAuth auth = executor.buildPlayerAuth(); + if (database.saveAuth(auth)) { + executor.executePostPersistAction(); + } else { + service.send(player, MessageKey.ERROR); + } + } + + /** + * Checks whether the registration threshold has been exceeded for the given player's IP address. + * + * @param player the player to check + * @return true if registration may take place, false otherwise (IP check failed) + */ + private boolean isPlayerIpAllowedToRegister(Player player) { final int maxRegPerIp = service.getProperty(RestrictionSettings.MAX_REGISTRATION_PER_IP); final String ip = PlayerUtils.getPlayerIp(player); if (maxRegPerIp > 0 @@ -108,84 +100,4 @@ public class AsyncRegister implements AsynchronousProcess { } return true; } - - public void register(Player player, String password, String email, boolean autoLogin) { - if (preRegisterCheck(player, password)) { - if (!StringUtils.isEmpty(email)) { - emailRegister(player, password, email); - } else { - passwordRegister(player, password, autoLogin); - } - } - } - - private void emailRegister(Player player, String password, String email) { - final String name = player.getName().toLowerCase(); - final int maxRegPerEmail = service.getProperty(EmailSettings.MAX_REG_PER_EMAIL); - if (maxRegPerEmail > 0 && !permissionsManager.hasPermission(player, ALLOW_MULTIPLE_ACCOUNTS)) { - int otherAccounts = database.countAuthsByEmail(email); - if (otherAccounts >= maxRegPerEmail) { - service.send(player, MessageKey.MAX_REGISTER_EXCEEDED, Integer.toString(maxRegPerEmail), - Integer.toString(otherAccounts), "@"); - return; - } - } - - final HashedPassword hashedPassword = passwordSecurity.computeHash(password, name); - final String ip = PlayerUtils.getPlayerIp(player); - PlayerAuth auth = PlayerAuth.builder() - .name(name) - .realName(player.getName()) - .password(hashedPassword) - .ip(ip) - .location(player.getLocation()) - .email(email) - .build(); - - if (!database.saveAuth(auth)) { - service.send(player, MessageKey.ERROR); - return; - } - database.updateEmail(auth); - database.updateSession(auth); - boolean couldSendMail = sendMailSsl.sendPasswordMail(name, email, password); - if (couldSendMail) { - syncProcessManager.processSyncEmailRegister(player); - } else { - service.send(player, MessageKey.EMAIL_SEND_FAILURE); - } - } - - private void passwordRegister(final Player player, String password, boolean autoLogin) { - final String name = player.getName().toLowerCase(); - final String ip = PlayerUtils.getPlayerIp(player); - final HashedPassword hashedPassword = passwordSecurity.computeHash(password, name); - PlayerAuth auth = PlayerAuth.builder() - .name(name) - .realName(player.getName()) - .password(hashedPassword) - .ip(ip) - .location(player.getLocation()) - .build(); - - if (!database.saveAuth(auth)) { - service.send(player, MessageKey.ERROR); - return; - } - - if (!service.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER) && autoLogin) { - if (service.getProperty(PluginSettings.USE_ASYNC_TASKS)) { - bukkitService.runTaskAsynchronously(() -> asynchronousLogin.forceLogin(player)); - } else { - bukkitService.scheduleSyncDelayedTask(() -> asynchronousLogin.forceLogin(player), SYNC_LOGIN_DELAY); - } - } - syncProcessManager.processSyncPasswordRegister(player); - - //give the user the secret code to setup their app code generation - if (service.getProperty(SecuritySettings.PASSWORD_HASH) == HashAlgorithm.TWO_FACTOR) { - String qrCodeUrl = TwoFactor.getQRBarcodeURL(player.getName(), Bukkit.getIp(), hashedPassword.getHash()); - service.send(player, MessageKey.TWO_FACTOR_CREATE, hashedPassword.getHash(), qrCodeUrl); - } - } } diff --git a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java new file mode 100644 index 00000000..88f8cd60 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java @@ -0,0 +1,91 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.mail.SendMailSSL; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.process.SyncProcessManager; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.util.RandomStringUtils; +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth; +import static fr.xephi.authme.permission.PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS; +import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH; + +/** + * Provides a registration executor for email registration. + */ +class EmailRegisterExecutorProvider { + + @Inject + private PermissionsManager permissionsManager; + + @Inject + private DataSource dataSource; + + @Inject + private CommonService commonService; + + @Inject + private SendMailSSL sendMailSsl; + + @Inject + private SyncProcessManager syncProcessManager; + + @Inject + private PasswordSecurity passwordSecurity; + + EmailRegisterExecutorProvider() { + } + + /** Registration executor implementation for email registration. */ + class EmailRegisterExecutor implements RegistrationExecutor { + + private final Player player; + private final String email; + private String password; + + EmailRegisterExecutor(Player player, String email) { + this.player = player; + this.email = email; + } + + @Override + public boolean isRegistrationAdmitted() { + final int maxRegPerEmail = commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL); + if (maxRegPerEmail > 0 && !permissionsManager.hasPermission(player, ALLOW_MULTIPLE_ACCOUNTS)) { + int otherAccounts = dataSource.countAuthsByEmail(email); + if (otherAccounts >= maxRegPerEmail) { + commonService.send(player, MessageKey.MAX_REGISTER_EXCEEDED, Integer.toString(maxRegPerEmail), + Integer.toString(otherAccounts), "@"); + return false; + } + } + return true; + } + + @Override + public PlayerAuth buildPlayerAuth() { + password = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH)); + HashedPassword hashedPassword = passwordSecurity.computeHash(password, player.getName()); + return createPlayerAuth(player, hashedPassword, email); + } + + @Override + public void executePostPersistAction() { + boolean couldSendMail = sendMailSsl.sendPasswordMail(player.getName(), email, password); + if (couldSendMail) { + syncProcessManager.processSyncEmailRegister(player); + } else { + commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); + } + } + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java new file mode 100644 index 00000000..a79c63c8 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java @@ -0,0 +1,155 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.process.SyncProcessManager; +import fr.xephi.authme.process.login.AsynchronousLogin; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.security.crypts.TwoFactor; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.ValidationService; +import fr.xephi.authme.service.ValidationService.ValidationResult; +import fr.xephi.authme.settings.properties.PluginSettings; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth; + +/** + * Provides registration executors for password-based registration variants. + */ +class PasswordRegisterExecutorProvider { + + /** + * Number of ticks to wait before running the login action when it is run synchronously. + * A small delay is necessary or the database won't return the newly saved PlayerAuth object + * and the login process thinks the user is not registered. + */ + private static final int SYNC_LOGIN_DELAY = 5; + + @Inject + private ValidationService validationService; + + @Inject + private CommonService commonService; + + @Inject + private PasswordSecurity passwordSecurity; + + @Inject + private BukkitService bukkitService; + + @Inject + private SyncProcessManager syncProcessManager; + + @Inject + private AsynchronousLogin asynchronousLogin; + + PasswordRegisterExecutorProvider() { + } + + /** Registration executor for password registration. */ + class PasswordRegisterExecutor implements RegistrationExecutor { + + protected final Player player; + private final String password; + private final String email; + protected HashedPassword hashedPassword; + + /** + * Constructor. + * + * @param player the player to register + * @param password the password to register with + * @param email the email of the player (may be null) + */ + PasswordRegisterExecutor(Player player, String password, String email) { + this.player = player; + this.password = password; + this.email = email; + } + + @Override + public boolean isRegistrationAdmitted() { + ValidationResult passwordValidation = validationService.validatePassword(password, player.getName()); + if (passwordValidation.hasError()) { + commonService.send(player, passwordValidation.getMessageKey(), passwordValidation.getArgs()); + return false; + } + return true; + } + + @Override + public PlayerAuth buildPlayerAuth() { + hashedPassword = passwordSecurity.computeHash(password, player.getName().toLowerCase()); + return createPlayerAuth(player, hashedPassword, email); + } + + protected boolean performLoginAfterRegister() { + return !commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER); + } + + @Override + public void executePostPersistAction() { + if (performLoginAfterRegister()) { + if (commonService.getProperty(PluginSettings.USE_ASYNC_TASKS)) { + bukkitService.runTaskAsynchronously(() -> asynchronousLogin.forceLogin(player)); + } else { + bukkitService.scheduleSyncDelayedTask(() -> asynchronousLogin.forceLogin(player), SYNC_LOGIN_DELAY); + } + } + syncProcessManager.processSyncPasswordRegister(player); + } + } + + /** Executor for password registration via API call. */ + class ApiPasswordRegisterExecutor extends PasswordRegisterExecutor { + + private final boolean loginAfterRegister; + + /** + * Constructor. + * + * @param player the player to register + * @param password the password to register with + * @param loginAfterRegister whether the user should be automatically logged in after registration + */ + ApiPasswordRegisterExecutor(Player player, String password, boolean loginAfterRegister) { + super(player, password, null); + this.loginAfterRegister = loginAfterRegister; + } + + @Override + protected boolean performLoginAfterRegister() { + return loginAfterRegister; + } + } + + /** Executor for two factor registration. */ + class TwoFactorRegisterExecutor extends PasswordRegisterExecutor { + + TwoFactorRegisterExecutor(Player player) { + super(player, "", null); + } + + @Override + public boolean isRegistrationAdmitted() { + // nothing to check + return true; + } + + @Override + public void executePostPersistAction() { + super.executePostPersistAction(); + + String qrCodeUrl = TwoFactor.getQRBarcodeURL(player.getName(), Bukkit.getIp(), hashedPassword.getHash()); + commonService.send(player, MessageKey.TWO_FACTOR_CREATE, hashedPassword.getHash(), qrCodeUrl); + } + + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java b/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java new file mode 100644 index 00000000..1943b5d5 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java @@ -0,0 +1,26 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.util.PlayerUtils; +import org.bukkit.entity.Player; + +/** + * Helper for constructing PlayerAuth objects. + */ +final class PlayerAuthBuilderHelper { + + private PlayerAuthBuilderHelper() { + } + + static PlayerAuth createPlayerAuth(Player player, HashedPassword hashedPassword, String email) { + return PlayerAuth.builder() + .name(player.getName().toLowerCase()) + .realName(player.getName()) + .password(hashedPassword) + .email(email) + .ip(PlayerUtils.getPlayerIp(player)) + .location(player.getLocation()) + .build(); + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java new file mode 100644 index 00000000..cceb5c18 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java @@ -0,0 +1,33 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; + +/** + * Performs the registration action. + */ +public interface RegistrationExecutor { + + /** + * Returns whether the registration may take place. Use this method to execute + * checks specific to the registration method. + *

+ * If this method returns {@code false}, it is expected that the executor inform + * the player about the error within this method call. + * + * @return true if registration may be performed, false otherwise + */ + boolean isRegistrationAdmitted(); + + /** + * Constructs the PlayerAuth object to persist into the database. + * + * @return the player auth to register in the data source + */ + PlayerAuth buildPlayerAuth(); + + /** + * Follow-up method called after the player auth could be added into the database. + */ + void executePostPersistAction(); + +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java new file mode 100644 index 00000000..04adce31 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java @@ -0,0 +1,40 @@ +package fr.xephi.authme.process.register.executors; + +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +/** + * Provides a {@link RegistrationExecutor} for various registration methods. + */ +public class RegistrationExecutorProvider { + + @Inject + private PasswordRegisterExecutorProvider passwordRegisterExecutorProvider; + + @Inject + private EmailRegisterExecutorProvider emailRegisterExecutorProvider; + + RegistrationExecutorProvider() { + } + + public RegistrationExecutor getPasswordRegisterExecutor(Player player, String password) { + return getPasswordRegisterExecutor(player, password, null); + } + + public RegistrationExecutor getPasswordRegisterExecutor(Player player, String password, String email) { + return passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, password, email); + } + + public RegistrationExecutor getPasswordRegisterExecutor(Player player, String password, boolean loginAfterRegister) { + return passwordRegisterExecutorProvider.new ApiPasswordRegisterExecutor(player, password, loginAfterRegister); + } + + public RegistrationExecutor getTwoFactorRegisterExecutor(Player player) { + return passwordRegisterExecutorProvider.new TwoFactorRegisterExecutor(player); + } + + public RegistrationExecutor getEmailRegisterExecutor(Player player, String email) { + return emailRegisterExecutorProvider.new EmailRegisterExecutor(player, email); + } +} diff --git a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java index 52932770..2642644f 100644 --- a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java +++ b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java @@ -9,6 +9,8 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.output.LogLevel; import fr.xephi.authme.settings.properties.PluginSettings; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RegistrationArgumentType; import fr.xephi.authme.settings.properties.SecuritySettings; import javax.inject.Inject; @@ -67,6 +69,7 @@ public class SettingsMigrationService extends PlainMigrationService { | changeBooleanSettingToLogLevelProperty(resource) | hasOldHelpHeaderProperty(resource) | hasSupportOldPasswordProperty(resource) + | convertToRegistrationType(resource) || hasDeprecatedProperties(resource); } @@ -224,6 +227,34 @@ public class SettingsMigrationService extends PlainMigrationService { return false; } + private static boolean convertToRegistrationType(PropertyResource resource) { + if (RegistrationSettings.REGISTRATION_TYPE.isPresent(resource)) { + return false; + } + + boolean useEmail = newProperty("settings.registration.enableEmailRegistrationSystem", false).getValue(resource); + String useConfirmationPath = useEmail + ? "settings.registration.doubleEmailCheck" + : "settings.restrictions.enablePasswordConfirmation"; + boolean hasConfirmation = newProperty(useConfirmationPath, false).getValue(resource); + + RegistrationArgumentType registerType; + if (useEmail) { + registerType = hasConfirmation + ? RegistrationArgumentType.EMAIL_WITH_CONFIRMATION + : RegistrationArgumentType.EMAIL; + } else { + registerType = hasConfirmation + ? RegistrationArgumentType.PASSWORD_WITH_CONFIRMATION + : RegistrationArgumentType.PASSWORD; + } + + ConsoleLogger.warning("Merging old registration settings into '" + + RegistrationSettings.REGISTRATION_TYPE.getPath() + "'"); + resource.setValue(RegistrationSettings.REGISTRATION_TYPE.getPath(), registerType); + return true; + } + /** * Checks for an old property path and moves it to a new path if present. * diff --git a/src/main/java/fr/xephi/authme/settings/properties/RegistrationArgumentType.java b/src/main/java/fr/xephi/authme/settings/properties/RegistrationArgumentType.java new file mode 100644 index 00000000..b0690508 --- /dev/null +++ b/src/main/java/fr/xephi/authme/settings/properties/RegistrationArgumentType.java @@ -0,0 +1,60 @@ +package fr.xephi.authme.settings.properties; + +/** + * Type of arguments used for the login command. + */ +public enum RegistrationArgumentType { + + /** /register [password] */ + PASSWORD(Execution.PASSWORD, 1), + + /** /register [password] [password] */ + PASSWORD_WITH_CONFIRMATION(Execution.PASSWORD, 2), + + /** /register [email] */ + EMAIL(Execution.EMAIL, 1), + + /** /register [email] [email] */ + EMAIL_WITH_CONFIRMATION(Execution.EMAIL, 2), + + /** /register [password] [email] */ + PASSWORD_WITH_EMAIL(Execution.PASSWORD, 2); + + private final Execution execution; + private final int requiredNumberOfArgs; + + /** + * Constructor. + * + * @param execution the registration process + * @param requiredNumberOfArgs the required number of arguments + */ + RegistrationArgumentType(Execution execution, int requiredNumberOfArgs) { + this.execution = execution; + this.requiredNumberOfArgs = requiredNumberOfArgs; + } + + /** + * @return the registration execution that is used for this argument type + */ + public Execution getExecution() { + return execution; + } + + /** + * @return number of arguments required to process the register command + */ + public int getRequiredNumberOfArgs() { + return requiredNumberOfArgs; + } + + /** + * Registration execution (the type of registration). + */ + public enum Execution { + + PASSWORD, + EMAIL + + } +} diff --git a/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java index a3de56b8..204c5a19 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java @@ -24,16 +24,12 @@ public class RegistrationSettings implements SettingsHolder { public static final Property FORCE = newProperty("settings.registration.force", true); - @Comment("Do we replace password registration by an email registration method?") - public static final Property USE_EMAIL_REGISTRATION = - newProperty("settings.registration.enableEmailRegistrationSystem", false); - @Comment({ - "Enable double check of email when you register", - "when it's true, registration requires that kind of command:", - "/register "}) - public static final Property ENABLE_CONFIRM_EMAIL = - newProperty("settings.registration.doubleEmailCheck", false); + "Type of registration: PASSWORD, PASSWORD_WITH_CONFIRMATION, EMAIL", + "EMAIL_WITH_CONFIRMATION, PASSWORD_WITH_EMAIL" + }) + public static final Property REGISTRATION_TYPE = + newProperty(RegistrationArgumentType.class, "settings.registration.type", RegistrationArgumentType.PASSWORD_WITH_CONFIRMATION); @Comment({ "Do we force kick a player after a successful registration?", diff --git a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java index c62b93e3..e5d83e47 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java @@ -126,13 +126,6 @@ public class RestrictionSettings implements SettingsHolder { public static final Property ALLOWED_MOVEMENT_RADIUS = newProperty("settings.restrictions.allowedMovementRadius", 100); - @Comment({ - "Enable double check of password when you register", - "when it's true, registration requires that kind of command:", - "/register "}) - public static final Property ENABLE_PASSWORD_CONFIRMATION = - newProperty("settings.restrictions.enablePasswordConfirmation", true); - @Comment("Should we protect the player inventory before logging in? Requires ProtocolLib.") public static final Property PROTECT_INVENTORY_BEFORE_LOGIN = newProperty("settings.restrictions.ProtectInventoryBeforeLogIn", true); diff --git a/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java b/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java index 6eceaae5..43749bbc 100644 --- a/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java +++ b/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java @@ -6,10 +6,11 @@ import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; +import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RegistrationArgumentType.Execution; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.service.BukkitService; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; @@ -95,7 +96,7 @@ public class LimboPlayerTaskManager { if (isRegistered) { return MessageKey.LOGIN_MESSAGE; } else { - return settings.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION) + return settings.getProperty(RegistrationSettings.REGISTRATION_TYPE).getExecution() == Execution.EMAIL ? MessageKey.REGISTER_EMAIL_MESSAGE : MessageKey.REGISTER_MESSAGE; } diff --git a/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java index c63c8c96..d896c4fb 100644 --- a/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java @@ -4,12 +4,13 @@ import fr.xephi.authme.TestHelper; import fr.xephi.authme.mail.SendMailSSL; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; +import fr.xephi.authme.process.register.executors.RegistrationExecutor; +import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.ValidationService; -import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.settings.properties.RegistrationArgumentType; import fr.xephi.authme.settings.properties.RegistrationSettings; -import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; @@ -25,9 +26,7 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.Arrays; import java.util.Collections; -import static fr.xephi.authme.AuthMeMatchers.stringWithLength; import static org.hamcrest.Matchers.containsString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -44,7 +43,7 @@ public class RegisterCommandTest { private RegisterCommand command; @Mock - private CommonService commandService; + private CommonService commonService; @Mock private Management management; @@ -55,6 +54,9 @@ public class RegisterCommandTest { @Mock private ValidationService validationService; + @Mock + private RegistrationExecutorProvider registrationExecutorProvider; + @BeforeClass public static void setup() { TestHelper.setupLogger(); @@ -62,9 +64,8 @@ public class RegisterCommandTest { @Before public void linkMocksAndProvideSettingDefaults() { - given(commandService.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.BCRYPT); - given(commandService.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(false); - given(commandService.getProperty(RestrictionSettings.ENABLE_PASSWORD_CONFIRMATION)).willReturn(false); + given(commonService.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.BCRYPT); + given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.PASSWORD); } @Test @@ -83,14 +84,16 @@ public class RegisterCommandTest { @Test public void shouldForwardToManagementForTwoFactor() { // given - given(commandService.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.TWO_FACTOR); + given(commonService.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.TWO_FACTOR); Player player = mock(Player.class); + RegistrationExecutor executor = mock(RegistrationExecutor.class); + given(registrationExecutorProvider.getTwoFactorRegisterExecutor(player)).willReturn(executor); // when command.executeCommand(player, Collections.emptyList()); // then - verify(management).performRegister(player, "", "", true); + verify(management).performRegister(player, executor); verifyZeroInteractions(sendMailSsl); } @@ -103,44 +106,42 @@ public class RegisterCommandTest { command.executeCommand(player, Collections.emptyList()); // then - verify(commandService).send(player, MessageKey.USAGE_REGISTER); + verify(commonService).send(player, MessageKey.USAGE_REGISTER); verifyZeroInteractions(management, sendMailSsl); } @Test public void shouldReturnErrorForMissingConfirmation() { // given - given(commandService.getProperty(RestrictionSettings.ENABLE_PASSWORD_CONFIRMATION)).willReturn(true); + given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.PASSWORD_WITH_CONFIRMATION); Player player = mock(Player.class); // when command.executeCommand(player, Collections.singletonList("arrrr")); // then - verify(commandService).send(player, MessageKey.USAGE_REGISTER); + verify(commonService).send(player, MessageKey.USAGE_REGISTER); verifyZeroInteractions(management, sendMailSsl); } @Test public void shouldReturnErrorForMissingEmailConfirmation() { // given - given(commandService.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); - given(commandService.getProperty(RegistrationSettings.ENABLE_CONFIRM_EMAIL)).willReturn(true); + given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.EMAIL_WITH_CONFIRMATION); Player player = mock(Player.class); // when command.executeCommand(player, Collections.singletonList("test@example.org")); // then - verify(commandService).send(player, MessageKey.USAGE_REGISTER); + verify(commonService).send(player, MessageKey.USAGE_REGISTER); verifyZeroInteractions(management, sendMailSsl); } @Test public void shouldThrowErrorForMissingEmailConfiguration() { // given - given(commandService.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); - given(commandService.getProperty(RegistrationSettings.ENABLE_CONFIRM_EMAIL)).willReturn(false); + given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.EMAIL); given(sendMailSsl.hasAllInformation()).willReturn(false); Player player = mock(Player.class); @@ -148,7 +149,7 @@ public class RegisterCommandTest { command.executeCommand(player, Collections.singletonList("myMail@example.tld")); // then - verify(commandService).send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS); + verify(commonService).send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS); verify(sendMailSsl).hasAllInformation(); verifyZeroInteractions(management); } @@ -158,9 +159,7 @@ public class RegisterCommandTest { // given String playerMail = "player@example.org"; given(validationService.validateEmail(playerMail)).willReturn(false); - - given(commandService.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); - given(commandService.getProperty(RegistrationSettings.ENABLE_CONFIRM_EMAIL)).willReturn(true); + given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.EMAIL_WITH_CONFIRMATION); given(sendMailSsl.hasAllInformation()).willReturn(true); Player player = mock(Player.class); @@ -169,7 +168,7 @@ public class RegisterCommandTest { // then verify(validationService).validateEmail(playerMail); - verify(commandService).send(player, MessageKey.INVALID_EMAIL); + verify(commonService).send(player, MessageKey.INVALID_EMAIL); verifyZeroInteractions(management); } @@ -178,9 +177,7 @@ public class RegisterCommandTest { // given String playerMail = "bobber@bobby.org"; given(validationService.validateEmail(playerMail)).willReturn(true); - - given(commandService.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); - given(commandService.getProperty(RegistrationSettings.ENABLE_CONFIRM_EMAIL)).willReturn(true); + given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.EMAIL_WITH_CONFIRMATION); given(sendMailSsl.hasAllInformation()).willReturn(true); Player player = mock(Player.class); @@ -188,7 +185,7 @@ public class RegisterCommandTest { command.executeCommand(player, Arrays.asList(playerMail, "invalid")); // then - verify(commandService).send(player, MessageKey.USAGE_REGISTER); + verify(commonService).send(player, MessageKey.USAGE_REGISTER); verify(sendMailSsl).hasAllInformation(); verifyZeroInteractions(management); } @@ -198,13 +195,11 @@ public class RegisterCommandTest { // given String playerMail = "asfd@lakjgre.lds"; given(validationService.validateEmail(playerMail)).willReturn(true); - int passLength = 7; - given(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(passLength); - - given(commandService.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); - given(commandService.getProperty(RegistrationSettings.ENABLE_CONFIRM_EMAIL)).willReturn(true); + given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.EMAIL_WITH_CONFIRMATION); given(sendMailSsl.hasAllInformation()).willReturn(true); Player player = mock(Player.class); + RegistrationExecutor executor = mock(RegistrationExecutor.class); + given(registrationExecutorProvider.getEmailRegisterExecutor(player, playerMail)).willReturn(executor); // when command.executeCommand(player, Arrays.asList(playerMail, playerMail)); @@ -212,20 +207,20 @@ public class RegisterCommandTest { // then verify(validationService).validateEmail(playerMail); verify(sendMailSsl).hasAllInformation(); - verify(management).performRegister(eq(player), argThat(stringWithLength(passLength)), eq(playerMail), eq(true)); + verify(management).performRegister(player, executor); } @Test public void shouldRejectInvalidPasswordConfirmation() { // given - given(commandService.getProperty(RestrictionSettings.ENABLE_PASSWORD_CONFIRMATION)).willReturn(true); + given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.PASSWORD_WITH_CONFIRMATION); Player player = mock(Player.class); // when command.executeCommand(player, Arrays.asList("myPass", "mypass")); // then - verify(commandService).send(player, MessageKey.PASSWORD_MATCH_ERROR); + verify(commonService).send(player, MessageKey.PASSWORD_MATCH_ERROR); verifyZeroInteractions(management, sendMailSsl); } @@ -233,11 +228,65 @@ public class RegisterCommandTest { public void shouldPerformPasswordValidation() { // given Player player = mock(Player.class); + RegistrationExecutor executor = mock(RegistrationExecutor.class); + given(registrationExecutorProvider.getPasswordRegisterExecutor(player, "myPass")).willReturn(executor); // when command.executeCommand(player, Collections.singletonList("myPass")); // then - verify(management).performRegister(player, "myPass", "", true); + verify(management).performRegister(player, executor); + } + + @Test + public void shouldPerformMailValidationForPasswordWithEmail() { + // given + given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)) + .willReturn(RegistrationArgumentType.PASSWORD_WITH_EMAIL); + String email = "email@example.org"; + given(validationService.validateEmail(email)).willReturn(true); + Player player = mock(Player.class); + RegistrationExecutor executor = mock(RegistrationExecutor.class); + given(registrationExecutorProvider.getPasswordRegisterExecutor(player, "myPass", email)).willReturn(executor); + + // when + command.executeCommand(player, Arrays.asList("myPass", email)); + + // then + verify(validationService).validateEmail(email); + verify(management).performRegister(player, executor); + } + + @Test + public void shouldStopForInvalidEmail() { + // given + given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)) + .willReturn(RegistrationArgumentType.PASSWORD_WITH_EMAIL); + String email = "email@example.org"; + given(validationService.validateEmail(email)).willReturn(false); + Player player = mock(Player.class); + + // when + command.executeCommand(player, Arrays.asList("myPass", email)); + + // then + verify(validationService).validateEmail(email); + verify(commonService).send(player, MessageKey.INVALID_EMAIL); + verifyZeroInteractions(management); + } + + @Test + public void shouldFailForInsufficientArguments() { + // given + given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)) + .willReturn(RegistrationArgumentType.PASSWORD_WITH_EMAIL); + Player player = mock(Player.class); + + // when + command.executeCommand(player, Collections.singletonList("myPass")); + + // then + verify(commonService).send(player, MessageKey.USAGE_REGISTER); + verifyZeroInteractions(management); } } diff --git a/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java b/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java index c8c82962..36c227d3 100644 --- a/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java +++ b/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java @@ -8,6 +8,7 @@ import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RegistrationArgumentType; import org.bukkit.entity.Player; import org.junit.BeforeClass; import org.junit.Test; @@ -172,7 +173,7 @@ public class AsyncAddEmailTest { given(player.getName()).willReturn("user"); given(playerCache.isAuthenticated("user")).willReturn(false); given(dataSource.isAuthAvailable("user")).willReturn(false); - given(service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); + given(service.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.EMAIL); // when asyncAddEmail.addEmail(player, "test@mail.com"); @@ -188,7 +189,7 @@ public class AsyncAddEmailTest { given(player.getName()).willReturn("user"); given(playerCache.isAuthenticated("user")).willReturn(false); given(dataSource.isAuthAvailable("user")).willReturn(false); - given(service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(false); + given(service.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.PASSWORD_WITH_CONFIRMATION); // when asyncAddEmail.addEmail(player, "test@mail.com"); diff --git a/src/test/java/fr/xephi/authme/process/email/AsyncChangeEmailTest.java b/src/test/java/fr/xephi/authme/process/email/AsyncChangeEmailTest.java index 145de51c..c002fec7 100644 --- a/src/test/java/fr/xephi/authme/process/email/AsyncChangeEmailTest.java +++ b/src/test/java/fr/xephi/authme/process/email/AsyncChangeEmailTest.java @@ -7,6 +7,7 @@ import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RegistrationArgumentType; import org.bukkit.entity.Player; import org.junit.Test; import org.junit.runner.RunWith; @@ -184,7 +185,7 @@ public class AsyncChangeEmailTest { given(player.getName()).willReturn("Bobby"); given(playerCache.isAuthenticated("bobby")).willReturn(false); given(dataSource.isAuthAvailable("Bobby")).willReturn(false); - given(service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); + given(service.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.EMAIL_WITH_CONFIRMATION); // when process.changeEmail(player, "old@mail.tld", "new@mail.tld"); @@ -201,7 +202,7 @@ public class AsyncChangeEmailTest { given(player.getName()).willReturn("Bobby"); given(playerCache.isAuthenticated("bobby")).willReturn(false); given(dataSource.isAuthAvailable("Bobby")).willReturn(false); - given(service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(false); + given(service.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.PASSWORD); // when process.changeEmail(player, "old@mail.tld", "new@mail.tld"); diff --git a/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java b/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java new file mode 100644 index 00000000..b845b8d5 --- /dev/null +++ b/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java @@ -0,0 +1,121 @@ +package fr.xephi.authme.process.register; + +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.auth.PlayerCache; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.process.register.executors.RegistrationExecutor; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import org.bukkit.entity.Player; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link AsyncRegister}. + */ +@RunWith(MockitoJUnitRunner.class) +public class AsyncRegisterTest { + + @InjectMocks + private AsyncRegister asyncRegister; + + @Mock + private PlayerCache playerCache; + @Mock + private PermissionsManager permissionsManager; + @Mock + private CommonService commonService; + @Mock + private DataSource dataSource; + + @Test + public void shouldDetectAlreadyLoggedInPlayer() { + // given + String name = "robert"; + Player player = mockPlayerWithName(name); + given(playerCache.isAuthenticated(name)).willReturn(true); + RegistrationExecutor executor = mock(RegistrationExecutor.class); + + // when + asyncRegister.register(player, executor); + + // then + verify(commonService).send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); + verifyZeroInteractions(dataSource, executor); + } + + @Test + public void shouldStopForDisabledRegistration() { + // given + String name = "albert"; + Player player = mockPlayerWithName(name); + given(playerCache.isAuthenticated(name)).willReturn(false); + given(commonService.getProperty(RegistrationSettings.IS_ENABLED)).willReturn(false); + RegistrationExecutor executor = mock(RegistrationExecutor.class); + + // when + asyncRegister.register(player, executor); + + // then + verify(commonService).send(player, MessageKey.REGISTRATION_DISABLED); + verifyZeroInteractions(dataSource, executor); + } + + @Test + public void shouldStopForAlreadyRegisteredName() { + // given + String name = "dilbert"; + Player player = mockPlayerWithName(name); + given(playerCache.isAuthenticated(name)).willReturn(false); + given(commonService.getProperty(RegistrationSettings.IS_ENABLED)).willReturn(true); + given(dataSource.isAuthAvailable(name)).willReturn(true); + RegistrationExecutor executor = mock(RegistrationExecutor.class); + + // when + asyncRegister.register(player, executor); + + // then + verify(commonService).send(player, MessageKey.NAME_ALREADY_REGISTERED); + verify(dataSource, only()).isAuthAvailable(name); + verifyZeroInteractions(executor); + } + + @Test + public void shouldStopForFailedExecutorCheck() { + // given + String name = "edbert"; + Player player = mockPlayerWithName(name); + TestHelper.mockPlayerIp(player, "33.44.55.66"); + given(playerCache.isAuthenticated(name)).willReturn(false); + given(commonService.getProperty(RegistrationSettings.IS_ENABLED)).willReturn(true); + given(commonService.getProperty(RestrictionSettings.MAX_REGISTRATION_PER_IP)).willReturn(0); + given(dataSource.isAuthAvailable(name)).willReturn(false); + RegistrationExecutor executor = mock(RegistrationExecutor.class); + given(executor.isRegistrationAdmitted()).willReturn(false); + + // when + asyncRegister.register(player, executor); + + // then + verify(dataSource, only()).isAuthAvailable(name); + verify(executor, only()).isRegistrationAdmitted(); + } + + private static Player mockPlayerWithName(String name) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + return player; + } +} diff --git a/src/test/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProviderTest.java b/src/test/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProviderTest.java new file mode 100644 index 00000000..aecc158a --- /dev/null +++ b/src/test/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProviderTest.java @@ -0,0 +1,173 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.ReflectionTestUtils; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.mail.SendMailSSL; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.permission.PlayerStatePermission; +import fr.xephi.authme.process.SyncProcessManager; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.settings.properties.EmailSettings; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static fr.xephi.authme.AuthMeMatchers.hasAuthBasicData; +import static fr.xephi.authme.AuthMeMatchers.hasAuthLocation; +import static fr.xephi.authme.AuthMeMatchers.stringWithLength; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link EmailRegisterExecutorProvider}. + */ +@RunWith(MockitoJUnitRunner.class) +public class EmailRegisterExecutorProviderTest { + + @InjectMocks + private EmailRegisterExecutorProvider emailRegisterExecutorProvider; + + @Mock + private PermissionsManager permissionsManager; + @Mock + private DataSource dataSource; + @Mock + private CommonService commonService; + @Mock + private SendMailSSL sendMailSsl; + @Mock + private SyncProcessManager syncProcessManager; + @Mock + private PasswordSecurity passwordSecurity; + + @Test + public void shouldNotPassEmailValidation() { + // given + given(commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL)).willReturn(3); + String email = "test@example.com"; + given(dataSource.countAuthsByEmail(email)).willReturn(4); + Player player = mock(Player.class); + RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, email); + + // when + boolean result = executor.isRegistrationAdmitted(); + + // then + assertThat(result, equalTo(false)); + verify(dataSource).countAuthsByEmail(email); + verify(permissionsManager).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); + verify(commonService).send(player, MessageKey.MAX_REGISTER_EXCEEDED, "3", "4", "@"); + } + + @Test + public void shouldPassVerificationForPlayerWithPermission() { + // given + given(commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL)).willReturn(3); + Player player = mock(Player.class); + given(permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(true); + RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + + // when + boolean result = executor.isRegistrationAdmitted(); + + // then + assertThat(result, equalTo(true)); + verify(permissionsManager).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); + } + + @Test + public void shouldPassVerificationForPreviouslyUnregisteredIp() { + // given + given(commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL)).willReturn(1); + String email = "test@example.com"; + given(dataSource.countAuthsByEmail(email)).willReturn(0); + Player player = mock(Player.class); + RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + + // when + boolean result = executor.isRegistrationAdmitted(); + + // then + assertThat(result, equalTo(true)); + verify(permissionsManager).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); + verify(dataSource).countAuthsByEmail(email); + } + + @Test + public void shouldCreatePlayerAuth() { + // given + given(commonService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(12); + given(passwordSecurity.computeHash(anyString(), anyString())).willAnswer( + invocation -> new HashedPassword(invocation.getArgument(0))); + Player player = mock(Player.class); + TestHelper.mockPlayerIp(player, "123.45.67.89"); + given(player.getName()).willReturn("Veronica"); + World world = mock(World.class); + given(world.getName()).willReturn("someWorld"); + given(player.getLocation()).willReturn(new Location(world, 48, 96, 144)); + RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + + // when + PlayerAuth auth = executor.buildPlayerAuth(); + + // then + assertThat(auth, hasAuthBasicData("veronica", "Veronica", "test@example.com", "123.45.67.89")); + assertThat(auth, hasAuthLocation(48, 96, 144, "someWorld")); + assertThat(auth.getPassword().getHash(), stringWithLength(12)); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldPerformActionAfterDataSourceSave() { + // given + given(sendMailSsl.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(true); + Player player = mock(Player.class); + given(player.getName()).willReturn("Laleh"); + RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + String password = "A892C#@"; + ReflectionTestUtils.setField((Class) executor.getClass(), executor, "password", password); + + // when + executor.executePostPersistAction(); + + // then + verify(sendMailSsl).sendPasswordMail("Laleh", "test@example.com", password); + verify(syncProcessManager).processSyncEmailRegister(player); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldHandleEmailSendingFailure() { + // given + given(sendMailSsl.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(false); + Player player = mock(Player.class); + given(player.getName()).willReturn("Laleh"); + RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + String password = "A892C#@"; + ReflectionTestUtils.setField((Class) executor.getClass(), executor, "password", password); + + // when + executor.executePostPersistAction(); + + // then + verify(sendMailSsl).sendPasswordMail("Laleh", "test@example.com", password); + verify(commonService).send(player, MessageKey.EMAIL_SEND_FAILURE); + verifyZeroInteractions(syncProcessManager); + } + +} diff --git a/src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProviderTest.java b/src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProviderTest.java new file mode 100644 index 00000000..a033975f --- /dev/null +++ b/src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProviderTest.java @@ -0,0 +1,152 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.process.SyncProcessManager; +import fr.xephi.authme.process.login.AsynchronousLogin; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.ValidationService; +import fr.xephi.authme.service.ValidationService.ValidationResult; +import fr.xephi.authme.settings.properties.PluginSettings; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static fr.xephi.authme.AuthMeMatchers.equalToHash; +import static fr.xephi.authme.AuthMeMatchers.hasAuthBasicData; +import static fr.xephi.authme.AuthMeMatchers.hasAuthLocation; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link PasswordRegisterExecutorProvider}. + */ +@RunWith(MockitoJUnitRunner.class) +public class PasswordRegisterExecutorProviderTest { + + @InjectMocks + private PasswordRegisterExecutorProvider passwordRegisterExecutorProvider; + + @Mock + private ValidationService validationService; + @Mock + private CommonService commonService; + @Mock + private PasswordSecurity passwordSecurity; + @Mock + private BukkitService bukkitService; + @Mock + private SyncProcessManager syncProcessManager; + @Mock + private AsynchronousLogin asynchronousLogin; + + @Test + public void shouldCheckPasswordValidity() { + // given + String password = "myPass"; + String name = "player040"; + given(validationService.validatePassword(password, name)).willReturn(new ValidationResult()); + Player player = mockPlayerWithName(name); + RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, password, null); + + // when + boolean result = executor.isRegistrationAdmitted(); + + // then + assertThat(result, equalTo(true)); + verify(validationService).validatePassword(password, name); + } + + @Test + public void shouldDetectInvalidPasswordAndInformPlayer() { + // given + String password = "myPass"; + String name = "player040"; + given(validationService.validatePassword(password, name)).willReturn( + new ValidationResult(MessageKey.PASSWORD_CHARACTERS_ERROR, "[a-z]")); + Player player = mockPlayerWithName(name); + RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, password, null); + + // when + boolean result = executor.isRegistrationAdmitted(); + + // then + assertThat(result, equalTo(false)); + verify(validationService).validatePassword(password, name); + verify(commonService).send(player, MessageKey.PASSWORD_CHARACTERS_ERROR, "[a-z]"); + } + + @Test + public void shouldCreatePlayerAuth() { + // given + given(passwordSecurity.computeHash(anyString(), anyString())).willAnswer( + invocation -> new HashedPassword(invocation.getArgument(0))); + Player player = mockPlayerWithName("S1m0N"); + TestHelper.mockPlayerIp(player, "123.45.67.89"); + World world = mock(World.class); + given(world.getName()).willReturn("someWorld"); + given(player.getLocation()).willReturn(new Location(world, 48, 96, 144)); + RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, "pass", "mail@example.org"); + + // when + PlayerAuth auth = executor.buildPlayerAuth(); + + // then + assertThat(auth, hasAuthBasicData("s1m0n", "S1m0N", "mail@example.org", "123.45.67.89")); + assertThat(auth, hasAuthLocation(48, 96, 144, "someWorld")); + assertThat(auth.getPassword(), equalToHash("pass")); + } + + @Test + public void shouldLogPlayerIn() { + // given + given(commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER)).willReturn(false); + given(commonService.getProperty(PluginSettings.USE_ASYNC_TASKS)).willReturn(false); + Player player = mock(Player.class); + RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, "pass", "mail@example.org"); + + // when + executor.executePostPersistAction(); + + // then + TestHelper.runSyncDelayedTaskWithDelay(bukkitService); + verify(asynchronousLogin).forceLogin(player); + verify(syncProcessManager).processSyncPasswordRegister(player); + } + + @Test + public void shouldNotLogPlayerIn() { + // given + given(commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER)).willReturn(true); + Player player = mock(Player.class); + RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, "pass", "mail@example.org"); + + // when + executor.executePostPersistAction(); + + // then + verifyZeroInteractions(bukkitService, asynchronousLogin); + verify(syncProcessManager).processSyncPasswordRegister(player); + } + + private static Player mockPlayerWithName(String name) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + return player; + } +} diff --git a/src/test/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelperTest.java b/src/test/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelperTest.java new file mode 100644 index 00000000..567cf594 --- /dev/null +++ b/src/test/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelperTest.java @@ -0,0 +1,49 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.security.crypts.HashedPassword; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.junit.Test; + +import static fr.xephi.authme.AuthMeMatchers.hasAuthBasicData; +import static fr.xephi.authme.AuthMeMatchers.hasAuthLocation; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Test for {@link PlayerAuthBuilderHelper}. + */ +public class PlayerAuthBuilderHelperTest { + + @Test + public void shouldConstructPlayerAuth() { + // given + Player player = mock(Player.class); + given(player.getName()).willReturn("Noah"); + String ip = "192.168.34.47"; + TestHelper.mockPlayerIp(player, ip); + World world = mock(World.class); + given(world.getName()).willReturn("worldName"); + Location location = new Location(world, 123, 80, -99); + given(player.getLocation()).willReturn(location); + HashedPassword hashedPassword = new HashedPassword("myHash0001"); + String email = "test@example.org"; + + // when + PlayerAuth auth = PlayerAuthBuilderHelper.createPlayerAuth(player, hashedPassword, email); + + // then + assertThat(auth, hasAuthBasicData("noah", "Noah", email, ip)); + assertThat(auth, hasAuthLocation(123, 80, -99, "worldName")); + } + + @Test + public void shouldHaveHiddenConstructor() { + TestHelper.validateHasOnlyPrivateEmptyConstructor(PlayerAuthBuilderHelper.class); + } + +} diff --git a/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java b/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java index af109fbe..a6abf1c3 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java @@ -6,6 +6,7 @@ import com.google.common.io.Files; import fr.xephi.authme.TestHelper; import fr.xephi.authme.output.LogLevel; import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever; +import fr.xephi.authme.settings.properties.RegistrationArgumentType; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @@ -18,6 +19,7 @@ import java.nio.charset.StandardCharsets; import static fr.xephi.authme.TestHelper.getJarFile; import static fr.xephi.authme.settings.properties.PluginSettings.LOG_LEVEL; import static fr.xephi.authme.settings.properties.RegistrationSettings.DELAY_JOIN_MESSAGE; +import static fr.xephi.authme.settings.properties.RegistrationSettings.REGISTRATION_TYPE; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS; import static fr.xephi.authme.settings.properties.RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN; import static fr.xephi.authme.settings.properties.RestrictionSettings.FORCE_SPAWN_ON_WORLDS; @@ -59,6 +61,7 @@ public class SettingsMigrationServiceTest { assertThat(settings.getProperty(FORCE_SPAWN_LOCATION_AFTER_LOGIN), equalTo(true)); assertThat(settings.getProperty(FORCE_SPAWN_ON_WORLDS), contains("survival", "survival_nether", "creative")); assertThat(settings.getProperty(LOG_LEVEL), equalTo(LogLevel.INFO)); + assertThat(settings.getProperty(REGISTRATION_TYPE), equalTo(RegistrationArgumentType.EMAIL_WITH_CONFIRMATION)); // Check migration of old setting to email.html assertThat(Files.readLines(new File(dataFolder, "email.html"), StandardCharsets.UTF_8), diff --git a/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java b/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java index 6e258c42..414f18fb 100644 --- a/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java +++ b/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java @@ -9,6 +9,7 @@ import fr.xephi.authme.message.Messages; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RegistrationArgumentType; import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; @@ -67,7 +68,7 @@ public class LimboPlayerTaskManagerTest { given(messages.retrieve(key)).willReturn(new String[]{"Please register!"}); int interval = 12; given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(interval); - given(settings.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); + given(settings.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.EMAIL_WITH_CONFIRMATION); // when limboPlayerTaskManager.registerMessageTask(name, false); @@ -123,7 +124,7 @@ public class LimboPlayerTaskManagerTest { .willReturn(new String[]{"Please register", "Use /register"}); given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(8); - given(settings.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); + given(settings.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationArgumentType.EMAIL); // when limboPlayerTaskManager.registerMessageTask(name, false); diff --git a/src/test/resources/fr/xephi/authme/settings/config-old.yml b/src/test/resources/fr/xephi/authme/settings/config-old.yml index ff2b2918..5181b3f7 100644 --- a/src/test/resources/fr/xephi/authme/settings/config-old.yml +++ b/src/test/resources/fr/xephi/authme/settings/config-old.yml @@ -218,11 +218,11 @@ settings: # See restrictions for exceptions force: true # Does we replace password registration by an Email registration method ? - enableEmailRegistrationSystem: false + enableEmailRegistrationSystem: true # Enable double check of email when you register # when it's true, registration require that kind of command: # /register - doubleEmailCheck: false + doubleEmailCheck: true # Do we force kicking player after a successful registration ? # Do not use with login feature below forceKickAfterRegister: false