Merge branch 'master' of https://github.com/AuthMe-Team/AuthMeReloaded into 707-process-as-service

Conflicts:
	src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java
	src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java
This commit is contained in:
ljacqu 2016-05-20 19:48:26 +02:00
commit c1885a381b
29 changed files with 489 additions and 386 deletions

14
pom.xml
View File

@ -241,26 +241,16 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.4.0</version>
<version>1.5.0</version>
<configuration>
<classpathScope>test</classpathScope>
<workingDirectory>${project.basedir}/target/test-classes</workingDirectory>
<mainClass>tools.ToolsRunner</mainClass>
<arguments>
<argument>writePermissionsList</argument>
<argument>updateDocs</argument>
</arguments>
<includeProjectDependencies>true</includeProjectDependencies>
</configuration>
<!-- Excluded from the lifecycle, it doesn't work!
<executions>
<execution>
<phase>test</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
-->
</plugin>
<!-- Test coverage -->
<plugin>

View File

@ -0,0 +1,126 @@
<?php
/*****************************************************************************
* AuthMe website integration logic *
* ------------------------------------------------------------------------- *
* Allows interaction with the AuthMe database (registration, password *
* verification). Don't forget to update the AUTHME_TABLE value and your *
* database credentials in getAuthmeMySqli(). *
* *
* Source: https://github.com/AuthMe-Team/AuthMeReloaded/ *
*****************************************************************************/
abstract class AuthMeController {
const AUTHME_TABLE = 'authme';
/**
* Entry point function to check supplied credentials against the AuthMe database.
*
* @param string $username the username
* @param string $password the password
* @return bool true iff the data is correct, false otherwise
*/
function checkPassword($username, $password) {
if (is_scalar($username) && is_scalar($password)) {
$hash = $this->getHashFromDatabase($username);
if ($hash) {
return $this->isValidPassword($password, $hash);
}
}
return false;
}
/**
* Returns whether the user exists in the database or not.
*
* @param string $username the username to check
* @return bool true if the user exists; false otherwise
*/
function isUserRegistered($username) {
$mysqli = $this->getAuthmeMySqli();
if ($mysqli !== null) {
$stmt = $mysqli->prepare('SELECT 1 FROM ' . self::AUTHME_TABLE . ' WHERE username = ?');
$stmt->bind_param('s', $username);
$stmt->execute();
return $stmt->fetch();
}
// Defensive default to true; we actually don't know
return true;
}
/**
* Registers a player with the given username.
*
* @param string $username the username to register
* @param string $password the password to associate to the user
* @return bool whether or not the registration was successful
*/
function register($username, $password) {
$mysqli = $this->getAuthmeMySqli();
if ($mysqli !== null) {
$hash = $this->hash($password);
$stmt = $mysqli->prepare('INSERT INTO ' . self::AUTHME_TABLE . ' (username, realname, password, ip) '
. 'VALUES (?, ?, ?, ?)');
$username_low = strtolower($username);
$stmt->bind_param('ssss', $username, $username_low, $hash, $_SERVER['REMOTE_ADDR']);
return $stmt->execute();
}
return false;
}
/**
* Hashes the given password.
*
* @param $password string the clear-text password to hash
* @return string the resulting hash
*/
protected abstract function hash($password);
/**
* Checks whether the given password matches the hash.
*
* @param $password string the clear-text password
* @param $hash string the password hash
* @return boolean true if the password matches, false otherwise
*/
protected abstract function isValidPassword($password, $hash);
/**
* Returns a connection to the database.
*
* @return mysqli|null the mysqli object or null upon error
*/
private function getAuthmeMySqli() {
// CHANGE YOUR DATABASE DETAILS HERE BELOW: host, user, password, database name
$mysqli = new mysqli('localhost', 'root', '', 'authme');
if (mysqli_connect_error()) {
printf('Could not connect to AuthMe database. Errno: %d, error: "%s"',
mysqli_connect_errno(), mysqli_connect_error());
return null;
}
return $mysqli;
}
/**
* Retrieves the hash associated with the given user from the database.
*
* @param string $username the username whose hash should be retrieved
* @return string|null the hash, or null if unavailable (e.g. username doesn't exist)
*/
private function getHashFromDatabase($username) {
// Add here your database host, username, password and database name
$mysqli = $this->getAuthmeMySqli();
if ($mysqli !== null) {
$stmt = $mysqli->prepare('SELECT password FROM ' . self::AUTHME_TABLE . ' WHERE username = ?');
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->bind_result($password);
if ($stmt->fetch()) {
return $password;
}
}
return null;
}
}

View File

@ -0,0 +1,20 @@
<?php
/***********************************************************
* AuthMe website integration logic for BCrypt *
* ------------------------------------------------------- *
* See AuthMeController for details. *
* *
* Source: https://github.com/AuthMe-Team/AuthMeReloaded/ *
***********************************************************/
class Bcrypt extends AuthMeController {
protected function hash($password) {
return password_hash($password, PASSWORD_BCRYPT);
}
protected function isValidPassword($password, $hash) {
return password_verify($password, $hash);
}
}

View File

@ -0,0 +1,48 @@
<?php
/***********************************************************
* AuthMe website integration logic for SHA256 *
* ------------------------------------------------------- *
* See AuthMeController for details. *
* *
* Source: https://github.com/AuthMe-Team/AuthMeReloaded/ *
***********************************************************/
class Sha256 extends AuthMeController {
/** @var string[] range of characters for salt generation */
private $CHARS;
const SALT_LENGTH = 16;
public function __construct() {
$this->CHARS = self::initRandomChars();
}
protected function isValidPassword($password, $hash) {
// $SHA$salt$hash, where hash := sha256(sha256(password) . salt)
$parts = explode('$', $hash);
return count($parts) === 4 && $parts[3] === hash('sha256', hash('sha256', $password) . $parts[2]);
}
protected function hash($password) {
$salt = $this->generateSalt();
return '$SHA$' . $salt . '$' . hash('sha256', hash('sha256', $password) . $salt);
}
/**
* @return string randomly generated salt
*/
private function generateSalt() {
$maxCharIndex = count($this->CHARS) - 1;
$salt = '';
for ($i = 0; $i < self::SALT_LENGTH; ++$i) {
$salt .= $this->CHARS[mt_rand(0, $maxCharIndex)];
}
return $salt;
}
private static function initRandomChars() {
return array_merge(range('0', '9'), range('a', 'f'));
}
}

View File

@ -1,107 +0,0 @@
<?php
/*****************************************************************************
* AuthMe website integration logic for BCrypt *
* -------------------------------- *
* Check with authme_check_password() whether the received username and *
* password match the AuthMe MySQL database. Don't forget to adjust the *
* database info in authme_get_hash(). *
* *
* Source: https://github.com/AuthMe-Team/AuthMeReloaded/ *
*****************************************************************************/
/** The name of the authme MySQL table. */
define('AUTHME_TABLE', 'authme');
/**
* Entry point function to check supplied credentials against the AuthMe database.
*
* @param string $username the username
* @param string $password the password
* @return bool true iff the data is correct, false otherwise
*/
function authme_check_password($username, $password) {
if (is_scalar($username) && is_scalar($password)) {
$hash = authme_get_hash($username);
if ($hash) {
return password_verify($password, $hash);
}
}
return false;
}
/**
* Returns a connection to the database.
*
* @return mysqli|null the mysqli object or null upon error
*/
function authme_get_mysqli() {
$mysqli = new mysqli('localhost', 'root', '', 'authme');
if (mysqli_connect_error()) {
printf('Could not connect to AuthMe database. Errno: %d, error: "%s"',
mysqli_connect_errno(), mysqli_connect_error());
return null;
}
return $mysqli;
}
/**
* Retrieves the hash associated with the given user from the database.
*
* @param string $username the username whose hash should be retrieved
* @return string|null the hash, or null if unavailable (e.g. username doesn't exist)
*/
function authme_get_hash($username) {
// Add here your database host, username, password and database name
$mysqli = authme_get_mysqli();
if ($mysqli !== null) {
$stmt = $mysqli->prepare('SELECT password FROM ' . AUTHME_TABLE . ' WHERE username = ?');
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->bind_result($password);
if ($stmt->fetch()) {
return $password;
}
}
return null;
}
/**
* Returns whether the user exists in the database or not.
*
* @param string $username the username to check
* @return bool true if the user exists; false otherwise
*/
function authme_has_user($username) {
$mysqli = authme_get_mysqli();
if ($mysqli !== null) {
$stmt = $mysqli->prepare('SELECT 1 FROM ' . AUTHME_TABLE . ' WHERE username = ?');
$stmt->bind_param('s', $username);
$stmt->execute();
return $stmt->fetch();
}
// Defensive default to true; we actually don't know
return true;
}
/**
* Registers a player with the given username.
*
* @param string $username the username to register
* @param string $password the password to associate to the user
* @return bool whether or not the registration was successful
*/
function authme_register($username, $password) {
$mysqli = authme_get_mysqli();
if ($mysqli !== null) {
$hash = password_hash($password, PASSWORD_BCRYPT);
$stmt = $mysqli->prepare('INSERT INTO ' . AUTHME_TABLE . ' (username, realname, password, ip) '
. 'VALUES (?, ?, ?, ?)');
$username_low = strtolower($username);
$stmt->bind_param('ssss', $username, $username_low, $hash, $_SERVER['REMOTE_ADDR']);
return $stmt->execute();
}
return false;
}

View File

@ -1,6 +1,6 @@
<!--
This is a demo page for AuthMe website integration with BCrypt.
See integration.php for the PHP code you need.
This is a demo page for AuthMe website integration.
See AuthMeController.php and the extending classes for the PHP code you need.
-->
<!DOCTYPE html>
<html lang="en">
@ -12,17 +12,23 @@
<?php
error_reporting(E_ALL);
require 'AuthMeController.php';
// Change this to the file of the hash encryption you need, e.g. Bcrypt.php or Sha256.php
require 'Sha256.php';
// The class name must correspond to the file you have in require above! e.g. require 'Sha256.php'; and new Sha256();
$authme_controller = new Sha256();
$action = get_from_post_or_empty('action');
$user = get_from_post_or_empty('username');
$pass = get_from_post_or_empty('password');
$was_successful = false;
if ($action && $user && $pass) {
require_once('integration.php');
if ($action === 'Log in') {
$was_successful = process_login($user, $pass);
$was_successful = process_login($user, $pass, $authme_controller);
} else if ($action === 'Register') {
$was_successful = process_register($user, $pass);
$was_successful = process_register($user, $pass, $authme_controller);
}
}
@ -50,11 +56,11 @@ function get_from_post_or_empty($index_name) {
// Login logic
function process_login($user, $pass) {
if (authme_check_password($user, $pass)) {
function process_login($user, $pass, AuthMeController $controller) {
if ($controller->checkPassword($user, $pass)) {
printf('<h1>Hello, %s!</h1>', htmlspecialchars($user));
echo 'Successful login. Nice to have you back!'
. '<br /><a href="form.php">Back to form</a>';
. '<br /><a href="index.php">Back to form</a>';
return true;
} else {
echo '<h1>Error</h1> Invalid username or password.';
@ -63,15 +69,15 @@ function process_login($user, $pass) {
}
// Register logic
function process_register($user, $pass) {
if (authme_has_user($user)) {
function process_register($user, $pass, AuthMeController $controller) {
if ($controller->isUserRegistered($user)) {
echo '<h1>Error</h1> This user already exists.';
} else {
// Note that we don't validate the password or username at all in this demo...
$register_success = authme_register($user, $pass);
$register_success = $controller->register($user, $pass);
if ($register_success) {
printf('<h1>Welcome, %s!</h1>Thanks for registering', htmlspecialchars($user));
echo '<br /><a href="form.php">Back to form</a>';
echo '<br /><a href="index.php">Back to form</a>';
return true;
} else {
echo '<h1>Error</h1>Unfortunately, there was an error during the registration.';

View File

@ -1,52 +0,0 @@
<!--
This is a demo page for AuthMe website integration with SHA256.
See integration.php for the PHP code you need.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<title>AuthMe Integration Sample</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<?php
error_reporting(E_ALL);
$user = get_from_post_or_empty('username');
$pass = get_from_post_or_empty('password');
$was_successful = false;
if ($user && $pass) {
require_once('integration.php');
if (authme_check_password($user, $pass)) {
printf('<h1>Hello, %s!</h1>', htmlspecialchars($user));
echo 'Successful login. Nice to have you back!'
. '<br /><a href="form.php">Back to form</a>';
$was_successful = true;
} else {
echo '<h1>Error</h1> Invalid username or password.';
}
}
if (!$was_successful) {
echo '<h1>Login sample</h1>
This is a demo form for AuthMe website integration. Enter your AuthMe login details
into the following form to test it.
<form method="post">
<table>
<tr><td>Name</td><td><input type="text" value="' . htmlspecialchars($user) . '" name="username" /></td></tr>
<tr><td>Pass</td><td><input type="password" value="' . htmlspecialchars($pass) . '" name="password" /></td></tr>
<tr><td colspan="2"><input type="submit" value=" Log in " /></td></tr>
</table>
</form>';
}
function get_from_post_or_empty($index_name) {
return trim(
filter_input(INPUT_POST, $index_name, FILTER_UNSAFE_RAW, FILTER_REQUIRE_SCALAR | FILTER_FLAG_STRIP_LOW)
?: '');
}
?>
</body>
</html>

View File

@ -1,67 +0,0 @@
<?php
/*****************************************************************************
* AuthMe website integration logic for SHA256 *
* -------------------------------- *
* Check with authme_check_password() whether the received username and *
* password match the AuthMe MySQL database. Don't forget to adjust the *
* database info in authme_get_hash(). *
* *
* Source: https://github.com/AuthMe-Team/AuthMeReloaded/ *
*****************************************************************************/
/**
* Entry point function to check supplied credentials against the AuthMe database.
*
* @param string $username the username
* @param string $password the password
* @return bool true iff the data is correct, false otherwise
*/
function authme_check_password($username, $password) {
if (is_scalar($username) && is_scalar($password)) {
$hash = authme_get_hash($username);
if ($hash) {
return authme_check_hash($password, $hash);
}
}
return false;
}
/**
* Retrieves the hash associated with the given user from the database.
*
* @param string $username the username whose hash should be retrieved
* @return string|null the hash, or null if unavailable (e.g. username doesn't exist)
*/
function authme_get_hash($username) {
// Add here your database host, username, password and database name
$mysqli = new mysqli('HOST', 'USER', 'PWD', 'DB');
$authme_table = 'authme';
if (mysqli_connect_error()) {
printf('Could not connect to AuthMe database. Errno: %d, error: "%s"',
mysqli_connect_errno(), mysqli_connect_error());
} else {
$stmt = $mysqli->prepare("SELECT password FROM $authme_table WHERE username = ?");
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->bind_result($password);
if ($stmt->fetch()) {
return $password;
}
}
return null;
}
/**
* Checks the given clear-text password against the hash.
*
* @param string $password the clear-text password to check
* @param string $hash the hash to check the password against
* @return bool true iff the password matches the hash, false otherwise
*/
function authme_check_hash($password, $hash) {
// $SHA$salt$hash, where hash := sha256(sha256(password) . salt)
$parts = explode('$', $hash);
return count($parts) === 4
&& $parts[3] === hash('sha256', hash('sha256', $password) . $parts[2]);
}

View File

@ -44,16 +44,10 @@ import fr.xephi.authme.settings.SettingsMigrationService;
import fr.xephi.authme.settings.SpawnLoader;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.settings.properties.EmailSettings;
import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_ACCOUNT;
import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD;
import static fr.xephi.authme.settings.properties.EmailSettings.RECALL_PLAYERS;
import fr.xephi.authme.settings.properties.HooksSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.PurgeSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.settings.properties.SettingsFieldRetriever;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import fr.xephi.authme.task.PurgeTask;
@ -64,6 +58,17 @@ import fr.xephi.authme.util.GeoLiteAPI;
import fr.xephi.authme.util.MigrationService;
import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Utils;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import java.io.File;
import java.sql.SQLException;
@ -76,17 +81,9 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_ACCOUNT;
import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD;
import static fr.xephi.authme.settings.properties.EmailSettings.RECALL_PLAYERS;
/**
* The AuthMe main class.
@ -223,8 +220,8 @@ public class AuthMe extends JavaPlugin {
getServer().shutdown();
return;
}
ConsoleLogger.setLoggingOptions(newSettings.getProperty(SecuritySettings.USE_LOGGING),
new File(getDataFolder(), "authme.log"));
ConsoleLogger.setLogFile(new File(getDataFolder(), "authme.log"));
ConsoleLogger.setLoggingOptions(newSettings);
// Old settings manager
if (!loadSettings()) {

View File

@ -1,6 +1,7 @@
package fr.xephi.authme;
import com.google.common.base.Throwables;
import fr.xephi.authme.settings.NewSetting;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.StringUtils;
@ -21,22 +22,26 @@ public final class ConsoleLogger {
private static final String NEW_LINE = System.getProperty("line.separator");
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("[MM-dd HH:mm:ss]");
private static Logger logger;
private static boolean enableDebug = false;
private static boolean useLogging = false;
private static File logFile;
private ConsoleLogger() {
// Service class
}
public static void setLogger(Logger logger) {
ConsoleLogger.logger = logger;
}
public static void setLoggingOptions(boolean useLogging, File logFile) {
ConsoleLogger.useLogging = useLogging;
public static void setLogFile(File logFile) {
ConsoleLogger.logFile = logFile;
}
public static void setLoggingOptions(NewSetting settings) {
ConsoleLogger.useLogging = settings.getProperty(SecuritySettings.USE_LOGGING);
ConsoleLogger.enableDebug = !settings.getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE);
}
/**
* Print an info message.
*
@ -50,7 +55,7 @@ public final class ConsoleLogger {
}
public static void debug(String message) {
if (!AuthMe.getInstance().getSettings().getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE)) {
if (enableDebug) {
logger.fine(message);
if (useLogging) {
writeLog("Debug: " + message);

View File

@ -4,9 +4,7 @@ import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.hooks.PluginHooks;
import fr.xephi.authme.task.PurgeTask;
import fr.xephi.authme.util.BukkitService;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
@ -26,12 +24,6 @@ public class PurgeCommand implements ExecutableCommand {
@Inject
private DataSource dataSource;
@Inject
private PluginHooks pluginHooks;
@Inject
private BukkitService bukkitService;
@Inject
private AuthMe plugin;

View File

@ -40,6 +40,7 @@ public class ReloadCommand implements ExecutableCommand {
ConsoleLogger.info("Note: cannot change database type during /authme reload");
sender.sendMessage("Note: cannot change database type during /authme reload");
}
ConsoleLogger.setLoggingOptions(settings);
initializer.performReloadOnServices();
commandService.send(sender, MessageKey.CONFIG_RELOAD_SUCCESS);
} catch (Exception e) {

View File

@ -144,8 +144,8 @@ public class AuthMeServiceInitializer {
}
/**
* Instantiates the given class by locating an @Inject constructor and retrieving
* or instantiating its parameters.
* Instantiates the given class by locating its @Inject elements and retrieving
* or instantiating the required instances.
*
* @param clazz the class to instantiate
* @param traversedClasses collection of classes already traversed
@ -164,13 +164,13 @@ public class AuthMeServiceInitializer {
validateInjectionHasNoCircularDependencies(injection.getDependencies(), traversedClasses);
Object[] dependencies = resolveDependencies(injection, traversedClasses);
T object = injection.instantiateWith(dependencies);
executePostConstructMethods(object);
executePostConstructMethod(object);
return object;
}
/**
* Resolves the dependencies for the given constructor, i.e. returns a collection that satisfy
* the constructor's parameter types by retrieving elements or instantiating them where necessary.
* Resolves the dependencies for the given class instantiation, i.e. returns a collection that satisfy
* the class' dependencies by retrieving elements or instantiating them where necessary.
*
* @param injection the injection parameters
* @param traversedClasses collection of traversed classes
@ -247,21 +247,20 @@ public class AuthMeServiceInitializer {
+ "allowed packages. It must be provided explicitly or the package must be passed to the constructor.");
}
private static void executePostConstructMethods(Object object) {
for (Method method : object.getClass().getDeclaredMethods()) {
if (method.isAnnotationPresent(PostConstruct.class)) {
if (method.getParameterTypes().length == 0 && !Modifier.isStatic(method.getModifiers())) {
try {
method.setAccessible(true);
method.invoke(object);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new UnsupportedOperationException(e);
}
} else {
throw new IllegalStateException(String.format("@PostConstruct methods may not be static or have "
+ " any parameters. Method '%s' of class '%s' is either static or has parameters",
method.getName(), object.getClass().getSimpleName()));
}
/**
* Executes an object's method annotated with {@link PostConstruct} if present.
* Throws an exception if there are multiple such methods, or if the method is static.
*
* @param object the object to execute the post construct method for
*/
private static void executePostConstructMethod(Object object) {
Method postConstructMethod = getAndValidatePostConstructMethod(object.getClass());
if (postConstructMethod != null) {
try {
postConstructMethod.setAccessible(true);
postConstructMethod.invoke(object);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new UnsupportedOperationException("Error executing @PostConstruct method", e);
}
}
}
@ -272,6 +271,32 @@ public class AuthMeServiceInitializer {
}
}
/**
* Validate and locate the given class' post construct method. Returns {@code null} if none present.
*
* @param clazz the class to search
* @return post construct method, or null
*/
private static Method getAndValidatePostConstructMethod(Class<?> clazz) {
Method postConstructMethod = null;
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(PostConstruct.class)) {
if (postConstructMethod != null) {
throw new IllegalStateException("Multiple methods with @PostConstruct on " + clazz);
} else if (method.getParameterTypes().length > 0 || Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@PostConstruct method may not be static or have any parameters. "
+ "Invalid method in " + clazz);
} else if (method.getReturnType() != void.class) {
throw new IllegalStateException("@PostConstruct method must have return type void. "
+ "Offending class: " + clazz);
} else {
postConstructMethod = method;
}
}
}
return postConstructMethod;
}
@SafeVarargs
private static <T> Injection<T> firstNotNull(Provider<? extends Injection<T>>... providers) {
for (Provider<? extends Injection<T>> provider : providers) {

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.initialization;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Provider;
import java.lang.reflect.AccessibleObject;
@ -8,7 +9,7 @@ import java.lang.reflect.InvocationTargetException;
/**
* Fallback instantiation method for classes with an accessible no-args constructor
* and no no {@link Inject} annotations whatsoever.
* and no elements whatsoever annotated with {@link Inject} or {@link PostConstruct}.
*/
public class InstantiationFallback<T> implements Injection<T> {
@ -54,9 +55,9 @@ public class InstantiationFallback<T> implements Injection<T> {
Constructor<T> noArgsConstructor = getNoArgsConstructor(clazz);
// Return fallback only if we have no args constructor and no @Inject annotation anywhere
if (noArgsConstructor != null
&& !isInjectAnnotationPresent(clazz.getDeclaredConstructors())
&& !isInjectAnnotationPresent(clazz.getDeclaredFields())
&& !isInjectAnnotationPresent(clazz.getDeclaredMethods())) {
&& !isInjectionAnnotationPresent(clazz.getDeclaredConstructors())
&& !isInjectionAnnotationPresent(clazz.getDeclaredFields())
&& !isInjectionAnnotationPresent(clazz.getDeclaredMethods())) {
return new InstantiationFallback<>(noArgsConstructor);
}
return null;
@ -73,9 +74,9 @@ public class InstantiationFallback<T> implements Injection<T> {
}
}
private static <A extends AccessibleObject> boolean isInjectAnnotationPresent(A[] accessibles) {
private static <A extends AccessibleObject> boolean isInjectionAnnotationPresent(A[] accessibles) {
for (A accessible : accessibles) {
if (accessible.isAnnotationPresent(Inject.class)) {
if (accessible.isAnnotationPresent(Inject.class) || accessible.isAnnotationPresent(PostConstruct.class)) {
return true;
}
}

View File

@ -17,7 +17,6 @@ import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.permission.PlayerStatePermission;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.settings.NewSetting;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.SpawnLoader;
import fr.xephi.authme.settings.properties.HooksSettings;
import fr.xephi.authme.settings.properties.ProtectionSettings;
@ -57,6 +56,7 @@ import org.bukkit.event.player.PlayerShearEntityEvent;
import javax.inject.Inject;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import static fr.xephi.authme.listener.ListenerService.shouldCancelEvent;
import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_MOVEMENT_RADIUS;
@ -354,14 +354,16 @@ public class AuthMePlayerListener implements Listener {
return;
}
if (name.length() > Settings.getMaxNickLength || name.length() < Settings.getMinNickLength) {
if (name.length() > settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH) || name.length() < settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)) {
event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_LENGTH));
event.setResult(PlayerLoginEvent.Result.KICK_OTHER);
return;
}
if (!Settings.nickPattern.matcher(player.getName()).matches() || name.equalsIgnoreCase("Player")) {
event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_CHARACTERS).replace("REG_EX", Settings.getNickRegex));
String nickRegEx = settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS);
Pattern nickPattern = Pattern.compile(nickRegEx);
if (nickPattern.matcher(player.getName()).matches() || name.equalsIgnoreCase("Player")) {
event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_CHARACTERS).replace("REG_EX", nickRegEx));
event.setResult(PlayerLoginEvent.Result.KICK_OTHER);
return;
}
@ -505,14 +507,16 @@ public class AuthMePlayerListener implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onPlayerRespawn(PlayerRespawnEvent event) {
if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) {
return;
}
if (!shouldCancelEvent(event)) {
return;
}
Player player = event.getPlayer();
String name = player.getName().toLowerCase();
Location spawn = spawnLoader.getSpawnLocation(player);
if (Settings.isSaveQuitLocationEnabled && dataSource.isAuthAvailable(name)) {
if (settings.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION) && dataSource.isAuthAvailable(name)) {
PlayerAuth auth = PlayerAuth.builder()
.name(name)
.realName(player.getName())

View File

@ -1,6 +1,9 @@
package fr.xephi.authme.listener;
import fr.xephi.authme.settings.NewSetting;
import fr.xephi.authme.settings.SpawnLoader;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@ -16,11 +19,20 @@ public class AuthMePlayerListener19 implements Listener {
@Inject
private SpawnLoader spawnLoader;
AuthMePlayerListener19() { }
@Inject
private NewSetting settings;
/* WTF was that? We need to check all the settings before moving the player to the spawn!
*
* TODO: fixme please!
*
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerSpawn(PlayerSpawnLocationEvent event) {
if(settings.getProperty(RestrictionSettings.NO_TELEPORT)) {
return;
}
event.setSpawnLocation(spawnLoader.getSpawnLocation(event.getPlayer()));
}
*/
}

View File

@ -96,11 +96,9 @@ public class PermissionsManager implements PermissionsService {
/**
* Setup and hook into the permissions systems.
*
* @return The detected permissions system.
*/
@PostConstruct
public PermissionsSystemType setup() {
public void setup() {
// Force-unhook from current hooked permissions systems
unhook();
@ -177,7 +175,7 @@ public class PermissionsManager implements PermissionsService {
ConsoleLogger.info("Hooked into " + type.getName() + "!");
// Return the used permissions system type
return type;
return;
} catch (Exception ex) {
// An error occurred, show a warning message
@ -187,7 +185,6 @@ public class PermissionsManager implements PermissionsService {
// No recognized permissions system found, show a message and return
ConsoleLogger.info("No supported permissions system found! Permissions are disabled!");
return null;
}
/**

View File

@ -230,9 +230,9 @@ public class AsynchronousJoin implements AsynchronousProcess {
: MessageKey.REGISTER_MESSAGE;
}
if (msgInterval > 0 && limboCache.getLimboPlayer(name) != null) {
BukkitTask msgTask = service.runTask(new MessageTask(service.getBukkitService(), plugin.getMessages(),
name, msg, msgInterval));
LimboPlayer limboPlayer = limboCache.getLimboPlayer(name);
BukkitTask msgTask = service.runTaskLater(new MessageTask(service.getBukkitService(), plugin.getMessages(),
name, msg, msgInterval), 20L);
LimboPlayer limboPlayer = limboCache.getLimboPlayer(name);
if (limboPlayer != null) {
limboPlayer.setMessageTask(msgTask);
}

View File

@ -1,7 +1,6 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
@ -14,13 +13,13 @@ import javax.inject.Inject;
@Recommendation(Usage.RECOMMENDED) // provided the salt length is >= 8
@HasSalt(value = SaltType.TEXT) // length depends on the bcryptLog2Rounds setting
public class BCRYPT implements EncryptionMethod, SettingsDependent {
public class BCRYPT implements EncryptionMethod {
private int bCryptLog2Rounds;
private final int bCryptLog2Rounds;
@Inject
public BCRYPT(NewSetting settings) {
loadSettings(settings);
bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND);
}
@Override
@ -54,8 +53,4 @@ public class BCRYPT implements EncryptionMethod, SettingsDependent {
return false;
}
@Override
public void loadSettings(NewSetting settings) {
bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND);
}
}

View File

@ -1,6 +1,5 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.security.RandomString;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
@ -15,13 +14,13 @@ import static fr.xephi.authme.security.HashUtils.md5;
@Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8)
@HasSalt(value = SaltType.TEXT) // length defined by the doubleMd5SaltLength setting
public class SALTED2MD5 extends SeparateSaltMethod implements SettingsDependent {
public class SALTED2MD5 extends SeparateSaltMethod {
private int saltLength;
private final int saltLength;
@Inject
public SALTED2MD5(NewSetting settings) {
loadSettings(settings);
saltLength = settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH);
}
@Override
@ -34,9 +33,5 @@ public class SALTED2MD5 extends SeparateSaltMethod implements SettingsDependent
return RandomString.generateHex(saltLength);
}
@Override
public void loadSettings(NewSetting settings) {
saltLength = settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH);
}
}

View File

@ -9,7 +9,9 @@ import fr.xephi.authme.initialization.AuthMeServiceInitializer;
import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.settings.NewSetting;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import org.bukkit.command.CommandSender;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -53,6 +55,13 @@ public class ReloadCommandTest {
TestHelper.setupLogger();
}
@Before
public void setDefaultSettings() {
// Mock properties retrieved by ConsoleLogger
given(settings.getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE)).willReturn(false);
given(settings.getProperty(SecuritySettings.USE_LOGGING)).willReturn(false);
}
@Test
public void shouldReload() {
// given

View File

@ -204,10 +204,11 @@ public abstract class AbstractResourceClosingTest {
}
/**
* Return a list with some test elements that correspond to the given list type's generic type.
* Return a collection of the required type with some test elements that correspond to the
* collection's generic type.
*
* @param type The list type to process and build a test list for
* @return Test list with sample elements of the correct type
* @param type The collection type to process and build a test collection for
* @return Test collection with sample elements of the correct type
*/
private static Collection<?> getTypedCollection(Type type) {
if (type instanceof ParameterizedType) {

View File

@ -18,8 +18,11 @@ import fr.xephi.authme.initialization.samples.ProvidedClass;
import fr.xephi.authme.initialization.samples.Size;
import fr.xephi.authme.settings.NewSetting;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
@ -36,6 +39,11 @@ public class AuthMeServiceInitializerTest {
private AuthMeServiceInitializer initializer;
// As we test many cases that throw exceptions, we use JUnit's ExpectedException Rule
// to make sure that we receive the exception we expect
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Before
public void setInitializer() {
initializer = new AuthMeServiceInitializer(ALLOWED_PACKAGE);
@ -54,15 +62,17 @@ public class AuthMeServiceInitializerTest {
}
}
@Test(expected = RuntimeException.class)
@Test
public void shouldThrowForInvalidPackage() {
// given / when / then
expectRuntimeExceptionWith("outside of the allowed packages");
initializer.get(InvalidClass.class);
}
@Test(expected = RuntimeException.class)
@Test
public void shouldThrowForUnregisteredPrimitiveType() {
// given / when / then
expectRuntimeExceptionWith("Primitive types must be provided");
initializer.get(int.class);
}
@ -85,24 +95,27 @@ public class AuthMeServiceInitializerTest {
assertThat(object.getGammaService(), equalTo(initializer.get(BetaManager.class).getDependencies()[1]));
}
@Test(expected = RuntimeException.class)
@Test
public void shouldRecognizeCircularReferences() {
// given / when / then
expectRuntimeExceptionWith("Found cyclic dependency");
initializer.get(CircularClasses.Circular3.class);
}
@Test(expected = RuntimeException.class)
@Test
public void shouldThrowForUnregisteredAnnotation() {
// given
initializer.provide(Size.class, 4523);
// when / then
expectRuntimeExceptionWith("must be registered beforehand");
initializer.get(ClassWithAnnotations.class);
}
@Test(expected = RuntimeException.class)
public void shouldThrowForFieldInjectionWithNoDefaultConstructor() {
@Test
public void shouldThrowForFieldInjectionWithoutNoArgsConstructor() {
// given / when / then
expectRuntimeExceptionWith("Did not find injection method");
initializer.get(BadFieldInjection.class);
}
@ -124,36 +137,41 @@ public class AuthMeServiceInitializerTest {
equalTo(result.getBetaManager().getDependencies()[1]));
}
@Test(expected = RuntimeException.class)
@Test
public void shouldThrowForAnnotationAsKey() {
// given / when / then
expectRuntimeExceptionWith("Cannot retrieve annotated elements in this way");
initializer.get(Size.class);
}
@Test(expected = RuntimeException.class)
@Test
public void shouldThrowForSecondRegistration() {
// given / when / then
expectRuntimeExceptionWith("There is already an object present");
initializer.register(ProvidedClass.class, new ProvidedClass(""));
}
@Test(expected = RuntimeException.class)
@Test
public void shouldThrowForSecondAnnotationRegistration() {
// given
initializer.provide(Size.class, 12);
// when / then
expectRuntimeExceptionWith("already registered");
initializer.provide(Size.class, -8);
}
@Test(expected = NullPointerException.class)
@Test
public void shouldThrowForNullValueAssociatedToAnnotation() {
// given / when / then
expectedException.expect(NullPointerException.class);
initializer.provide(Duration.class, null);
}
@Test(expected = NullPointerException.class)
@Test
public void shouldThrowForRegisterWithNull() {
// given / when / then
expectedException.expect(NullPointerException.class);
initializer.register(String.class, null);
}
@ -166,31 +184,49 @@ public class AuthMeServiceInitializerTest {
PostConstructTestClass testClass = initializer.get(PostConstructTestClass.class);
// then
assertThat(testClass.werePostConstructsCalled(), equalTo(true));
assertThat(testClass.wasPostConstructCalled(), equalTo(true));
assertThat(testClass.getBetaManager(), not(nullValue()));
}
@Test(expected = RuntimeException.class)
@Test
public void shouldThrowForInvalidPostConstructMethod() {
// given / when / then
expectRuntimeExceptionWith("@PostConstruct method may not be static or have any parameters");
initializer.get(InvalidPostConstruct.WithParams.class);
}
@Test(expected = RuntimeException.class)
@Test
public void shouldThrowForStaticPostConstructMethod() {
// given / when / then
expectRuntimeExceptionWith("@PostConstruct method may not be static or have any parameters");
initializer.get(InvalidPostConstruct.Static.class);
}
@Test(expected = RuntimeException.class)
@Test
public void shouldForwardExceptionFromPostConstruct() {
// given / when / then
expectRuntimeExceptionWith("Error executing @PostConstruct method");
initializer.get(InvalidPostConstruct.ThrowsException.class);
}
@Test(expected = RuntimeException.class)
@Test
public void shouldThrowForMultiplePostConstructMethods() {
// given / when / then
expectRuntimeExceptionWith("Multiple methods with @PostConstruct");
initializer.get(InvalidPostConstruct.MultiplePostConstructs.class);
}
@Test
public void shouldThrowForPostConstructNotReturningVoid() {
// given / when / then
expectRuntimeExceptionWith("@PostConstruct method must have return type void");
initializer.get(InvalidPostConstruct.NotVoidReturnType.class);
}
@Test
public void shouldThrowForAbstractNonRegisteredDependency() {
// given / when / then
expectRuntimeExceptionWith("cannot be instantiated");
initializer.get(ClassWithAbstractDependency.class);
}
@ -208,12 +244,13 @@ public class AuthMeServiceInitializerTest {
assertThat(cwad.getAlphaService(), not(nullValue()));
}
@Test(expected = RuntimeException.class)
@Test
public void shouldThrowForAlreadyRegisteredClass() {
// given
initializer.register(BetaManager.class, new BetaManager());
// when / then
expectRuntimeExceptionWith("There is already an object present");
initializer.register(BetaManager.class, new BetaManager());
}
@ -229,9 +266,10 @@ public class AuthMeServiceInitializerTest {
assertThat(singletonScoped, not(sameInstance(requestScoped)));
}
@Test(expected = RuntimeException.class)
@Test
public void shouldThrowForStaticFieldInjection() {
// given / when / then
expectRuntimeExceptionWith("is static but annotated with @Inject");
initializer.newInstance(InvalidStaticFieldInjection.class);
}
@ -271,10 +309,16 @@ public class AuthMeServiceInitializerTest {
assertThat(providedClass.getWasReloaded(), equalTo(true));
}
@Test(expected = RuntimeException.class)
@Test
public void shouldThrowForNullSetting() {
// given / when / then
expectRuntimeExceptionWith("Settings instance is null");
initializer.performReloadOnServices();
}
private void expectRuntimeExceptionWith(String message) {
expectedException.expect(RuntimeException.class);
expectedException.expectMessage(containsString(message));
}
}

View File

@ -115,11 +115,9 @@ public class FieldInjectionTest {
}
private static class ThrowingConstructor {
@SuppressWarnings("unused")
@Inject
private ProvidedClass providedClass;
@SuppressWarnings("unused")
public ThrowingConstructor() {
throw new UnsupportedOperationException("Exception in constructor");
}

View File

@ -65,4 +65,14 @@ public class InstantiationFallbackTest {
assertThat(instantiation, nullValue());
}
@Test
public void shouldReturnNullForClassWithPostConstruct() {
// given / when
Injection<InstantiationFallbackClasses.ClassWithPostConstruct> instantiation =
InstantiationFallback.provide(InstantiationFallbackClasses.ClassWithPostConstruct.class).get();
// then
assertThat(instantiation, nullValue());
}
}

View File

@ -1,9 +1,10 @@
package fr.xephi.authme.initialization.samples;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
/**
* Sample class - triggers instantiation fallback.
* Sample class - tests various situations for the instantiation fallback.
*/
public abstract class InstantiationFallbackClasses {
@ -42,4 +43,12 @@ public abstract class InstantiationFallbackClasses {
}
}
// Class with @PostConstruct method should never be instantiated by instantiation fallback
public static final class ClassWithPostConstruct {
@PostConstruct
public void postConstructMethod() {
// --
}
}
}

View File

@ -9,10 +9,8 @@ import javax.inject.Inject;
public abstract class InvalidPostConstruct {
public static final class WithParams {
@SuppressWarnings("unused")
@Inject
private AlphaService alphaService;
@SuppressWarnings("unused")
@Inject
private ProvidedClass providedClass;
@ -36,9 +34,36 @@ public abstract class InvalidPostConstruct {
}
public static final class ThrowsException {
@Inject
private ProvidedClass providedClass;
@PostConstruct
public void throwingPostConstruct() {
throw new IllegalStateException("Exception in post construct");
}
}
public static final class NotVoidReturnType {
@Inject
private ProvidedClass providedClass;
@PostConstruct
public int returnsInt() {
return 42;
}
}
public static final class MultiplePostConstructs {
@Inject
private ProvidedClass providedClass;
@PostConstruct
public void postConstruct1() {
// --
}
@PostConstruct
public void postConstruct2() {
// --
}
}
}

View File

@ -17,22 +17,15 @@ public class PostConstructTestClass implements SettingsDependent {
@Inject
private BetaManager betaManager;
private boolean wasPostConstructCalled = false;
private boolean wasSecondPostConstructCalled = false;
private boolean wasReloaded = false;
@PostConstruct
protected void setFieldToTrue() {
public void postConstructMethod() {
wasPostConstructCalled = true;
}
@PostConstruct
public int otherPostConstructMethod() {
wasSecondPostConstructCalled = true;
return 42;
}
public boolean werePostConstructsCalled() {
return wasPostConstructCalled && wasSecondPostConstructCalled;
public boolean wasPostConstructCalled() {
return wasPostConstructCalled;
}
public BetaManager getBetaManager() {

View File

@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableSet;
import tools.commands.CommandPageCreater;
import tools.hashmethods.HashAlgorithmsDescriptionTask;
import tools.permissions.PermissionsListWriter;
import tools.utils.AutoToolTask;
import tools.utils.ToolTask;
import java.util.Scanner;
@ -12,9 +13,9 @@ import java.util.Set;
/**
* Task that runs all tasks which update files in the docs folder.
*/
public class UpdateDocsTask implements ToolTask {
public class UpdateDocsTask implements AutoToolTask {
private final Set<Class<? extends ToolTask>> TASKS = ImmutableSet.<Class<? extends ToolTask>>of(
private static final Set<Class<? extends ToolTask>> TASKS = ImmutableSet.<Class<? extends ToolTask>>of(
CommandPageCreater.class, HashAlgorithmsDescriptionTask.class, PermissionsListWriter.class);
@Override
@ -23,17 +24,25 @@ public class UpdateDocsTask implements ToolTask {
}
@Override
public void execute(Scanner scanner) {
for (Class<? extends ToolTask> taskClass : TASKS) {
try {
ToolTask task = instantiateTask(taskClass);
System.out.println("\nRunning " + task.getTaskName() + "\n-------------------");
public void execute(final Scanner scanner) {
executeTasks(new TaskRunner() {
@Override
public void execute(ToolTask task) {
task.execute(scanner);
} catch (UnsupportedOperationException e) {
System.err.println("Error running task of class '" + taskClass + "'");
e.printStackTrace();
}
}
});
}
@Override
public void executeDefault() {
executeTasks(new TaskRunner() {
@Override
public void execute(ToolTask task) {
if (task instanceof AutoToolTask) {
((AutoToolTask) task).executeDefault();
}
}
});
}
private static ToolTask instantiateTask(Class<? extends ToolTask> clazz) {
@ -43,4 +52,21 @@ public class UpdateDocsTask implements ToolTask {
throw new UnsupportedOperationException(e);
}
}
private static void executeTasks(TaskRunner runner) {
for (Class<? extends ToolTask> taskClass : TASKS) {
try {
ToolTask task = instantiateTask(taskClass);
System.out.println("\nRunning " + task.getTaskName() + "\n-------------------");
runner.execute(task);
} catch (UnsupportedOperationException e) {
System.err.println("Error running task of class '" + taskClass + "'");
e.printStackTrace();
}
}
}
private interface TaskRunner {
void execute(ToolTask task);
}
}