#1037 Improve architecture of registration methods (#236)

* #1037 Improve architecture of registration methods
- Introduce parameters classes for each registration method
- RegistrationMethod has constants with the required parameters class -> no longer need to have a RegistrationExecutorProvider class that needs to be injected everywhere, let AsyncRegister be the only one to worry about which class will perform the registration
- Fix inheritance of password registration types - previously two-factor auth registration inherited from password registration directly

* Create matcher which checks for equality via reflections
- Allow to perform equality check on objects with default equals() method
This commit is contained in:
ljacqu 2017-03-20 08:19:52 +01:00 committed by GitHub
parent 7f16e80442
commit 9a9d0974f8
31 changed files with 820 additions and 393 deletions

View File

@ -15,6 +15,7 @@ import fr.xephi.authme.initialization.OnStartupTasks;
import fr.xephi.authme.initialization.SettingsProvider;
import fr.xephi.authme.initialization.TaskCloser;
import fr.xephi.authme.initialization.factory.FactoryDependencyHandler;
import fr.xephi.authme.initialization.factory.SingletonStoreDependencyHandler;
import fr.xephi.authme.listener.BlockListener;
import fr.xephi.authme.listener.EntityListener;
import fr.xephi.authme.listener.PlayerListener;
@ -206,7 +207,7 @@ public class AuthMe extends JavaPlugin {
// Create injector, provide elements from the Bukkit environment and register providers
injector = new InjectorBuilder()
.addHandlers(new FactoryDependencyHandler())
.addHandlers(new FactoryDependencyHandler(), new SingletonStoreDependencyHandler())
.addDefaultHandlers("fr.xephi.authme")
.create();
injector.register(AuthMe.class, this);

View File

@ -5,7 +5,8 @@ import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider;
import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams;
import fr.xephi.authme.process.register.executors.RegistrationMethod;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.PluginHookService;
@ -35,15 +36,13 @@ 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,
RegistrationExecutorProvider registrationExecutorProvider) {
Management management, ValidationService validationService, PlayerCache playerCache) {
this.plugin = plugin;
this.pluginHookService = pluginHookService;
this.dataSource = dataSource;
@ -51,7 +50,6 @@ public class NewAPI {
this.management = management;
this.validationService = validationService;
this.playerCache = playerCache;
this.registrationExecutorProvider = registrationExecutorProvider;
NewAPI.singleton = this;
}
@ -204,8 +202,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,
registrationExecutorProvider.getPasswordRegisterExecutor(player, password, autoLogin));
management.performRegister(RegistrationMethod.API_REGISTRATION,
ApiPasswordRegisterParams.of(player, password, autoLogin));
}
/**

View File

@ -7,7 +7,10 @@ import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
import fr.xephi.authme.process.register.RegistrationType;
import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider;
import fr.xephi.authme.process.register.executors.EmailRegisterParams;
import fr.xephi.authme.process.register.executors.PasswordRegisterParams;
import fr.xephi.authme.process.register.executors.RegistrationMethod;
import fr.xephi.authme.process.register.executors.TwoFactorRegisterParams;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.ValidationService;
@ -42,15 +45,12 @@ public class RegisterCommand extends PlayerCommand {
@Inject
private ValidationService validationService;
@Inject
private RegistrationExecutorProvider registrationExecutorProvider;
@Override
public void runCommand(Player player, List<String> 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,
registrationExecutorProvider.getTwoFactorRegisterExecutor(player));
management.performRegister(RegistrationMethod.TWO_FACTOR_REGISTRATION,
TwoFactorRegisterParams.of(player));
return;
} else if (arguments.size() < 1) {
commonService.send(player, MessageKey.USAGE_REGISTER);
@ -82,8 +82,8 @@ public class RegisterCommand extends PlayerCommand {
final String password = arguments.get(0);
final String email = getEmailIfAvailable(arguments);
management.performRegister(
player, registrationExecutorProvider.getPasswordRegisterExecutor(player, password, email));
management.performRegister(RegistrationMethod.PASSWORD_REGISTRATION,
PasswordRegisterParams.of(player, password, email));
}
}
@ -138,7 +138,8 @@ public class RegisterCommand extends PlayerCommand {
if (!validationService.validateEmail(email)) {
commonService.send(player, MessageKey.INVALID_EMAIL);
} else if (isSecondArgValidForEmailRegistration(player, arguments)) {
management.performRegister(player, registrationExecutorProvider.getEmailRegisterExecutor(player, email));
management.performRegister(RegistrationMethod.EMAIL_REGISTRATION,
EmailRegisterParams.of(player, email));
}
}

View File

@ -16,8 +16,8 @@ public class FactoryDependencyHandler implements DependencyHandler {
if (dependencyDescription.getType() == Factory.class) {
Class<?> genericType = ReflectionUtils.getGenericType(dependencyDescription.getGenericType());
if (genericType == null) {
throw new IllegalStateException("Factory fields must have concrete generic type. " +
"Cannot get generic type for field in '" + context.getMappedClass() + "'");
throw new IllegalStateException("Factory fields must have concrete generic type. "
+ "Cannot get generic type for field in '" + context.getMappedClass() + "'");
}
return new FactoryImpl<>(genericType, context.getInjector());

View File

@ -0,0 +1,37 @@
package fr.xephi.authme.initialization.factory;
import java.util.Collection;
/**
* Injectable object to retrieve and create singletons of a common parent.
*
* @param <P> the parent class to which this store is limited to
*/
public interface SingletonStore<P> {
/**
* Returns the singleton of the given type, creating it if it hasn't been yet created.
*
* @param clazz the class to get the singleton for
* @param <C> type of the singleton
* @return the singleton of type {@code C}
*/
<C extends P> C getSingleton(Class<C> clazz);
/**
* Returns all existing singletons of this store's type.
*
* @return all registered singletons of type {@code P}
*/
Collection<P> retrieveAllOfType();
/**
* Returns all existing singletons of the given type.
*
* @param clazz the type to get singletons for
* @param <C> class type
* @return all registered singletons of type {@code C}
*/
<C extends P> Collection<C> retrieveAllOfType(Class<C> clazz);
}

View File

@ -0,0 +1,61 @@
package fr.xephi.authme.initialization.factory;
import ch.jalu.injector.Injector;
import ch.jalu.injector.context.ResolvedInstantiationContext;
import ch.jalu.injector.handlers.dependency.DependencyHandler;
import ch.jalu.injector.handlers.instantiation.DependencyDescription;
import ch.jalu.injector.utils.ReflectionUtils;
import java.util.Collection;
/**
* Dependency handler that builds {@link SingletonStore} objects.
*/
public class SingletonStoreDependencyHandler implements DependencyHandler {
@Override
public Object resolveValue(ResolvedInstantiationContext<?> context, DependencyDescription dependencyDescription) {
if (dependencyDescription.getType() == SingletonStore.class) {
Class<?> genericType = ReflectionUtils.getGenericType(dependencyDescription.getGenericType());
if (genericType == null) {
throw new IllegalStateException("Singleton store fields must have concrete generic type. "
+ "Cannot get generic type for field in '" + context.getMappedClass() + "'");
}
return new SingletonStoreImpl<>(genericType, context.getInjector());
}
return null;
}
private static final class SingletonStoreImpl<P> implements SingletonStore<P> {
private final Injector injector;
private final Class<P> parentClass;
SingletonStoreImpl(Class<P> parentClass, Injector injector) {
this.parentClass = parentClass;
this.injector = injector;
}
@Override
public <C extends P> C getSingleton(Class<C> clazz) {
if (parentClass.isAssignableFrom(clazz)) {
return injector.getSingleton(clazz);
}
throw new IllegalArgumentException(clazz + " not child of " + parentClass);
}
@Override
public Collection<P> retrieveAllOfType() {
return retrieveAllOfType(parentClass);
}
@Override
public <C extends P> Collection<C> retrieveAllOfType(Class<C> clazz) {
if (parentClass.isAssignableFrom(clazz)) {
return injector.retrieveAllOfType(clazz);
}
throw new IllegalArgumentException(clazz + " not child of " + parentClass);
}
}
}

View File

@ -8,7 +8,8 @@ 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.register.executors.RegistrationMethod;
import fr.xephi.authme.process.register.executors.RegistrationParameters;
import fr.xephi.authme.process.unregister.AsynchronousUnregister;
import fr.xephi.authme.service.BukkitService;
import org.bukkit.command.CommandSender;
@ -60,8 +61,8 @@ public class Management {
runTask(() -> asynchronousLogout.logout(player));
}
public void performRegister(Player player, RegistrationExecutor registrationExecutor) {
runTask(() -> asyncRegister.register(player, registrationExecutor));
public <P extends RegistrationParameters> void performRegister(RegistrationMethod<P> variant, P parameters) {
runTask(() -> asyncRegister.register(variant, parameters));
}
public void performUnregister(Player player, String password) {

View File

@ -3,10 +3,13 @@ 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.initialization.factory.SingletonStore;
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.process.register.executors.RegistrationParameters;
import fr.xephi.authme.process.register.executors.RegistrationMethod;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
@ -31,6 +34,8 @@ public class AsyncRegister implements AsynchronousProcess {
private CommonService service;
@Inject
private PermissionsManager permissionsManager;
@Inject
private SingletonStore<RegistrationExecutor> registrationExecutorFactory;
AsyncRegister() {
}
@ -38,12 +43,16 @@ public class AsyncRegister implements AsynchronousProcess {
/**
* Performs the registration process for the given player.
*
* @param player the player to register
* @param executor the registration executor to perform the registration with
* @param variant the registration method
* @param parameters the parameters
* @param <P> parameters type
*/
public void register(Player player, RegistrationExecutor executor) {
if (preRegisterCheck(player) && executor.isRegistrationAdmitted()) {
executeRegistration(player, executor);
public <P extends RegistrationParameters> void register(RegistrationMethod<P> variant, P parameters) {
if (preRegisterCheck(parameters.getPlayer())) {
RegistrationExecutor<P> executor = registrationExecutorFactory.getSingleton(variant.getExecutorClass());
if (executor.isRegistrationAdmitted(parameters)) {
executeRegistration(parameters, executor);
}
}
}
@ -66,15 +75,17 @@ public class AsyncRegister implements AsynchronousProcess {
/**
* Executes the registration.
*
* @param player the player to register
* @param parameters the registration parameters
* @param executor the executor to perform the registration process with
* @param <P> registration params type
*/
private void executeRegistration(Player player, RegistrationExecutor executor) {
PlayerAuth auth = executor.buildPlayerAuth();
private <P extends RegistrationParameters>
void executeRegistration(P parameters, RegistrationExecutor<P> executor) {
PlayerAuth auth = executor.buildPlayerAuth(parameters);
if (database.saveAuth(auth)) {
executor.executePostPersistAction();
executor.executePostPersistAction(parameters);
} else {
service.send(player, MessageKey.ERROR);
service.send(parameters.getPlayer(), MessageKey.ERROR);
}
}

View File

@ -0,0 +1,97 @@
package fr.xephi.authme.process.register.executors;
import fr.xephi.authme.data.auth.PlayerAuth;
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.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import org.bukkit.entity.Player;
import javax.inject.Inject;
/**
* Registration executor for registration methods where the password
* is supplied by the user.
*/
abstract class AbstractPasswordRegisterExecutor<P extends AbstractPasswordRegisterParams>
implements RegistrationExecutor<P> {
/**
* 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;
@Override
public boolean isRegistrationAdmitted(P params) {
ValidationService.ValidationResult passwordValidation = validationService.validatePassword(
params.getPassword(), params.getPlayer().getName());
if (passwordValidation.hasError()) {
commonService.send(params.getPlayer(), passwordValidation.getMessageKey(), passwordValidation.getArgs());
return false;
}
return true;
}
@Override
public PlayerAuth buildPlayerAuth(P params) {
HashedPassword hashedPassword = passwordSecurity.computeHash(params.getPassword(), params.getPlayerName());
params.setHashedPassword(hashedPassword);
return createPlayerAuthObject(params);
}
/**
* Creates the PlayerAuth object to store into the database, based on the registration parameters.
*
* @param params the parameters
* @return the PlayerAuth representing the new account to register
*/
protected abstract PlayerAuth createPlayerAuthObject(P params);
/**
* Returns whether the player should be automatically logged in after registration.
*
* @param params the registration parameters
* @return true if the player should be logged in, false otherwise
*/
protected boolean performLoginAfterRegister(P params) {
return !commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER);
}
@Override
public void executePostPersistAction(P params) {
final Player player = params.getPlayer();
if (performLoginAfterRegister(params)) {
if (commonService.getProperty(PluginSettings.USE_ASYNC_TASKS)) {
bukkitService.runTaskAsynchronously(() -> asynchronousLogin.forceLogin(player));
} else {
bukkitService.scheduleSyncDelayedTask(() -> asynchronousLogin.forceLogin(player), SYNC_LOGIN_DELAY);
}
}
syncProcessManager.processSyncPasswordRegister(player);
}
}

View File

@ -0,0 +1,48 @@
package fr.xephi.authme.process.register.executors;
import fr.xephi.authme.security.crypts.HashedPassword;
import org.bukkit.entity.Player;
/**
* Common params type for implementors of {@link AbstractPasswordRegisterExecutor}.
* Password must be supplied on creation and cannot be changed later on. The {@link HashedPassword}
* is stored on the params object for later use.
*/
public abstract class AbstractPasswordRegisterParams extends RegistrationParameters {
private final String password;
private HashedPassword hashedPassword;
/**
* Constructor.
*
* @param player the player to register
* @param password the password to use
*/
public AbstractPasswordRegisterParams(Player player, String password) {
super(player);
this.password = password;
}
/**
* Constructor with no defined password. Use for registration methods which
* have no implicit password (like two factor authentication).
*
* @param player the player to register
*/
public AbstractPasswordRegisterParams(Player player) {
this(player, null);
}
public String getPassword() {
return password;
}
void setHashedPassword(HashedPassword hashedPassword) {
this.hashedPassword = hashedPassword;
}
HashedPassword getHashedPassword() {
return hashedPassword;
}
}

View File

@ -0,0 +1,20 @@
package fr.xephi.authme.process.register.executors;
import fr.xephi.authme.data.auth.PlayerAuth;
/**
* Executor for password registration via API call.
*/
class ApiPasswordRegisterExecutor extends AbstractPasswordRegisterExecutor<ApiPasswordRegisterParams> {
@Override
protected PlayerAuth createPlayerAuthObject(ApiPasswordRegisterParams params) {
return PlayerAuthBuilderHelper
.createPlayerAuth(params.getPlayer(), params.getHashedPassword(), null);
}
@Override
protected boolean performLoginAfterRegister(ApiPasswordRegisterParams params) {
return params.getLoginAfterRegister();
}
}

View File

@ -0,0 +1,35 @@
package fr.xephi.authme.process.register.executors;
import org.bukkit.entity.Player;
/**
* Parameters for {@link ApiPasswordRegisterExecutor}.
*/
public class ApiPasswordRegisterParams extends PasswordRegisterParams {
private final boolean loginAfterRegister;
protected ApiPasswordRegisterParams(Player player, String password, boolean loginAfterRegister) {
super(player, password, null);
this.loginAfterRegister = loginAfterRegister;
}
/**
* Creates a parameters object.
*
* @param player the player to register
* @param password the password to register with
* @param loginAfterRegister whether the player should be logged in after registration
* @return params object with the given data
*/
public static ApiPasswordRegisterParams of(Player player, String password, boolean loginAfterRegister) {
return new ApiPasswordRegisterParams(player, password, loginAfterRegister);
}
/**
* @return true if the player should be logged in after being registered, false otherwise
*/
public boolean getLoginAfterRegister() {
return loginAfterRegister;
}
}

View File

@ -0,0 +1,80 @@
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.EmailService;
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.permission.PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS;
import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
/**
* Executor for email registration: the player only provides his email address,
* to which a generated password is sent.
*/
class EmailRegisterExecutor implements RegistrationExecutor<EmailRegisterParams> {
@Inject
private PermissionsManager permissionsManager;
@Inject
private DataSource dataSource;
@Inject
private CommonService commonService;
@Inject
private EmailService emailService;
@Inject
private SyncProcessManager syncProcessManager;
@Inject
private PasswordSecurity passwordSecurity;
@Override
public boolean isRegistrationAdmitted(EmailRegisterParams params) {
final int maxRegPerEmail = commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL);
if (maxRegPerEmail > 0 && !permissionsManager.hasPermission(params.getPlayer(), ALLOW_MULTIPLE_ACCOUNTS)) {
int otherAccounts = dataSource.countAuthsByEmail(params.getEmail());
if (otherAccounts >= maxRegPerEmail) {
commonService.send(params.getPlayer(), MessageKey.MAX_REGISTER_EXCEEDED,
Integer.toString(maxRegPerEmail), Integer.toString(otherAccounts), "@");
return false;
}
}
return true;
}
@Override
public PlayerAuth buildPlayerAuth(EmailRegisterParams params) {
String password = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH));
HashedPassword hashedPassword = passwordSecurity.computeHash(password, params.getPlayer().getName());
params.setPassword(password);
return createPlayerAuth(params.getPlayer(), hashedPassword, params.getEmail());
}
@Override
public void executePostPersistAction(EmailRegisterParams params) {
Player player = params.getPlayer();
boolean couldSendMail = emailService.sendPasswordMail(
player.getName(), params.getEmail(), params.getPassword());
if (couldSendMail) {
syncProcessManager.processSyncEmailRegister(player);
} else {
commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
}
}
}

View File

@ -1,91 +0,0 @@
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.EmailService;
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.permission.PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS;
import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
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 EmailService emailService;
@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 = emailService.sendPasswordMail(player.getName(), email, password);
if (couldSendMail) {
syncProcessManager.processSyncEmailRegister(player);
} else {
commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
}
}
}
}

View File

@ -0,0 +1,43 @@
package fr.xephi.authme.process.register.executors;
import org.bukkit.entity.Player;
/**
* Parameters for email registration.
*/
public class EmailRegisterParams extends RegistrationParameters {
private final String email;
private String password;
protected EmailRegisterParams(Player player, String email) {
super(player);
this.email = email;
}
/**
* Creates a params object for email registration.
*
* @param player the player to register
* @param email the player's email
* @return params object with the given data
*/
public static EmailRegisterParams of(Player player, String email) {
return new EmailRegisterParams(player, email);
}
public String getEmail() {
return email;
}
void setPassword(String password) {
this.password = password;
}
/**
* @return the password generated for the player
*/
String getPassword() {
return password;
}
}

View File

@ -0,0 +1,17 @@
package fr.xephi.authme.process.register.executors;
import fr.xephi.authme.data.auth.PlayerAuth;
import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
/**
* Registration executor for password registration.
*/
class PasswordRegisterExecutor extends AbstractPasswordRegisterExecutor<PasswordRegisterParams> {
@Override
public PlayerAuth createPlayerAuthObject(PasswordRegisterParams params) {
return createPlayerAuth(params.getPlayer(), params.getHashedPassword(), params.getEmail());
}
}

View File

@ -1,164 +0,0 @@
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 {
private final Player player;
private final String password;
private final String email;
private 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);
}
protected Player getPlayer() {
return player;
}
protected HashedPassword getHashedPassword() {
return hashedPassword;
}
}
/** 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 hash = getHashedPassword().getHash();
String qrCodeUrl = TwoFactor.getQRBarcodeURL(getPlayer().getName(), Bukkit.getIp(), hash);
commonService.send(getPlayer(), MessageKey.TWO_FACTOR_CREATE, hash, qrCodeUrl);
}
}
}

View File

@ -0,0 +1,32 @@
package fr.xephi.authme.process.register.executors;
import org.bukkit.entity.Player;
/**
* Parameters for registration with a given password, and optionally an email address.
*/
public class PasswordRegisterParams extends AbstractPasswordRegisterParams {
private final String email;
protected PasswordRegisterParams(Player player, String password, String email) {
super(player, password);
this.email = email;
}
/**
* Creates a params object.
*
* @param player the player to register
* @param password the password to register with
* @param email the email of the player (may be null)
* @return params object with the given data
*/
public static PasswordRegisterParams of(Player player, String password, String email) {
return new PasswordRegisterParams(player, password, email);
}
public String getEmail() {
return email;
}
}

View File

@ -13,6 +13,14 @@ final class PlayerAuthBuilderHelper {
private PlayerAuthBuilderHelper() {
}
/**
* Creates a {@link PlayerAuth} object with the given data.
*
* @param player the player to create a PlayerAuth for
* @param hashedPassword the hashed password
* @param email the email address (nullable)
* @return the generated PlayerAuth object
*/
static PlayerAuth createPlayerAuth(Player player, HashedPassword hashedPassword, String email) {
return PlayerAuth.builder()
.name(player.getName().toLowerCase())

View File

@ -5,7 +5,7 @@ import fr.xephi.authme.data.auth.PlayerAuth;
/**
* Performs the registration action.
*/
public interface RegistrationExecutor {
public interface RegistrationExecutor<P extends RegistrationParameters> {
/**
* Returns whether the registration may take place. Use this method to execute
@ -14,20 +14,24 @@ public interface RegistrationExecutor {
* If this method returns {@code false}, it is expected that the executor inform
* the player about the error within this method call.
*
* @param params the parameters for the registration
* @return true if registration may be performed, false otherwise
*/
boolean isRegistrationAdmitted();
boolean isRegistrationAdmitted(P params);
/**
* Constructs the PlayerAuth object to persist into the database.
*
* @param params the parameters for the registration
* @return the player auth to register in the data source
*/
PlayerAuth buildPlayerAuth();
PlayerAuth buildPlayerAuth(P params);
/**
* Follow-up method called after the player auth could be added into the database.
*
* @param params the parameters for the registration
*/
void executePostPersistAction();
void executePostPersistAction(P params);
}

View File

@ -1,36 +0,0 @@
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, 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);
}
}

View File

@ -0,0 +1,51 @@
package fr.xephi.authme.process.register.executors;
/**
* Methods with which a player can be registered.
* <p>
* These constants each define a different way of registering a player and define the
* {@link RegistrationParameters parameters} and {@link RegistrationExecutor executor}
* classes which perform this registration method. This is essentially a <i>typed enum</i>
* as passing a constant of this class along with a parameters object to a method can
* be restricted to the correct parameters type.
*/
public final class RegistrationMethod<P extends RegistrationParameters> {
/**
* Password registration.
*/
public static final RegistrationMethod<PasswordRegisterParams> PASSWORD_REGISTRATION =
new RegistrationMethod<>(PasswordRegisterExecutor.class);
/**
* Registration with two-factor authentication as login means.
*/
public static final RegistrationMethod<TwoFactorRegisterParams> TWO_FACTOR_REGISTRATION =
new RegistrationMethod<>(TwoFactorRegisterExecutor.class);
/**
* Email registration: an email address is provided, to which a generated password is sent.
*/
public static final RegistrationMethod<EmailRegisterParams> EMAIL_REGISTRATION =
new RegistrationMethod<>(EmailRegisterExecutor.class);
/**
* API registration: player and password are provided via an API method.
*/
public static final RegistrationMethod<ApiPasswordRegisterParams> API_REGISTRATION =
new RegistrationMethod<>(ApiPasswordRegisterExecutor.class);
private final Class<? extends RegistrationExecutor<P>> executorClass;
private RegistrationMethod(Class<? extends RegistrationExecutor<P>> executorClass) {
this.executorClass = executorClass;
}
/**
* @return the executor class to perform the registration method
*/
public Class<? extends RegistrationExecutor<P>> getExecutorClass() {
return executorClass;
}
}

View File

@ -0,0 +1,28 @@
package fr.xephi.authme.process.register.executors;
import org.bukkit.entity.Player;
/**
* Parent of all registration parameters.
*/
public abstract class RegistrationParameters {
private final Player player;
/**
* Constructor.
*
* @param player the player to perform the registration for
*/
public RegistrationParameters(Player player) {
this.player = player;
}
public Player getPlayer() {
return player;
}
public String getPlayerName() {
return player.getName();
}
}

View File

@ -0,0 +1,43 @@
package fr.xephi.authme.process.register.executors;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.security.crypts.TwoFactor;
import fr.xephi.authme.service.CommonService;
import org.bukkit.Bukkit;
import javax.inject.Inject;
import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
/**
* Executor for two-factor registration.
*/
class TwoFactorRegisterExecutor extends AbstractPasswordRegisterExecutor<TwoFactorRegisterParams> {
@Inject
private CommonService commonService;
@Override
public boolean isRegistrationAdmitted(TwoFactorRegisterParams params) {
// nothing to check
return true;
}
@Override
protected PlayerAuth createPlayerAuthObject(TwoFactorRegisterParams params) {
return createPlayerAuth(params.getPlayer(), params.getHashedPassword(), null);
}
@Override
public void executePostPersistAction(TwoFactorRegisterParams params) {
super.executePostPersistAction(params);
// Note ljacqu 20170317: This two-factor registration type is only invoked when the password hash is configured
// to two-factor authentication. Therefore, the hashed password is the result of the TwoFactor EncryptionMethod
// implementation (contains the TOTP secret).
String hash = params.getHashedPassword().getHash();
String qrCodeUrl = TwoFactor.getQRBarcodeURL(params.getPlayerName(), Bukkit.getIp(), hash);
commonService.send(params.getPlayer(), MessageKey.TWO_FACTOR_CREATE, hash, qrCodeUrl);
}
}

View File

@ -0,0 +1,23 @@
package fr.xephi.authme.process.register.executors;
import org.bukkit.entity.Player;
/**
* Parameters for registration with two-factor authentication.
*/
public class TwoFactorRegisterParams extends AbstractPasswordRegisterParams {
protected TwoFactorRegisterParams(Player player) {
super(player);
}
/**
* Creates a parameters object.
*
* @param player the player to register
* @return params object with the given player
*/
public static TwoFactorRegisterParams of(Player player) {
return new TwoFactorRegisterParams(player);
}
}

View File

@ -9,6 +9,7 @@ import fr.xephi.authme.command.CommandHandler;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.initialization.factory.FactoryDependencyHandler;
import fr.xephi.authme.initialization.factory.SingletonStoreDependencyHandler;
import fr.xephi.authme.listener.BlockListener;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.Management;
@ -93,7 +94,7 @@ public class AuthMeInitializationTest {
new Settings(dataFolder, mock(PropertyResource.class), null, buildConfigurationData());
Injector injector = new InjectorBuilder()
.addHandlers(new FactoryDependencyHandler())
.addHandlers(new FactoryDependencyHandler(), new SingletonStoreDependencyHandler())
.addDefaultHandlers("fr.xephi.authme")
.create();
injector.provide(DataFolder.class, dataFolder);

View File

@ -25,7 +25,7 @@ public final class ReflectionTestUtils {
*/
public static <T> void setField(Class<T> clazz, T instance, String fieldName, Object value) {
try {
Field field = getField(clazz, instance, fieldName);
Field field = getField(clazz, fieldName);
field.set(instance, value);
} catch (IllegalAccessException e) {
throw new UnsupportedOperationException(
@ -34,24 +34,30 @@ public final class ReflectionTestUtils {
}
}
private static <T> Field getField(Class<T> clazz, T instance, String fieldName) {
private static <T> Field getField(Class<T> clazz, String fieldName) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {
throw new UnsupportedOperationException(format("Could not get field '%s' for instance '%s' of class '%s'",
fieldName, instance, clazz.getName()), e);
throw new UnsupportedOperationException(format("Could not get field '%s' from class '%s'",
fieldName, clazz.getName()), e);
}
}
@SuppressWarnings("unchecked")
public static <T, V> V getFieldValue(Class<T> clazz, T instance, String fieldName) {
Field field = getField(clazz, instance, fieldName);
Field field = getField(clazz, fieldName);
return getFieldValue(field, instance);
}
@SuppressWarnings("unchecked")
public static <V> V getFieldValue(Field field, Object instance) {
field.setAccessible(true);
try {
return (V) field.get(instance);
} catch (IllegalAccessException e) {
throw new UnsupportedOperationException("Could not get value of field '" + fieldName + "'", e);
throw new UnsupportedOperationException("Could not get value of field '" + field.getName() + "'", e);
}
}

View File

@ -1,13 +1,17 @@
package fr.xephi.authme.command.executable.register;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
import fr.xephi.authme.process.register.RegistrationType;
import fr.xephi.authme.process.register.executors.RegistrationExecutor;
import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider;
import fr.xephi.authme.process.register.executors.EmailRegisterParams;
import fr.xephi.authme.process.register.executors.PasswordRegisterParams;
import fr.xephi.authme.process.register.executors.RegistrationMethod;
import fr.xephi.authme.process.register.executors.RegistrationParameters;
import fr.xephi.authme.process.register.executors.TwoFactorRegisterParams;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.ValidationService;
@ -16,6 +20,9 @@ import fr.xephi.authme.settings.properties.SecuritySettings;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@ -24,10 +31,16 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
@ -57,9 +70,6 @@ public class RegisterCommandTest {
@Mock
private ValidationService validationService;
@Mock
private RegistrationExecutorProvider registrationExecutorProvider;
@BeforeClass
public static void setup() {
TestHelper.setupLogger();
@ -90,14 +100,13 @@ 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, executor);
verify(management).performRegister(eq(RegistrationMethod.TWO_FACTOR_REGISTRATION),
argThat(isEqualTo(TwoFactorRegisterParams.of(player))));
verifyZeroInteractions(emailService);
}
@ -208,8 +217,6 @@ public class RegisterCommandTest {
given(commonService.getProperty(RegistrationSettings.REGISTER_SECOND_ARGUMENT)).willReturn(RegisterSecondaryArgument.CONFIRMATION);
given(emailService.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));
@ -217,7 +224,8 @@ public class RegisterCommandTest {
// then
verify(validationService).validateEmail(playerMail);
verify(emailService).hasAllInformation();
verify(management).performRegister(player, executor);
verify(management).performRegister(eq(RegistrationMethod.EMAIL_REGISTRATION),
argThat(isEqualTo(EmailRegisterParams.of(player, playerMail))));
}
@Test
@ -239,14 +247,13 @@ public class RegisterCommandTest {
public void shouldPerformPasswordRegistration() {
// given
Player player = mock(Player.class);
RegistrationExecutor executor = mock(RegistrationExecutor.class);
given(registrationExecutorProvider.getPasswordRegisterExecutor(player, "myPass", null)).willReturn(executor);
// when
command.executeCommand(player, Collections.singletonList("myPass"));
// then
verify(management).performRegister(player, executor);
verify(management).performRegister(eq(RegistrationMethod.PASSWORD_REGISTRATION),
argThat(isEqualTo(PasswordRegisterParams.of(player, "myPass", null))));
}
@Test
@ -257,15 +264,14 @@ 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, executor);
verify(management).performRegister(eq(RegistrationMethod.PASSWORD_REGISTRATION),
argThat(isEqualTo(PasswordRegisterParams.of(player, "myPass", email))));
}
@Test
@ -292,14 +298,64 @@ public class RegisterCommandTest {
given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationType.PASSWORD);
given(commonService.getProperty(RegistrationSettings.REGISTER_SECOND_ARGUMENT)).willReturn(RegisterSecondaryArgument.EMAIL_OPTIONAL);
Player player = mock(Player.class);
RegistrationExecutor executor = mock(RegistrationExecutor.class);
given(registrationExecutorProvider.getPasswordRegisterExecutor(eq(player), anyString(), eq(null))).willReturn(executor);
// when
command.executeCommand(player, Collections.singletonList("myPass"));
// then
verify(registrationExecutorProvider).getPasswordRegisterExecutor(player, "myPass", null);
verify(management).performRegister(player, executor);
verify(management).performRegister(eq(RegistrationMethod.PASSWORD_REGISTRATION),
argThat(isEqualTo(PasswordRegisterParams.of(player, "myPass", null))));
}
// TODO ljacqu 20170317: Document and extract as util
private static <P extends RegistrationParameters> Matcher<P> isEqualTo(P expected) {
return new TypeSafeMatcher<P>() {
@Override
protected boolean matchesSafely(RegistrationParameters item) {
assertAreParamsEqual(expected, item);
return true;
}
@Override
public void describeTo(Description description) {
description.appendText("parameters " + expected);
}
};
}
private static void assertAreParamsEqual(RegistrationParameters lhs, RegistrationParameters rhs) {
if (lhs.getClass() != rhs.getClass()) {
fail("Params classes don't match, got " + lhs.getClass().getSimpleName()
+ " and " + rhs.getClass().getSimpleName());
}
List<Field> fieldsToCheck = getFields(lhs);
for (Field field : fieldsToCheck) {
Object lhsValue = ReflectionTestUtils.getFieldValue(field, lhs);
Object rhsValue = ReflectionTestUtils.getFieldValue(field, rhs);
if (!Objects.equals(lhsValue, rhsValue)) {
fail("Field '" + field.getName() + "' does not have same value: '"
+ lhsValue + "' vs. '" + rhsValue + "'");
}
}
}
private static List<Field> getFields(RegistrationParameters params) {
List<Field> fields = new ArrayList<>();
Class<?> currentClass = params.getClass();
while (currentClass != null) {
for (Field f : currentClass.getDeclaredFields()) {
if (!Modifier.isStatic(f.getModifiers())) {
fields.add(f);
}
}
if (currentClass == RegistrationParameters.class) {
break;
}
currentClass = currentClass.getSuperclass();
}
return fields;
}
}

View File

@ -3,9 +3,13 @@ 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.initialization.factory.SingletonStore;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.register.executors.PasswordRegisterParams;
import fr.xephi.authme.process.register.executors.RegistrationExecutor;
import fr.xephi.authme.process.register.executors.RegistrationMethod;
import fr.xephi.authme.process.register.executors.TwoFactorRegisterParams;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
@ -16,6 +20,7 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
@ -39,6 +44,8 @@ public class AsyncRegisterTest {
private CommonService commonService;
@Mock
private DataSource dataSource;
@Mock
private SingletonStore<RegistrationExecutor> registrationExecutorStore;
@Test
public void shouldDetectAlreadyLoggedInPlayer() {
@ -47,9 +54,10 @@ public class AsyncRegisterTest {
Player player = mockPlayerWithName(name);
given(playerCache.isAuthenticated(name)).willReturn(true);
RegistrationExecutor executor = mock(RegistrationExecutor.class);
singletonStoreWillReturn(registrationExecutorStore, executor);
// when
asyncRegister.register(player, executor);
asyncRegister.register(RegistrationMethod.PASSWORD_REGISTRATION, PasswordRegisterParams.of(player, "abc", null));
// then
verify(commonService).send(player, MessageKey.ALREADY_LOGGED_IN_ERROR);
@ -64,9 +72,10 @@ public class AsyncRegisterTest {
given(playerCache.isAuthenticated(name)).willReturn(false);
given(commonService.getProperty(RegistrationSettings.IS_ENABLED)).willReturn(false);
RegistrationExecutor executor = mock(RegistrationExecutor.class);
singletonStoreWillReturn(registrationExecutorStore, executor);
// when
asyncRegister.register(player, executor);
asyncRegister.register(RegistrationMethod.TWO_FACTOR_REGISTRATION, TwoFactorRegisterParams.of(player));
// then
verify(commonService).send(player, MessageKey.REGISTRATION_DISABLED);
@ -82,9 +91,10 @@ public class AsyncRegisterTest {
given(commonService.getProperty(RegistrationSettings.IS_ENABLED)).willReturn(true);
given(dataSource.isAuthAvailable(name)).willReturn(true);
RegistrationExecutor executor = mock(RegistrationExecutor.class);
singletonStoreWillReturn(registrationExecutorStore, executor);
// when
asyncRegister.register(player, executor);
asyncRegister.register(RegistrationMethod.TWO_FACTOR_REGISTRATION, TwoFactorRegisterParams.of(player));
// then
verify(commonService).send(player, MessageKey.NAME_ALREADY_REGISTERED);
@ -93,6 +103,7 @@ public class AsyncRegisterTest {
}
@Test
@SuppressWarnings("unchecked")
public void shouldStopForFailedExecutorCheck() {
// given
String name = "edbert";
@ -103,14 +114,16 @@ public class AsyncRegisterTest {
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);
TwoFactorRegisterParams params = TwoFactorRegisterParams.of(player);
given(executor.isRegistrationAdmitted(params)).willReturn(false);
singletonStoreWillReturn(registrationExecutorStore, executor);
// when
asyncRegister.register(player, executor);
asyncRegister.register(RegistrationMethod.TWO_FACTOR_REGISTRATION, params);
// then
verify(dataSource, only()).isAuthAvailable(name);
verify(executor, only()).isRegistrationAdmitted();
verify(executor, only()).isRegistrationAdmitted(params);
}
private static Player mockPlayerWithName(String name) {
@ -118,4 +131,10 @@ public class AsyncRegisterTest {
given(player.getName()).willReturn(name);
return player;
}
@SuppressWarnings("unchecked")
private static void singletonStoreWillReturn(SingletonStore<RegistrationExecutor> store,
RegistrationExecutor mock) {
given(store.getSingleton(any(Class.class))).willReturn(mock);
}
}

View File

@ -1,6 +1,5 @@
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;
@ -34,13 +33,13 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Test for {@link EmailRegisterExecutorProvider}.
* Test for {@link EmailRegisterExecutor}.
*/
@RunWith(MockitoJUnitRunner.class)
public class EmailRegisterExecutorProviderTest {
@InjectMocks
private EmailRegisterExecutorProvider emailRegisterExecutorProvider;
private EmailRegisterExecutor executor;
@Mock
private PermissionsManager permissionsManager;
@ -62,10 +61,10 @@ public class EmailRegisterExecutorProviderTest {
String email = "test@example.com";
given(dataSource.countAuthsByEmail(email)).willReturn(4);
Player player = mock(Player.class);
RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, email);
EmailRegisterParams params = EmailRegisterParams.of(player, email);
// when
boolean result = executor.isRegistrationAdmitted();
boolean result = executor.isRegistrationAdmitted(params);
// then
assertThat(result, equalTo(false));
@ -80,10 +79,10 @@ public class EmailRegisterExecutorProviderTest {
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");
EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com");
// when
boolean result = executor.isRegistrationAdmitted();
boolean result = executor.isRegistrationAdmitted(params);
// then
assertThat(result, equalTo(true));
@ -97,10 +96,10 @@ public class EmailRegisterExecutorProviderTest {
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");
EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com");
// when
boolean result = executor.isRegistrationAdmitted();
boolean result = executor.isRegistrationAdmitted(params);
// then
assertThat(result, equalTo(true));
@ -120,10 +119,10 @@ public class EmailRegisterExecutorProviderTest {
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");
EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com");
// when
PlayerAuth auth = executor.buildPlayerAuth();
PlayerAuth auth = executor.buildPlayerAuth(params);
// then
assertThat(auth, hasAuthBasicData("veronica", "Veronica", "test@example.com", "123.45.67.89"));
@ -132,18 +131,17 @@ public class EmailRegisterExecutorProviderTest {
}
@Test
@SuppressWarnings("unchecked")
public void shouldPerformActionAfterDataSourceSave() {
// given
given(emailService.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");
EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com");
String password = "A892C#@";
ReflectionTestUtils.setField((Class) executor.getClass(), executor, "password", password);
params.setPassword(password);
// when
executor.executePostPersistAction();
executor.executePostPersistAction(params);
// then
verify(emailService).sendPasswordMail("Laleh", "test@example.com", password);
@ -151,18 +149,17 @@ public class EmailRegisterExecutorProviderTest {
}
@Test
@SuppressWarnings("unchecked")
public void shouldHandleEmailSendingFailure() {
// given
given(emailService.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");
EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com");
String password = "A892C#@";
ReflectionTestUtils.setField((Class) executor.getClass(), executor, "password", password);
params.setPassword(password);
// when
executor.executePostPersistAction();
executor.executePostPersistAction(params);
// then
verify(emailService).sendPasswordMail("Laleh", "test@example.com", password);

View File

@ -34,13 +34,13 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Test for {@link PasswordRegisterExecutorProvider}.
* Test for {@link PasswordRegisterExecutor}.
*/
@RunWith(MockitoJUnitRunner.class)
public class PasswordRegisterExecutorProviderTest {
public class PasswordRegisterExecutorTest {
@InjectMocks
private PasswordRegisterExecutorProvider passwordRegisterExecutorProvider;
private PasswordRegisterExecutor executor;
@Mock
private ValidationService validationService;
@ -62,10 +62,10 @@ public class PasswordRegisterExecutorProviderTest {
String name = "player040";
given(validationService.validatePassword(password, name)).willReturn(new ValidationResult());
Player player = mockPlayerWithName(name);
RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, password, null);
PasswordRegisterParams params = PasswordRegisterParams.of(player, password, null);
// when
boolean result = executor.isRegistrationAdmitted();
boolean result = executor.isRegistrationAdmitted(params);
// then
assertThat(result, equalTo(true));
@ -80,10 +80,10 @@ public class PasswordRegisterExecutorProviderTest {
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);
PasswordRegisterParams params = PasswordRegisterParams.of(player, password, null);
// when
boolean result = executor.isRegistrationAdmitted();
boolean result = executor.isRegistrationAdmitted(params);
// then
assertThat(result, equalTo(false));
@ -101,10 +101,10 @@ public class PasswordRegisterExecutorProviderTest {
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");
PasswordRegisterParams params = PasswordRegisterParams.of(player, "pass", "mail@example.org");
// when
PlayerAuth auth = executor.buildPlayerAuth();
PlayerAuth auth = executor.buildPlayerAuth(params);
// then
assertThat(auth, hasAuthBasicData("s1m0n", "S1m0N", "mail@example.org", "123.45.67.89"));
@ -118,10 +118,10 @@ public class PasswordRegisterExecutorProviderTest {
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");
PasswordRegisterParams params = PasswordRegisterParams.of(player, "pass", "mail@example.org");
// when
executor.executePostPersistAction();
executor.executePostPersistAction(params);
// then
TestHelper.runSyncDelayedTaskWithDelay(bukkitService);
@ -134,10 +134,10 @@ public class PasswordRegisterExecutorProviderTest {
// 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");
PasswordRegisterParams params = PasswordRegisterParams.of(player, "pass", "mail@example.org");
// when
executor.executePostPersistAction();
executor.executePostPersistAction(params);
// then
verifyZeroInteractions(bukkitService, asynchronousLogin);