From 398fa4d38d0c6cca8ecead2f810535b906092366 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 17 Dec 2016 21:59:34 +0100 Subject: [PATCH] #830 Refactor registration process - Introduce registration executors: one for each registration variant; each extending class implements one registration variant and worries about that method's details only - AsyncRegister receives the player and a registration executor --- src/main/java/fr/xephi/authme/api/NewAPI.java | 11 +- .../executable/register/RegisterCommand.java | 34 ++-- .../fr/xephi/authme/process/Management.java | 5 +- .../process/register/AsyncRegister.java | 174 ++++-------------- .../EmailRegisterExecutorProvider.java | 91 +++++++++ .../PasswordRegisterExecutorProvider.java | 153 +++++++++++++++ .../executors/PlayerAuthBuilderHelper.java | 26 +++ .../executors/RegistrationExecutor.java | 30 +++ .../RegistrationExecutorProvider.java | 40 ++++ .../register/RegisterCommandTest.java | 29 +-- 10 files changed, 431 insertions(+), 162 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index 82a2483bc..d4f34b2c6 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 9164ab471..cc5a0315e 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,6 +5,7 @@ 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; @@ -12,13 +13,12 @@ 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.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; @@ -40,11 +40,15 @@ 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; } @@ -71,12 +75,20 @@ public class RegisterCommand extends PlayerCommand { 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 && !validationService.validateEmail(arguments.get(1))) { - commonService.send(player, MessageKey.INVALID_EMAIL); + } else if (registrationType == PASSWORD_WITH_EMAIL) { + handlePasswordWithEmailRegistration(player, arguments); } else { - // We might only have received one argument, need to check that it's safe to do arguments.get(1) - String email = registrationType == PASSWORD_WITH_EMAIL ? arguments.get(1) : null; - management.performRegister(player, arguments.get(0), email, 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); } } @@ -91,12 +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(REGISTRATION_TYPE) == RegistrationArgumentType.EMAIL_WITH_CONFIRMATION - && !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)); } } } diff --git a/src/main/java/fr/xephi/authme/process/Management.java b/src/main/java/fr/xephi/authme/process/Management.java index be32e99e8..8aea8341a 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/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index 7b84a73ed..d32f6eadb 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -3,73 +3,51 @@ 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.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.service.BukkitService; +import fr.xephi.authme.process.register.executors.RegistrationExecutor; 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.EmailSettings; -import fr.xephi.authme.settings.properties.PluginSettings; -import fr.xephi.authme.settings.properties.RegistrationArgumentType.Execution; 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.util.PlayerUtils; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import javax.inject.Inject; import java.util.List; import static fr.xephi.authme.permission.PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS; -import static fr.xephi.authme.settings.properties.RegistrationArgumentType.PASSWORD_WITH_EMAIL; /** * Asynchronous processing of a request for registration. */ 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); @@ -77,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 @@ -109,89 +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 (Execution.EMAIL == service.getProperty(RegistrationSettings.REGISTRATION_TYPE).getExecution()) { - emailRegister(player, password, email); - } else { - passwordRegister(player, password, email, 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); - } - } - - // Email arg might be the password depending on the registration type. TODO #830: Fix with more specific methods - private void passwordRegister(Player player, String password, String email, 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 (service.getProperty(RegistrationSettings.REGISTRATION_TYPE) == PASSWORD_WITH_EMAIL) { - auth.setEmail(email); - } - - 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 000000000..88f8cd602 --- /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 000000000..c5a4761ba --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java @@ -0,0 +1,153 @@ +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; + + +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 000000000..dfad47cf9 --- /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 { + + 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 000000000..cfb02393d --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java @@ -0,0 +1,30 @@ +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. + * + * @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 000000000..04adce312 --- /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/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java index 2c2947c3b..d896c4fb9 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.RegistrationSettings; import fr.xephi.authme.settings.properties.RegistrationArgumentType; +import fr.xephi.authme.settings.properties.RegistrationSettings; 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; @@ -55,6 +54,9 @@ public class RegisterCommandTest { @Mock private ValidationService validationService; + @Mock + private RegistrationExecutorProvider registrationExecutorProvider; + @BeforeClass public static void setup() { TestHelper.setupLogger(); @@ -84,12 +86,14 @@ public class RegisterCommandTest { // given 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); } @@ -191,12 +195,11 @@ public class RegisterCommandTest { // given String playerMail = "asfd@lakjgre.lds"; given(validationService.validateEmail(playerMail)).willReturn(true); - int passLength = 7; - given(commonService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(passLength); - 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)); @@ -204,7 +207,7 @@ 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 @@ -225,12 +228,14 @@ 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", null, true); + verify(management).performRegister(player, executor); } @Test @@ -241,13 +246,15 @@ public class RegisterCommandTest { 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, "myPass", email, true); + verify(management).performRegister(player, executor); } @Test