Merge pull request #1431 from AuthMe/5.5-dev

5.5.0 [WIP]
This commit is contained in:
Gabriele C 2017-12-01 09:58:40 +01:00 committed by GitHub
commit c150adb665
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 494 additions and 237 deletions

View File

@ -1,8 +1,8 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Tue Oct 31 15:56:59 CET 2017. See docs/config/config.tpl.md -->
<!-- File auto-generated on Tue Nov 28 12:49:57 CET 2017. See docs/config/config.tpl.md -->
## AuthMe Configuration
The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder,
The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder,
with which you can configure various settings. This following is the initial contents of
the generated config.yml file.
@ -131,7 +131,7 @@ settings:
# Hide the chat log from players who are not authenticated?
hideChat: false
# Allowed commands for unauthenticated players
allowCommands:
allowCommands:
- '/login'
- '/register'
- '/l'
@ -158,7 +158,7 @@ settings:
enabled: false
# WorldNames where we need to force the spawn location
# Case-sensitive!
worlds:
worlds:
- 'world'
- 'world_nether'
- 'world_the_end'
@ -202,8 +202,8 @@ settings:
# Should we display all other accounts from a player when he joins?
# permission: /authme.admin.accounts
displayOtherAccounts: true
# Spawn priority; values: authme, essentials, multiverse, default
spawnPriority: 'authme,essentials,multiverse,default'
# Spawn priority; values: authme, essentials, cmi, multiverse, default
spawnPriority: 'authme,essentials,cmi,multiverse,default'
# Maximum Login authorized by IP
maxLoginPerIp: 0
# Maximum Join authorized by IP
@ -258,7 +258,7 @@ settings:
# - '123456'
# - 'password'
# - 'help'
unsafePasswords:
unsafePasswords:
- '123456'
- 'password'
- 'qwerty'
@ -364,7 +364,7 @@ Email:
# Delay in minute for the recall scheduler
delayRecall: 5
# Blacklist these domains for emails
emailBlacklisted:
emailBlacklisted:
- '10minutemail.com'
# Whitelist ONLY these domains for emails
emailWhitelisted: []
@ -391,12 +391,12 @@ Protection:
# Countries allowed to join the server and register. For country codes, see
# http://dev.maxmind.com/geoip/legacy/codes/iso3166/
# PLEASE USE QUOTES!
countries:
countries:
- 'US'
- 'GB'
# Countries not allowed to join the server and register
# PLEASE USE QUOTES!
countriesBlacklist:
countriesBlacklist:
- 'A1'
# Do we need to enable automatic antibot system?
enableAntiBot: true
@ -555,9 +555,9 @@ Converter:
password: ''
```
To change settings on a running server, save your changes to config.yml and use
To change settings on a running server, save your changes to config.yml and use
`/authme reload`.
---
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Tue Oct 31 15:56:59 CET 2017
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Tue Nov 28 12:49:57 CET 2017

View File

@ -6,7 +6,7 @@
<groupId>fr.xephi</groupId>
<artifactId>authme</artifactId>
<version>5.4.0</version>
<version>5.5.0-SNAPSHOT</version>
<name>AuthMeReloaded</name>
<description>The first authentication plugin for the Bukkit API!</description>
@ -302,7 +302,7 @@
<dependency>
<groupId>ch.jalu</groupId>
<artifactId>injector</artifactId>
<version>0.4.1</version>
<version>1.0</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>

View File

@ -12,8 +12,6 @@ import fr.xephi.authme.initialization.OnShutdownPlayerSaver;
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;
@ -201,7 +199,6 @@ public class AuthMe extends JavaPlugin {
// Create injector, provide elements from the Bukkit environment and register providers
injector = new InjectorBuilder()
.addHandlers(new FactoryDependencyHandler(), new SingletonStoreDependencyHandler())
.addDefaultHandlers("fr.xephi.authme")
.create();
injector.register(AuthMe.class, this);

View File

@ -1,8 +1,8 @@
package fr.xephi.authme.command;
import ch.jalu.injector.factory.Factory;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.help.HelpProvider;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.permission.PermissionsManager;

View File

@ -17,6 +17,7 @@ import fr.xephi.authme.command.executable.authme.PurgeBannedPlayersCommand;
import fr.xephi.authme.command.executable.authme.PurgeCommand;
import fr.xephi.authme.command.executable.authme.PurgeLastPositionCommand;
import fr.xephi.authme.command.executable.authme.PurgePlayerCommand;
import fr.xephi.authme.command.executable.authme.RecentPlayersCommand;
import fr.xephi.authme.command.executable.authme.RegisterAdminCommand;
import fr.xephi.authme.command.executable.authme.ReloadCommand;
import fr.xephi.authme.command.executable.authme.SetEmailCommand;
@ -433,6 +434,15 @@ public class CommandInitializer {
.executableCommand(MessagesCommand.class)
.register();
CommandDescription.builder()
.parent(authmeBase)
.labels("recent")
.description("See players who have recently logged in")
.detailedDescription("Shows the last players that have logged in.")
.permission(AdminPermission.SEE_RECENT_PLAYERS)
.executableCommand(RecentPlayersCommand.class)
.register();
CommandDescription.builder()
.parent(authmeBase)
.labels("debug", "dbg")

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.command.executable.authme;
import ch.jalu.injector.factory.Factory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSortedMap;
import fr.xephi.authme.ConsoleLogger;
@ -13,7 +14,6 @@ import fr.xephi.authme.datasource.converter.RoyalAuthConverter;
import fr.xephi.authme.datasource.converter.SqliteToSql;
import fr.xephi.authme.datasource.converter.VAuthConverter;
import fr.xephi.authme.datasource.converter.XAuthConverter;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;

View File

@ -0,0 +1,50 @@
package fr.xephi.authme.command.executable.authme;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import static java.time.Instant.ofEpochMilli;
/**
* Command showing the most recent logged in players.
*/
public class RecentPlayersCommand implements ExecutableCommand {
/** DateTime formatter, producing Strings such as "10:42 AM, 11 Jul". */
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("hh:mm a, dd MMM");
@Inject
private DataSource dataSource;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
List<PlayerAuth> recentPlayers = dataSource.getRecentlyLoggedInPlayers();
sender.sendMessage(ChatColor.BLUE + "[AuthMe] Recently logged in players");
for (PlayerAuth auth : recentPlayers) {
sender.sendMessage(formatPlayerMessage(auth));
}
}
@VisibleForTesting
ZoneId getZoneId() {
return ZoneId.systemDefault();
}
private String formatPlayerMessage(PlayerAuth auth) {
LocalDateTime lastLogin = LocalDateTime.ofInstant(ofEpochMilli(auth.getLastLogin()), getZoneId());
String lastLoginText = DATE_FORMAT.format(lastLogin);
return "- " + auth.getRealName() + " (" + lastLoginText + " with IP " + auth.getLastIp() + ")";
}
}

View File

@ -1,12 +1,12 @@
package fr.xephi.authme.command.executable.authme;
import ch.jalu.injector.factory.SingletonStore;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.initialization.factory.SingletonStore;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.Settings;

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.command.executable.authme.debug;
import ch.jalu.injector.factory.SingletonStore;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.datasource.CacheDataSource;
@ -7,7 +8,6 @@ import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.initialization.factory.SingletonStore;
import fr.xephi.authme.permission.DebugSectionPermissions;
import fr.xephi.authme.permission.PermissionNode;
import org.bukkit.ChatColor;

View File

@ -1,8 +1,8 @@
package fr.xephi.authme.command.executable.authme.debug;
import ch.jalu.injector.factory.Factory;
import com.google.common.collect.ImmutableSet;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.permission.PermissionsManager;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;

View File

@ -1,9 +1,9 @@
package fr.xephi.authme.data.limbo.persistence;
import ch.jalu.injector.factory.Factory;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.LimboSettings;
import org.bukkit.entity.Player;

View File

@ -263,6 +263,11 @@ public class CacheDataSource implements DataSource {
.collect(Collectors.toList());
}
@Override
public List<PlayerAuth> getRecentlyLoggedInPlayers() {
return source.getRecentlyLoggedInPlayers();
}
@Override
public void invalidateCache(String playerName) {
cachedAuths.invalidate(playerName);

View File

@ -225,6 +225,13 @@ public interface DataSource extends Reloadable {
*/
List<PlayerAuth> getAllAuths();
/**
* Returns the last ten players who have recently logged in (first ten players with highest last login date).
*
* @return the 10 last players who last logged in
*/
List<PlayerAuth> getRecentlyLoggedInPlayers();
/**
* Reload the data source.
*/

View File

@ -393,6 +393,11 @@ public class FlatFile implements DataSource {
throw new UnsupportedOperationException("Flat file no longer supported");
}
@Override
public List<PlayerAuth> getRecentlyLoggedInPlayers() {
throw new UnsupportedOperationException("Flat file no longer supported");
}
/**
* Creates a PlayerAuth object from the read data.
*

View File

@ -716,6 +716,22 @@ public class MySQL implements DataSource {
return players;
}
@Override
public List<PlayerAuth> getRecentlyLoggedInPlayers() {
List<PlayerAuth> players = new ArrayList<>();
String sql = "SELECT * FROM " + tableName + " ORDER BY " + col.LAST_LOGIN + " DESC LIMIT 10;";
try (Connection con = getConnection();
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) {
players.add(buildAuthFromResultSet(rs));
}
} catch (SQLException e) {
logSqlException(e);
}
return players;
}
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
String salt = col.SALT.isEmpty() ? null : row.getString(col.SALT);
int group = col.GROUP.isEmpty() ? -1 : row.getInt(col.GROUP);

View File

@ -639,6 +639,20 @@ public class SQLite implements DataSource {
return players;
}
@Override
public List<PlayerAuth> getRecentlyLoggedInPlayers() {
List<PlayerAuth> players = new ArrayList<>();
String sql = "SELECT * FROM " + tableName + " ORDER BY " + col.LAST_LOGIN + " DESC LIMIT 10;";
try (Statement st = con.createStatement(); ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) {
players.add(buildAuthFromResultSet(rs));
}
} catch (SQLException e) {
logSqlException(e);
}
return players;
}
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
String salt = !col.SALT.isEmpty() ? row.getString(col.SALT) : null;

View File

@ -1,19 +0,0 @@
package fr.xephi.authme.initialization.factory;
/**
* Injectable factory that creates new instances of a certain type.
*
* @param <P> the parent type to which the factory is limited to
*/
public interface Factory<P> {
/**
* Creates an instance of the given class.
*
* @param clazz the class to instantiate
* @param <C> the class type
* @return new instance of the class
*/
<C extends P> C newInstance(Class<C> clazz);
}

View File

@ -1,46 +0,0 @@
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;
/**
* Dependency handler that builds {@link Factory} objects.
*/
public class FactoryDependencyHandler implements DependencyHandler {
@Override
public Object resolveValue(ResolvedInstantiationContext<?> context, DependencyDescription dependencyDescription) {
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() + "'");
}
return new FactoryImpl<>(genericType, context.getInjector());
}
return null;
}
private static final class FactoryImpl<P> implements Factory<P> {
private final Injector injector;
private final Class<P> parentClass;
FactoryImpl(Class<P> parentClass, Injector injector) {
this.parentClass = parentClass;
this.injector = injector;
}
@Override
public <C extends P> C newInstance(Class<C> clazz) {
if (parentClass.isAssignableFrom(clazz)) {
return injector.newInstance(clazz);
}
throw new IllegalArgumentException(clazz + " not child of " + parentClass);
}
}
}

View File

@ -1,37 +0,0 @@
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

@ -1,61 +0,0 @@
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

@ -40,6 +40,10 @@ public class ServerListener implements Listener {
if ("Essentials".equalsIgnoreCase(pluginName)) {
pluginHookService.unhookEssentials();
ConsoleLogger.info("Essentials has been disabled: unhooking");
} else if ("CMI".equalsIgnoreCase(pluginName)) {
pluginHookService.unhookCmi();
spawnLoader.unloadCmiSpawn();
ConsoleLogger.info("CMI has been disabled: unhooking");
} else if ("Multiverse-Core".equalsIgnoreCase(pluginName)) {
pluginHookService.unhookMultiverse();
ConsoleLogger.info("Multiverse-Core has been disabled: unhooking");
@ -70,6 +74,9 @@ public class ServerListener implements Listener {
pluginHookService.tryHookToMultiverse();
} else if ("EssentialsSpawn".equalsIgnoreCase(pluginName)) {
spawnLoader.loadEssentialsSpawn();
} else if ("CMI".equalsIgnoreCase(pluginName)) {
pluginHookService.tryHookToCmi();
spawnLoader.loadCmiSpawn();
} else if ("ProtocolLib".equalsIgnoreCase(pluginName)) {
protocolLibService.setup();
}

View File

@ -50,6 +50,11 @@ public enum AdminPermission implements PermissionNode {
*/
GET_IP("authme.admin.getip"),
/**
* Administrator command to see the last recently logged in players.
*/
SEE_RECENT_PLAYERS("authme.admin.seerecent"),
/**
* Administrator command to teleport to the AuthMe spawn.
*/

View File

@ -47,8 +47,8 @@ public class SyncProcessManager {
runTask(() -> processSyncPlayerLogout.processSyncLogout(player));
}
public void processSyncPlayerLogin(Player player) {
runTask(() -> processSyncPlayerLogin.processPlayerLogin(player));
public void processSyncPlayerLogin(Player player, boolean isFirstLogin) {
runTask(() -> processSyncPlayerLogin.processPlayerLogin(player, isFirstLogin));
}
public void processSyncPlayerQuit(Player player, boolean wasLoggedIn) {

View File

@ -220,6 +220,8 @@ public class AsynchronousLogin implements AsynchronousProcess {
*/
private void performLogin(Player player, PlayerAuth auth) {
if (player.isOnline()) {
final boolean isFirstLogin = (auth.getLastLogin() == null);
// Update auth to reflect this new login
final String ip = PlayerUtils.getPlayerIp(player);
auth.setRealName(player.getName());
@ -258,7 +260,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
// task, we schedule it in the end
// so that we can be sure, and have not to care if it might be
// processed in other order.
syncProcessManager.processSyncPlayerLogin(player);
syncProcessManager.processSyncPlayerLogin(player, isFirstLogin);
} else {
ConsoleLogger.warning("Player '" + player.getName() + "' wasn't online during login process, aborted...");
}

View File

@ -62,7 +62,13 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
}
}
public void processPlayerLogin(Player player) {
/**
* Performs operations in sync mode for a player that has just logged in.
*
* @param player the player that was logged in
* @param isFirstLogin true if this is the first time the player logged in
*/
public void processPlayerLogin(Player player, boolean isFirstLogin) {
final String name = player.getName().toLowerCase();
final LimboPlayer limbo = limboService.getLimboPlayer(name);
@ -93,6 +99,9 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
welcomeMessageConfiguration.sendWelcomeMessage(player);
// Login is now finished; we can force all commands
if (isFirstLogin) {
commandManager.runCommandsOnFirstLogin(player);
}
commandManager.runCommandsOnLogin(player);
// Send Bungee stuff. The service will check if it is enabled or not.

View File

@ -1,9 +1,9 @@
package fr.xephi.authme.process.register;
import ch.jalu.injector.factory.SingletonStore;
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;

View File

@ -1,9 +1,9 @@
package fr.xephi.authme.security;
import ch.jalu.injector.factory.Factory;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.PasswordEncryptionEvent;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.Settings;

View File

@ -22,6 +22,7 @@ public class PluginHookService {
private final PluginManager pluginManager;
private Essentials essentials;
private Plugin cmi;
private MultiverseCore multiverse;
/**
@ -33,6 +34,7 @@ public class PluginHookService {
public PluginHookService(PluginManager pluginManager) {
this.pluginManager = pluginManager;
tryHookToEssentials();
tryHookToCmi();
tryHookToMultiverse();
}
@ -60,6 +62,19 @@ public class PluginHookService {
return null;
}
/**
* If CMI is hooked into, return CMI' data folder.
*
* @return The CMI data folder, or null if unavailable
*/
public File getCmiDataFolder() {
Plugin plugin = pluginManager.getPlugin("CMI");
if(plugin == null) {
return null;
}
return plugin.getDataFolder();
}
/**
* Return the spawn of the given world as defined by Multiverse (if available).
*
@ -76,10 +91,10 @@ public class PluginHookService {
return null;
}
// ------
// "Is plugin available" methods
// ------
/**
* @return true if we have a hook to Essentials, false otherwise
*/
@ -87,6 +102,13 @@ public class PluginHookService {
return essentials != null;
}
/**
* @return true if we have a hook to CMI, false otherwise
*/
public boolean isCmiAvailable() {
return cmi != null;
}
/**
* @return true if we have a hook to Multiverse, false otherwise
*/
@ -109,6 +131,17 @@ public class PluginHookService {
}
}
/**
* Attempts to create a hook into CMI.
*/
public void tryHookToCmi() {
try {
cmi = getPlugin(pluginManager, "CMI", Plugin.class);
} catch (Exception | NoClassDefFoundError ignored) {
cmi = null;
}
}
/**
* Attempts to create a hook into Multiverse.
*/
@ -123,6 +156,7 @@ public class PluginHookService {
// ------
// Unhook methods
// ------
/**
* Unhooks from Essentials.
*/
@ -130,6 +164,13 @@ public class PluginHookService {
essentials = null;
}
/**
* Unhooks from CMI.
*/
public void unhookCmi() {
cmi = null;
}
/**
* Unhooks from Multiverse.
*/
@ -140,6 +181,7 @@ public class PluginHookService {
// ------
// Helpers
// ------
private static <T extends Plugin> T getPlugin(PluginManager pluginManager, String name, Class<T> clazz)
throws Exception, NoClassDefFoundError {
if (pluginManager.isPluginEnabled(name)) {
@ -150,5 +192,4 @@ public class PluginHookService {
return null;
}
}

View File

@ -35,6 +35,7 @@ public class SpawnLoader implements Reloadable {
private FileConfiguration authMeConfiguration;
private String[] spawnPriority;
private Location essentialsSpawn;
private Location cmiSpawn;
/**
* Constructor.
@ -130,6 +131,31 @@ public class SpawnLoader implements Reloadable {
essentialsSpawn = null;
}
/**
* Load the spawn point defined in CMI.
*/
public void loadCmiSpawn() {
File cmiFolder = pluginHookService.getCmiDataFolder();
if (cmiFolder == null) {
return;
}
File cmiConfig = new File(cmiFolder, "config.yml");
if (cmiConfig.exists()) {
cmiSpawn = getLocationFromCmiConfiguration(YamlConfiguration.loadConfiguration(cmiConfig));
} else {
cmiSpawn = null;
ConsoleLogger.info("CMI config file not found: '" + cmiConfig.getAbsolutePath() + "'");
}
}
/**
* Unset the spawn point defined in CMI.
*/
public void unloadCmiSpawn() {
cmiSpawn = null;
}
/**
* Return the spawn location for the given player. The source of the spawn location varies
* depending on the spawn priority setting.
@ -162,6 +188,9 @@ public class SpawnLoader implements Reloadable {
case "essentials":
spawnLoc = essentialsSpawn;
break;
case "cmi":
spawnLoc = cmiSpawn;
break;
case "authme":
spawnLoc = getSpawn();
break;
@ -242,6 +271,28 @@ public class SpawnLoader implements Reloadable {
return null;
}
/**
* Build a {@link Location} object based on the CMI configuration.
*
* @param configuration The CMI file configuration to read from
*
* @return Location corresponding to the values in the path
*/
private static Location getLocationFromCmiConfiguration(FileConfiguration configuration) {
final String pathPrefix = "Spawn.Main";
if (isLocationCompleteInCmiConfig(configuration, pathPrefix)) {
String prefix = pathPrefix + ".";
String worldName = configuration.getString(prefix + "World");
World world = Bukkit.getWorld(worldName);
if (!StringUtils.isEmpty(worldName) && world != null) {
return new Location(world, configuration.getDouble(prefix + "X"),
configuration.getDouble(prefix + "Y"), configuration.getDouble(prefix + "Z"),
getFloat(configuration, prefix + "Yaw"), getFloat(configuration, prefix + "Pitch"));
}
}
return null;
}
/**
* Return whether the file configuration contains all fields necessary to define a spawn
* under the given path.
@ -261,6 +312,24 @@ public class SpawnLoader implements Reloadable {
return true;
}
/**
* Return whether the CMI file configuration contains all spawn fields under the given path.
*
* @param cmiConfiguration The file configuration from CMI
* @param pathPrefix The path to verify
*
* @return True if all spawn fields are present, false otherwise
*/
private static boolean isLocationCompleteInCmiConfig(FileConfiguration cmiConfiguration, String pathPrefix) {
String[] fields = {"World", "X", "Y", "Z", "Yaw", "Pitch"};
for (String field : fields) {
if (!cmiConfiguration.contains(pathPrefix + "." + field)) {
return false;
}
}
return true;
}
/**
* Retrieve a property as a float from the given file configuration.
*
@ -274,4 +343,5 @@ public class SpawnLoader implements Reloadable {
// This behavior is consistent with FileConfiguration#getDouble
return (value instanceof Number) ? ((Number) value).floatValue() : 0;
}
}

View File

@ -13,6 +13,7 @@ public class CommandConfig {
private Map<String, Command> onJoin = new LinkedHashMap<>();
private Map<String, Command> onLogin = new LinkedHashMap<>();
private Map<String, Command> onSessionLogin = new LinkedHashMap<>();
private Map<String, Command> onFirstLogin = new LinkedHashMap<>();
private Map<String, Command> onRegister = new LinkedHashMap<>();
private Map<String, Command> onUnregister = new LinkedHashMap<>();
private Map<String, Command> onLogout = new LinkedHashMap<>();
@ -41,6 +42,14 @@ public class CommandConfig {
this.onSessionLogin = onSessionLogin;
}
public Map<String, Command> getOnFirstLogin() {
return onFirstLogin;
}
public void setOnFirstLogin(Map<String, Command> onFirstLogin) {
this.onFirstLogin = onFirstLogin;
}
public Map<String, Command> getOnRegister() {
return onRegister;
}

View File

@ -34,6 +34,7 @@ public class CommandManager implements Reloadable {
private WrappedTagReplacer<Command, Player> onJoinCommands;
private WrappedTagReplacer<Command, Player> onLoginCommands;
private WrappedTagReplacer<Command, Player> onSessionLoginCommands;
private WrappedTagReplacer<Command, Player> onFirstLoginCommands;
private WrappedTagReplacer<Command, Player> onRegisterCommands;
private WrappedTagReplacer<Command, Player> onUnregisterCommands;
private WrappedTagReplacer<Command, Player> onLogoutCommands;
@ -75,7 +76,6 @@ public class CommandManager implements Reloadable {
executeCommands(player, onLoginCommands.getAdaptedItems(player));
}
/**
* Runs the configured commands for when a player has logged in successfully due to session.
*
@ -85,6 +85,15 @@ public class CommandManager implements Reloadable {
executeCommands(player, onSessionLoginCommands.getAdaptedItems(player));
}
/**
* Runs the configured commands for when a player logs in the first time.
*
* @param player the player that has logged in for the first time
*/
public void runCommandsOnFirstLogin(Player player) {
executeCommands(player, onFirstLoginCommands.getAdaptedItems(player));
}
/**
* Runs the configured commands for when a player has been unregistered.
*
@ -124,6 +133,7 @@ public class CommandManager implements Reloadable {
CommandConfig commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS);
onJoinCommands = newReplacer(commandConfig.getOnJoin());
onLoginCommands = newReplacer(commandConfig.getOnLogin());
onFirstLoginCommands = newReplacer(commandConfig.getOnFirstLogin());
onSessionLoginCommands = newReplacer(commandConfig.getOnSessionLogin());
onRegisterCommands = newReplacer(commandConfig.getOnRegister());
onUnregisterCommands = newReplacer(commandConfig.getOnUnregister());

View File

@ -23,7 +23,7 @@ class CommandMigrationService implements MigrationService {
/** List of all properties in {@link CommandConfig}. */
@VisibleForTesting
static final List<String> COMMAND_CONFIG_PROPERTIES = ImmutableList.of(
"onJoin", "onLogin", "onSessionLogin", "onRegister", "onUnregister", "onLogout");
"onJoin", "onLogin", "onSessionLogin", "onFirstLogin", "onRegister", "onUnregister", "onLogout");
@Inject
private SettingsMigrationService settingsMigrationService;

View File

@ -22,7 +22,7 @@ public final class CommandSettingsHolder implements SettingsHolder {
@SectionComments
public static Map<String, String[]> sectionComments() {
String[] comments = {
String[] rootComments = {
"This configuration file allows you to execute commands on various events.",
"Supported placeholders in commands:",
" %p is replaced with the player name.",
@ -48,10 +48,14 @@ public final class CommandSettingsHolder implements SettingsHolder {
" command: 'broadcast %p has joined, welcome back!'",
" executor: CONSOLE",
"",
"Supported command events: onLogin, onSessionLogin, onJoin, onLogout, onRegister, onUnregister"
"Supported command events: onLogin, onSessionLogin, onFirstLogin, onJoin, onLogout, onRegister, "
+ "onUnregister"
};
Map<String, String[]> commentMap = new HashMap<>();
commentMap.put("", comments);
commentMap.put("", rootComments);
commentMap.put("onFirstLogin", new String[]{
"Commands to run for players logging in whose 'last login date' was empty"
});
commentMap.put("onUnregister", new String[]{
"Commands to run whenever a player is unregistered (by himself, or by an admin)"
});

View File

@ -140,9 +140,9 @@ public final class RestrictionSettings implements SettingsHolder {
public static final Property<Boolean> DISPLAY_OTHER_ACCOUNTS =
newProperty("settings.restrictions.displayOtherAccounts", true);
@Comment("Spawn priority; values: authme, essentials, multiverse, default")
@Comment("Spawn priority; values: authme, essentials, cmi, multiverse, default")
public static final Property<String> SPAWN_PRIORITY =
newProperty("settings.restrictions.spawnPriority", "authme,essentials,multiverse,default");
newProperty("settings.restrictions.spawnPriority", "authme,essentials,cmi,multiverse,default");
@Comment("Maximum Login authorized by IP")
public static final Property<Integer> MAX_LOGIN_PER_IP =

View File

@ -1,7 +1,7 @@
package fr.xephi.authme.task;
import ch.jalu.injector.factory.SingletonStore;
import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.factory.SingletonStore;
import org.bukkit.scheduler.BukkitRunnable;
import javax.inject.Inject;

View File

@ -24,7 +24,9 @@
# command: 'broadcast %p has joined, welcome back!'
# executor: CONSOLE
#
# Supported command events: onLogin, onSessionLogin, onJoin, onLogout, onRegister, onUnregister
# Supported command events: onLogin, onSessionLogin, onFirstLogin, onJoin, onLogout, onRegister, onUnregister
# Commands to run for players logging in whose 'last login date' was empty
onFirstLogin: {}
onJoin: {}
onLogin: {}
# These commands are called whenever a logged in player uses /logout or quits.

View File

@ -17,7 +17,7 @@ softdepend:
commands:
authme:
description: AuthMe op commands
usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|purgeplayer|backup|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|debug
usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|purgeplayer|backup|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|recent|debug
email:
description: Add email or recover password
usage: /email show|add|change|recover|code|setpassword
@ -74,6 +74,7 @@ permissions:
authme.admin.register: true
authme.admin.reload: true
authme.admin.seeotheraccounts: true
authme.admin.seerecent: true
authme.admin.setfirstspawn: true
authme.admin.setspawn: true
authme.admin.spawn: true
@ -134,6 +135,9 @@ permissions:
authme.admin.seeotheraccounts:
description: Permission to see the other accounts of the players that log in.
default: op
authme.admin.seerecent:
description: Administrator command to see the last recently logged in players.
default: op
authme.admin.setfirstspawn:
description: Administrator command to set the first AuthMe spawn.
default: op

View File

@ -8,8 +8,6 @@ import fr.xephi.authme.api.v3.AuthMeApi;
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;
@ -96,7 +94,6 @@ public class AuthMeInitializationTest {
new Settings(dataFolder, mock(PropertyResource.class), null, buildConfigurationData());
Injector injector = new InjectorBuilder()
.addHandlers(new FactoryDependencyHandler(), new SingletonStoreDependencyHandler())
.addDefaultHandlers("fr.xephi.authme")
.create();
injector.provide(DataFolder.class, dataFolder);

View File

@ -1,12 +1,12 @@
package fr.xephi.authme.command;
import ch.jalu.injector.Injector;
import ch.jalu.injector.factory.Factory;
import com.google.common.collect.Sets;
import fr.xephi.authme.command.TestCommandsUtil.TestLoginCommand;
import fr.xephi.authme.command.TestCommandsUtil.TestRegisterCommand;
import fr.xephi.authme.command.TestCommandsUtil.TestUnregisterCommand;
import fr.xephi.authme.command.help.HelpProvider;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.permission.PermissionsManager;

View File

@ -1,8 +1,8 @@
package fr.xephi.authme.command.executable.authme;
import ch.jalu.injector.factory.Factory;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.datasource.converter.Converter;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;

View File

@ -0,0 +1,61 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import org.bukkit.command.CommandSender;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Collections;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
/**
* Test for {@link RecentPlayersCommand}.
*/
@RunWith(MockitoJUnitRunner.class)
public class RecentPlayersCommandTest {
@InjectMocks
@Spy
private RecentPlayersCommand command;
@Mock
private DataSource dataSource;
@Test
public void shouldShowRecentPlayers() {
// given
PlayerAuth auth1 = PlayerAuth.builder()
.name("hannah").realName("Hannah").lastIp("11.11.11.11")
.lastLogin(1510387755000L) // 11/11/2017 @ 8:09am
.build();
PlayerAuth auth2 = PlayerAuth.builder()
.name("matt").realName("MATT").lastIp("22.11.22.33")
.lastLogin(1510269301000L) // 11/09/2017 @ 11:15pm
.build();
doReturn(ZoneId.of("UTC")).when(command).getZoneId();
given(dataSource.getRecentlyLoggedInPlayers()).willReturn(Arrays.asList(auth1, auth2));
CommandSender sender = mock(CommandSender.class);
// when
command.executeCommand(sender, Collections.emptyList());
// then
verify(sender).sendMessage(argThat(containsString("Recently logged in players")));
verify(sender).sendMessage("- Hannah (08:09 AM, 11 Nov with IP 11.11.11.11)");
verify(sender).sendMessage("- MATT (11:15 PM, 09 Nov with IP 22.11.22.33)");
}
}

View File

@ -1,12 +1,12 @@
package fr.xephi.authme.command.executable.authme;
import ch.jalu.injector.factory.SingletonStore;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.DataSourceType;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.initialization.factory.SingletonStore;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.service.CommonService;

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.command.executable.authme.debug;
import ch.jalu.injector.factory.SingletonStore;
import com.google.common.cache.LoadingCache;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.data.auth.PlayerAuth;
@ -11,7 +12,6 @@ import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.initialization.factory.SingletonStore;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.junit.Before;

View File

@ -1,6 +1,6 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.initialization.factory.Factory;
import ch.jalu.injector.factory.Factory;
import fr.xephi.authme.permission.DebugSectionPermissions;
import fr.xephi.authme.permission.PermissionNode;
import fr.xephi.authme.permission.PermissionsManager;

View File

@ -1,12 +1,12 @@
package fr.xephi.authme.data.limbo.persistence;
import ch.jalu.injector.factory.Factory;
import ch.jalu.injector.testing.BeforeInjecting;
import ch.jalu.injector.testing.DelayedInjectionRunner;
import ch.jalu.injector.testing.InjectDelayed;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.LimboSettings;
import org.bukkit.entity.Player;

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.datasource;
import com.google.common.collect.Lists;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.security.crypts.HashedPassword;
import org.junit.Test;
@ -467,4 +468,30 @@ public abstract class AbstractDataSourceIntegrationTest {
assertThat(dataSource.hasSession("user"), equalTo(true));
assertThat(dataSource.hasSession("nonExistentName"), equalTo(false));
}
@Test
public void shouldGetRecentlyLoggedInPlayers() {
// given
DataSource dataSource = getDataSource();
String[] names = {"user3", "user8", "user2", "user4", "user7",
"user11", "user14", "user12", "user18", "user16",
"user28", "user29", "user22", "user20", "user24"};
long timestamp = 1461024000; // 2016-04-19 00:00:00
for (int i = 0; i < names.length; ++i) {
PlayerAuth auth = PlayerAuth.builder().name(names[i])
.registrationDate(1234567)
.lastLogin(timestamp + i * 3600)
.build();
dataSource.saveAuth(auth);
dataSource.updateSession(auth);
}
// when
List<PlayerAuth> recentPlayers = dataSource.getRecentlyLoggedInPlayers();
// then
assertThat(Lists.transform(recentPlayers, PlayerAuth::getNickname),
contains("user24", "user20", "user22", "user29", "user28",
"user16", "user18", "user12", "user14", "user11"));
}
}

View File

@ -31,6 +31,7 @@ public class ServerListenerTest {
private static final String ESSENTIALS = "Essentials";
private static final String ESSENTIALS_SPAWN = "EssentialsSpawn";
private static final String CMI = "CMI";
private static final String MULTIVERSE = "Multiverse-Core";
private static final String PROTOCOL_LIB = "ProtocolLib";
@ -58,6 +59,10 @@ public class ServerListenerTest {
public void shouldForwardPluginNameOnEnable() {
checkEnableHandling(ESSENTIALS, () -> verify(pluginHookService).tryHookToEssentials());
checkEnableHandling(ESSENTIALS_SPAWN, () -> verify(spawnLoader).loadEssentialsSpawn());
checkEnableHandling(CMI, () -> {
verify(pluginHookService).tryHookToCmi();
verify(spawnLoader).loadCmiSpawn();
});
checkEnableHandling(MULTIVERSE, () -> verify(pluginHookService).tryHookToMultiverse());
checkEnableHandling(PROTOCOL_LIB, () -> verify(protocolLibService).setup());
checkEnableHandling("UnknownPlugin", () -> verifyZeroInteractions(pluginHookService, spawnLoader));
@ -67,6 +72,10 @@ public class ServerListenerTest {
public void shouldForwardPluginNameOnDisable() {
checkDisableHandling(ESSENTIALS, () -> verify(pluginHookService).unhookEssentials());
checkDisableHandling(ESSENTIALS_SPAWN, () -> verify(spawnLoader).unloadEssentialsSpawn());
checkDisableHandling(CMI, () -> {
verify(pluginHookService).unhookCmi();
verify(spawnLoader).unloadCmiSpawn();
});
checkDisableHandling(MULTIVERSE, () -> verify(pluginHookService).unhookMultiverse());
checkDisableHandling(PROTOCOL_LIB, () -> verify(protocolLibService).disable());
checkDisableHandling("UnknownPlugin", () -> verifyZeroInteractions(pluginHookService, spawnLoader));

View File

@ -1,9 +1,9 @@
package fr.xephi.authme.process.register;
import ch.jalu.injector.factory.SingletonStore;
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;

View File

@ -2,6 +2,7 @@ package fr.xephi.authme.security;
import ch.jalu.injector.Injector;
import ch.jalu.injector.InjectorBuilder;
import ch.jalu.injector.factory.Factory;
import ch.jalu.injector.testing.BeforeInjecting;
import ch.jalu.injector.testing.DelayedInjectionRunner;
import ch.jalu.injector.testing.InjectDelayed;
@ -9,7 +10,6 @@ import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.PasswordEncryptionEvent;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.security.crypts.Joomla;

View File

@ -35,6 +35,8 @@ public class PluginHookServiceTest {
/** The plugin name of Essentials. */
private static final String ESSENTIALS = "Essentials";
/** The plugin name of CMI. */
private static final String CMI = "CMI";
/** The plugin name of Multiverse-Core. */
private static final String MULTIVERSE = "Multiverse-Core";
@ -71,6 +73,19 @@ public class PluginHookServiceTest {
assertThat(pluginHookService.isEssentialsAvailable(), equalTo(true));
}
@Test
public void shouldHookIntoCmiAtInitialization() {
// given
PluginManager pluginManager = mock(PluginManager.class);
setPluginAvailable(pluginManager, CMI, Plugin.class);
// when
PluginHookService pluginHookService = new PluginHookService(pluginManager);
// then
assertThat(pluginHookService.isCmiAvailable(), equalTo(true));
}
@Test
public void shouldHookIntoMultiverseAtInitialization() {
// given
@ -175,6 +190,7 @@ public class PluginHookServiceTest {
// then
assertThat(pluginHookService.isEssentialsAvailable(), equalTo(false));
assertThat(pluginHookService.isCmiAvailable(), equalTo(false));
assertThat(pluginHookService.isMultiverseAvailable(), equalTo(false));
}

View File

@ -107,6 +107,21 @@ public class CommandManagerTest {
verifyZeroInteractions(geoIpService);
}
@Test
public void shouldExecuteCommandsOnFirstLogin() {
// given
copyJarFileAsCommandsYml(TEST_FILES_FOLDER + "commands.complete.yml");
initManager();
// when
manager.runCommandsOnFirstLogin(player);
// then
verify(bukkitService).dispatchConsoleCommand("pay Bobby 30");
verifyNoMoreInteractions(bukkitService);
verifyZeroInteractions(geoIpService);
}
@Test
public void shouldExecuteCommandsOnJoin() {
// given

View File

@ -1,7 +1,7 @@
package fr.xephi.authme.task;
import ch.jalu.injector.factory.SingletonStore;
import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.factory.SingletonStore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;

View File

@ -1,7 +1,9 @@
package tools.dependencygraph;
import ch.jalu.injector.handlers.instantiation.DependencyDescription;
import ch.jalu.injector.handlers.instantiation.Instantiation;
import ch.jalu.injector.context.ObjectIdentifier;
import ch.jalu.injector.factory.Factory;
import ch.jalu.injector.factory.SingletonStore;
import ch.jalu.injector.handlers.instantiation.Resolution;
import ch.jalu.injector.handlers.instantiation.StandardInjectionProvider;
import ch.jalu.injector.utils.ReflectionUtils;
import com.google.common.collect.HashMultimap;
@ -14,13 +16,12 @@ import fr.xephi.authme.command.executable.authme.debug.DebugCommand;
import fr.xephi.authme.data.limbo.persistence.LimboPersistence;
import fr.xephi.authme.datasource.converter.Converter;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.initialization.factory.SingletonStore;
import fr.xephi.authme.process.AsynchronousProcess;
import fr.xephi.authme.process.SynchronousProcess;
import fr.xephi.authme.process.register.executors.RegistrationExecutor;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import org.bukkit.event.Listener;
import tools.utils.InjectorUtils;
import tools.utils.ToolTask;
import tools.utils.ToolsConstants;
@ -124,21 +125,20 @@ public class DrawDependency implements ToolTask {
* This is interesting so that a dependency in a class to {@code Factory<Foo>} is
* rendered as a dependency to {@code Foo}, not to {@code Factory}.
*
* @param clazz class of the dependency
* @param genericType generic type of the dependency
* @return the class to use to render the dependency
*/
private Class<?> unwrapGenericClass(Class<?> clazz, Type genericType) {
if (clazz == Factory.class || clazz == SingletonStore.class) {
private Class<?> unwrapGenericClass(Type genericType) {
if (genericType == Factory.class || genericType == SingletonStore.class) {
Class<?> parameterType = ReflectionUtils.getGenericType(genericType);
Objects.requireNonNull(parameterType, "Parameter type for '" + clazz + "' should be a concrete class");
Objects.requireNonNull(parameterType, "Parameter type for '" + genericType + "' should be a concrete class");
return parameterType;
}
return clazz;
return InjectorUtils.convertToClass(genericType);
}
private List<String> getDependencies(Class<?> clazz) {
Instantiation<?> instantiation = new StandardInjectionProvider().safeGet(clazz);
Resolution<?> instantiation = new StandardInjectionProvider().safeGet(clazz);
return instantiation == null ? null : formatInjectionDependencies(instantiation);
}
@ -150,22 +150,22 @@ public class DrawDependency implements ToolTask {
* @param injection the injection whose dependencies should be formatted
* @return list of dependencies in a friendly format
*/
private List<String> formatInjectionDependencies(Instantiation<?> injection) {
List<DependencyDescription> descriptions = injection.getDependencies();
List<String> result = new ArrayList<>(descriptions.size());
for (DependencyDescription dependency : descriptions) {
private List<String> formatInjectionDependencies(Resolution<?> injection) {
List<ObjectIdentifier> dependencies = injection.getDependencies();
List<String> result = new ArrayList<>(dependencies.size());
for (ObjectIdentifier dependency : dependencies) {
Class<?> annotation = getRelevantAnnotationClass(dependency.getAnnotations());
if (annotation != null) {
result.add("@" + annotation.getSimpleName());
} else {
Class<?> clazz = unwrapGenericClass(dependency.getType(), dependency.getGenericType());
Class<?> clazz = unwrapGenericClass(dependency.getType());
result.add(mapToSuper(clazz).getSimpleName());
}
}
return result;
}
private static Class<? extends Annotation> getRelevantAnnotationClass(Annotation[] annotations) {
private static Class<? extends Annotation> getRelevantAnnotationClass(List<Annotation> annotations) {
for (Annotation annotation : annotations) {
if (ANNOTATION_TYPES.contains(annotation.annotationType())) {
return annotation.annotationType();

View File

@ -1,9 +1,11 @@
package tools.utils;
import ch.jalu.injector.handlers.instantiation.DependencyDescription;
import ch.jalu.injector.handlers.instantiation.Instantiation;
import ch.jalu.injector.context.ObjectIdentifier;
import ch.jalu.injector.handlers.instantiation.Resolution;
import ch.jalu.injector.handlers.instantiation.StandardInjectionProvider;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;
@ -22,15 +24,37 @@ public final class InjectorUtils {
* @return the class' dependencies, or null if no instantiation method found
*/
public static Set<Class<?>> getDependencies(Class<?> clazz) {
Instantiation<?> instantiation = new StandardInjectionProvider().safeGet(clazz);
Resolution<?> instantiation = new StandardInjectionProvider().safeGet(clazz);
if (instantiation == null) {
return null;
}
Set<Class<?>> dependencies = new HashSet<>();
for (DependencyDescription description : instantiation.getDependencies()) {
dependencies.add(description.getType());
for (ObjectIdentifier description : instantiation.getDependencies()) {
dependencies.add(convertToClass(description.getType()));
}
return dependencies;
}
/**
* Returns the given type as a {@link Class}.
*
* @param type the type to convert
* @return class corresponding to the provided type
*/
public static Class<?> convertToClass(Type type) {
if (type instanceof Class<?>) {
return (Class<?>) type;
} else if (type instanceof ParameterizedType) {
Type rawType = ((ParameterizedType) type).getRawType();
if (rawType instanceof Class<?>) {
return (Class<?>) rawType;
} else {
throw new IllegalStateException("Got raw type '" + rawType + "' of type '"
+ rawType.getClass() + "' for genericType '" + type + "'");
}
}
Class<?> typeClass = type == null ? null : type.getClass();
throw new IllegalStateException("Unknown type implementation '" + typeClass + "' for '" + type + "'");
}
}

View File

@ -25,6 +25,10 @@ onSessionLogin:
welcome:
command: 'msg %p Session login!'
executor: CONSOLE
onFirstLogin:
give_money:
command: 'pay %p 30'
executor: CONSOLE
onUnregister: {}
onLogout:
announce: