Service cleanup
This commit is contained in:
@@ -1,311 +0,0 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import fr.xephi.authme.AuthMe;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.initialization.SettingsDependent;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.PluginSettings;
|
||||
import org.bukkit.BanEntry;
|
||||
import org.bukkit.BanList;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Service for operations requiring the Bukkit API, such as for scheduling.
|
||||
*/
|
||||
public class BukkitService implements SettingsDependent {
|
||||
|
||||
/** Number of ticks per second in the Bukkit main thread. */
|
||||
public static final int TICKS_PER_SECOND = 20;
|
||||
/** Number of ticks per minute. */
|
||||
public static final int TICKS_PER_MINUTE = 60 * TICKS_PER_SECOND;
|
||||
|
||||
private final AuthMe authMe;
|
||||
private final boolean getOnlinePlayersIsCollection;
|
||||
private Method getOnlinePlayers;
|
||||
private boolean useAsyncTasks;
|
||||
|
||||
public BukkitService(AuthMe authMe, Settings settings) {
|
||||
this.authMe = authMe;
|
||||
getOnlinePlayersIsCollection = initializeOnlinePlayersIsCollectionField();
|
||||
reload(settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a once off task to occur as soon as possible.
|
||||
* <p>
|
||||
* This task will be executed by the main server thread.
|
||||
*
|
||||
* @param task Task to be executed
|
||||
* @return Task id number (-1 if scheduling failed)
|
||||
*/
|
||||
public int scheduleSyncDelayedTask(Runnable task) {
|
||||
return Bukkit.getScheduler().scheduleSyncDelayedTask(authMe, task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a once off task to occur after a delay.
|
||||
* <p>
|
||||
* This task will be executed by the main server thread.
|
||||
*
|
||||
* @param task Task to be executed
|
||||
* @param delay Delay in server ticks before executing task
|
||||
* @return Task id number (-1 if scheduling failed)
|
||||
*/
|
||||
public int scheduleSyncDelayedTask(Runnable task, long delay) {
|
||||
return Bukkit.getScheduler().scheduleSyncDelayedTask(authMe, task, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a synchronous task if async tasks are enabled; if not, it runs the task immediately.
|
||||
* Use this when {@link #runTaskOptionallyAsync(Runnable) optionally asynchronous tasks} have to
|
||||
* run something synchronously.
|
||||
*
|
||||
* @param task the task to be run
|
||||
*/
|
||||
public void scheduleSyncTaskFromOptionallyAsyncTask(Runnable task) {
|
||||
if (useAsyncTasks) {
|
||||
scheduleSyncDelayedTask(task);
|
||||
} else {
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a task that will run on the next server tick.
|
||||
*
|
||||
* @param task the task to be run
|
||||
* @return a BukkitTask that contains the id number
|
||||
* @throws IllegalArgumentException if plugin is null
|
||||
* @throws IllegalArgumentException if task is null
|
||||
*/
|
||||
public BukkitTask runTask(Runnable task) {
|
||||
return Bukkit.getScheduler().runTask(authMe, task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a task that will run after the specified number of server
|
||||
* ticks.
|
||||
*
|
||||
* @param task the task to be run
|
||||
* @param delay the ticks to wait before running the task
|
||||
* @return a BukkitTask that contains the id number
|
||||
* @throws IllegalArgumentException if plugin is null
|
||||
* @throws IllegalArgumentException if task is null
|
||||
*/
|
||||
public BukkitTask runTaskLater(Runnable task, long delay) {
|
||||
return Bukkit.getScheduler().runTaskLater(authMe, task, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules this task to run asynchronously or immediately executes it based on
|
||||
* AuthMe's configuration.
|
||||
*
|
||||
* @param task the task to run
|
||||
*/
|
||||
public void runTaskOptionallyAsync(Runnable task) {
|
||||
if (useAsyncTasks) {
|
||||
runTaskAsynchronously(task);
|
||||
} else {
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>Asynchronous tasks should never access any API in Bukkit. Great care
|
||||
* should be taken to assure the thread-safety of asynchronous tasks.</b>
|
||||
* <p>
|
||||
* Returns a task that will run asynchronously.
|
||||
*
|
||||
* @param task the task to be run
|
||||
* @return a BukkitTask that contains the id number
|
||||
* @throws IllegalArgumentException if plugin is null
|
||||
* @throws IllegalArgumentException if task is null
|
||||
*/
|
||||
public BukkitTask runTaskAsynchronously(Runnable task) {
|
||||
return Bukkit.getScheduler().runTaskAsynchronously(authMe, task);
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>Asynchronous tasks should never access any API in Bukkit. Great care
|
||||
* should be taken to assure the thread-safety of asynchronous tasks.</b>
|
||||
* <p>
|
||||
* Returns a task that will repeatedly run asynchronously until cancelled,
|
||||
* starting after the specified number of server ticks.
|
||||
*
|
||||
* @param task the task to be run
|
||||
* @param delay the ticks to wait before running the task for the first
|
||||
* time
|
||||
* @param period the ticks to wait between runs
|
||||
* @return a BukkitTask that contains the id number
|
||||
* @throws IllegalArgumentException if task is null
|
||||
*/
|
||||
public BukkitTask runTaskTimerAsynchronously(Runnable task, long delay, long period) {
|
||||
return Bukkit.getScheduler().runTaskTimerAsynchronously(authMe, task, delay, period);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the given task to repeatedly run until cancelled, starting after the
|
||||
* specified number of server ticks.
|
||||
*
|
||||
* @param task the task to schedule
|
||||
* @param delay the ticks to wait before running the task
|
||||
* @param period the ticks to wait between runs
|
||||
* @return a BukkitTask that contains the id number
|
||||
* @throws IllegalArgumentException if plugin is null
|
||||
* @throws IllegalStateException if this was already scheduled
|
||||
* @see BukkitScheduler#runTaskTimer(Plugin, Runnable, long, long)
|
||||
*/
|
||||
public BukkitTask runTaskTimer(BukkitRunnable task, long delay, long period) {
|
||||
return task.runTaskTimer(authMe, period, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a message to all players.
|
||||
*
|
||||
* @param message the message
|
||||
* @return the number of players
|
||||
*/
|
||||
public int broadcastMessage(String message) {
|
||||
return Bukkit.broadcastMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player with the exact given name, case insensitive.
|
||||
*
|
||||
* @param name Exact name of the player to retrieve
|
||||
* @return a player object if one was found, null otherwise
|
||||
*/
|
||||
public Player getPlayerExact(String name) {
|
||||
return authMe.getServer().getPlayerExact(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a set containing all banned players.
|
||||
*
|
||||
* @return a set containing banned players
|
||||
*/
|
||||
public Set<OfflinePlayer> getBannedPlayers() {
|
||||
return Bukkit.getBannedPlayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets every player that has ever played on this server.
|
||||
*
|
||||
* @return an array containing all previous players
|
||||
*/
|
||||
public OfflinePlayer[] getOfflinePlayers() {
|
||||
return Bukkit.getOfflinePlayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe way to retrieve the list of online players from the server. Depending on the
|
||||
* implementation of the server, either an array of {@link Player} instances is being returned,
|
||||
* or a Collection. Always use this wrapper to retrieve online players instead of {@link
|
||||
* Bukkit#getOnlinePlayers()} directly.
|
||||
*
|
||||
* @return collection of online players
|
||||
*
|
||||
* @see <a href="https://www.spigotmc.org/threads/solved-cant-use-new-getonlineplayers.33061/">SpigotMC
|
||||
* forum</a>
|
||||
* @see <a href="http://stackoverflow.com/questions/32130851/player-changed-from-array-to-collection">StackOverflow</a>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Collection<? extends Player> getOnlinePlayers() {
|
||||
if (getOnlinePlayersIsCollection) {
|
||||
return Bukkit.getOnlinePlayers();
|
||||
}
|
||||
try {
|
||||
// The lookup of a method via Reflections is rather expensive, so we keep a reference to it
|
||||
if (getOnlinePlayers == null) {
|
||||
getOnlinePlayers = Bukkit.class.getDeclaredMethod("getOnlinePlayers");
|
||||
}
|
||||
Object obj = getOnlinePlayers.invoke(null);
|
||||
if (obj instanceof Collection<?>) {
|
||||
return (Collection<? extends Player>) obj;
|
||||
} else if (obj instanceof Player[]) {
|
||||
return Arrays.asList((Player[]) obj);
|
||||
} else {
|
||||
String type = (obj == null) ? "null" : obj.getClass().getName();
|
||||
ConsoleLogger.warning("Unknown list of online players of type " + type);
|
||||
}
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
ConsoleLogger.logException("Could not retrieve list of online players:", e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls an event with the given details.
|
||||
*
|
||||
* @param event Event details
|
||||
* @throws IllegalStateException Thrown when an asynchronous event is
|
||||
* fired from synchronous code.
|
||||
*/
|
||||
public void callEvent(Event event) {
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world with the given name.
|
||||
*
|
||||
* @param name the name of the world to retrieve
|
||||
* @return a world with the given name, or null if none exists
|
||||
*/
|
||||
public World getWorld(String name) {
|
||||
return Bukkit.getWorld(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(Settings settings) {
|
||||
useAsyncTasks = settings.getProperty(PluginSettings.USE_ASYNC_TASKS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method run upon initialization to verify whether or not the Bukkit implementation
|
||||
* returns the online players as a Collection.
|
||||
*
|
||||
* @see #getOnlinePlayers()
|
||||
*/
|
||||
private static boolean initializeOnlinePlayersIsCollectionField() {
|
||||
try {
|
||||
Method method = Bukkit.class.getDeclaredMethod("getOnlinePlayers");
|
||||
return method.getReturnType() == Collection.class;
|
||||
} catch (NoSuchMethodException e) {
|
||||
ConsoleLogger.warning("Error verifying if getOnlinePlayers is a collection! Method doesn't exist");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a ban to the this list. If a previous ban exists, this will
|
||||
* update the previous entry.
|
||||
*
|
||||
* @param ip the ip of the ban
|
||||
* @param reason reason for the ban, null indicates implementation default
|
||||
* @param expires date for the ban's expiration (unban), or null to imply
|
||||
* forever
|
||||
* @param source source of the ban, null indicates implementation default
|
||||
* @return the entry for the newly created ban, or the entry for the
|
||||
* (updated) previous ban
|
||||
*/
|
||||
public BanEntry banIp(String ip, String reason, Date expires, String source) {
|
||||
return Bukkit.getServer().getBanList(BanList.Type.IP).addBan(ip, reason, expires, source);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import java.util.List;
|
||||
*/
|
||||
public final class CollectionUtils {
|
||||
|
||||
// Utility class
|
||||
private CollectionUtils() {
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import static java.lang.String.format;
|
||||
*/
|
||||
public final class FileUtils {
|
||||
|
||||
// Utility class
|
||||
private FileUtils() {
|
||||
}
|
||||
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.maxmind.geoip.LookupService;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.initialization.DataFolder;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class GeoLiteAPI {
|
||||
private static final String LICENSE =
|
||||
"[LICENSE] This product uses data from the GeoLite API created by MaxMind, available at http://www.maxmind.com";
|
||||
private static final String GEOIP_URL =
|
||||
"http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz";
|
||||
private LookupService lookupService;
|
||||
private Thread downloadTask;
|
||||
|
||||
private final File dataFile;
|
||||
|
||||
@Inject
|
||||
GeoLiteAPI(@DataFolder File dataFolder) {
|
||||
this.dataFile = new File(dataFolder, "GeoIP.dat");
|
||||
// Fires download of recent data or the initialization of the look up service
|
||||
isDataAvailable();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
GeoLiteAPI(@DataFolder File dataFolder, LookupService lookupService) {
|
||||
this.dataFile = dataFolder;
|
||||
this.lookupService = lookupService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download (if absent or old) the GeoIpLite data file and then try to load it.
|
||||
*
|
||||
* @return True if the data is available, false otherwise.
|
||||
*/
|
||||
private synchronized boolean isDataAvailable() {
|
||||
if (downloadTask != null && downloadTask.isAlive()) {
|
||||
return false;
|
||||
}
|
||||
if (lookupService != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dataFile.exists()) {
|
||||
boolean dataIsOld = (System.currentTimeMillis() - dataFile.lastModified()) > TimeUnit.DAYS.toMillis(30);
|
||||
if (!dataIsOld) {
|
||||
try {
|
||||
lookupService = new LookupService(dataFile);
|
||||
ConsoleLogger.info(LICENSE);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
ConsoleLogger.logException("Failed to load GeoLiteAPI database", e);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
FileUtils.delete(dataFile);
|
||||
}
|
||||
}
|
||||
// Ok, let's try to download the data file!
|
||||
downloadTask = createDownloadTask();
|
||||
downloadTask.start();
|
||||
return false;
|
||||
}
|
||||
|
||||
private Thread createDownloadTask() {
|
||||
return new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
URL downloadUrl = new URL(GEOIP_URL);
|
||||
URLConnection conn = downloadUrl.openConnection();
|
||||
conn.setConnectTimeout(10000);
|
||||
conn.connect();
|
||||
InputStream input = conn.getInputStream();
|
||||
if (conn.getURL().toString().endsWith(".gz")) {
|
||||
input = new GZIPInputStream(input);
|
||||
}
|
||||
OutputStream output = new FileOutputStream(dataFile);
|
||||
byte[] buffer = new byte[2048];
|
||||
int length = input.read(buffer);
|
||||
while (length >= 0) {
|
||||
output.write(buffer, 0, length);
|
||||
length = input.read(buffer);
|
||||
}
|
||||
output.close();
|
||||
input.close();
|
||||
} catch (IOException e) {
|
||||
ConsoleLogger.logException("Could not download GeoLiteAPI database", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the country code of the given IP address.
|
||||
*
|
||||
* @param ip textual IP address to lookup.
|
||||
*
|
||||
* @return two-character ISO 3166-1 alpha code for the country.
|
||||
*/
|
||||
public String getCountryCode(String ip) {
|
||||
if (!"127.0.0.1".equals(ip) && isDataAvailable()) {
|
||||
return lookupService.getCountry(ip).getCode();
|
||||
}
|
||||
return "--";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the country name of the given IP address.
|
||||
*
|
||||
* @param ip textual IP address to lookup.
|
||||
*
|
||||
* @return The name of the country.
|
||||
*/
|
||||
public String getCountryName(String ip) {
|
||||
if (!"127.0.0.1".equals(ip) && isDataAvailable()) {
|
||||
return lookupService.getCountry(ip).getName();
|
||||
}
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.cache.auth.PlayerAuth;
|
||||
import fr.xephi.authme.converter.ForceFlatToSqlite;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.datasource.DataSourceType;
|
||||
import fr.xephi.authme.datasource.FlatFile;
|
||||
import fr.xephi.authme.datasource.SQLite;
|
||||
import fr.xephi.authme.security.HashAlgorithm;
|
||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||
import fr.xephi.authme.security.crypts.SHA256;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.DatabaseSettings;
|
||||
import fr.xephi.authme.settings.properties.SecuritySettings;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Migrations to perform during the initialization of AuthMe.
|
||||
*/
|
||||
public final class MigrationService {
|
||||
|
||||
private MigrationService() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash all passwords to SHA256 and updated the setting if the password hash is set to the deprecated PLAINTEXT.
|
||||
*
|
||||
* @param settings The settings instance
|
||||
* @param dataSource The data source
|
||||
* @param authmeSha256 Instance to the AuthMe SHA256 encryption method implementation
|
||||
*/
|
||||
public static void changePlainTextToSha256(Settings settings, DataSource dataSource,
|
||||
SHA256 authmeSha256) {
|
||||
if (HashAlgorithm.PLAINTEXT == settings.getProperty(SecuritySettings.PASSWORD_HASH)) {
|
||||
ConsoleLogger.warning("Your HashAlgorithm has been detected as plaintext and is now deprecated;"
|
||||
+ " it will be changed and hashed now to the AuthMe default hashing method");
|
||||
ConsoleLogger.warning("Don't stop your server; wait for the conversion to have been completed!");
|
||||
List<PlayerAuth> allAuths = dataSource.getAllAuths();
|
||||
for (PlayerAuth auth : allAuths) {
|
||||
String hash = auth.getPassword().getHash();
|
||||
if (hash.startsWith("$SHA$")) {
|
||||
ConsoleLogger.warning("Skipping conversion for " + auth.getNickname() + "; detected SHA hash");
|
||||
} else {
|
||||
HashedPassword hashedPassword = authmeSha256.computeHash(hash, auth.getNickname());
|
||||
auth.setPassword(hashedPassword);
|
||||
dataSource.updatePassword(auth);
|
||||
}
|
||||
}
|
||||
settings.setProperty(SecuritySettings.PASSWORD_HASH, HashAlgorithm.SHA256);
|
||||
settings.save();
|
||||
ConsoleLogger.info("Migrated " + allAuths.size() + " accounts from plaintext to SHA256");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the data source from the deprecated FLATFILE type to SQLITE.
|
||||
*
|
||||
* @param settings The settings instance
|
||||
* @param dataSource The data source
|
||||
* @return The converted datasource (SQLite), or null if no migration was necessary
|
||||
*/
|
||||
public static DataSource convertFlatfileToSqlite(Settings settings, DataSource dataSource) {
|
||||
if (DataSourceType.FILE == settings.getProperty(DatabaseSettings.BACKEND)) {
|
||||
ConsoleLogger.warning("FlatFile backend has been detected and is now deprecated; it will be changed "
|
||||
+ "to SQLite... Connection will be impossible until conversion is done!");
|
||||
FlatFile flatFile = (FlatFile) dataSource;
|
||||
try {
|
||||
SQLite sqlite = new SQLite(settings);
|
||||
ForceFlatToSqlite converter = new ForceFlatToSqlite(flatFile, sqlite);
|
||||
converter.execute(null);
|
||||
settings.setProperty(DatabaseSettings.BACKEND, DataSourceType.SQLITE);
|
||||
settings.save();
|
||||
return sqlite;
|
||||
} catch (Exception e) {
|
||||
ConsoleLogger.logException("Error during conversion from Flatfile to SQLite", e);
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Player utilities.
|
||||
*/
|
||||
public class PlayerUtils {
|
||||
|
||||
// Utility class
|
||||
private PlayerUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get player's UUID if can, name otherwise.
|
||||
*
|
||||
* @param player Player to retrieve
|
||||
*
|
||||
* @return player's UUID or Name in String.
|
||||
*/
|
||||
public static String getUUIDorName(OfflinePlayer player) {
|
||||
// We may made this configurable in future
|
||||
// so we can have uuid support.
|
||||
try {
|
||||
return player.getUniqueId().toString();
|
||||
} catch (NoSuchMethodError ignore) {
|
||||
return player.getName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IP of the given player.
|
||||
*
|
||||
* @param p The player to return the IP address for
|
||||
*
|
||||
* @return The player's IP address
|
||||
*/
|
||||
public static String getPlayerIp(Player p) {
|
||||
return p.getAddress().getAddress().getHostAddress();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,19 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
/**
|
||||
* Runtime utilities.
|
||||
*/
|
||||
public class RuntimeUtils {
|
||||
|
||||
// Utility class
|
||||
private RuntimeUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the available core count of the JVM.
|
||||
*
|
||||
* @return the core count
|
||||
*/
|
||||
public static int getCoreCount() {
|
||||
return Runtime.getRuntime().availableProcessors();
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import java.io.File;
|
||||
*/
|
||||
public final class StringUtils {
|
||||
|
||||
// Utility class
|
||||
private StringUtils() {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import fr.xephi.authme.cache.auth.PlayerAuth;
|
||||
import fr.xephi.authme.cache.auth.PlayerCache;
|
||||
import fr.xephi.authme.cache.limbo.PlayerData;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.events.AbstractTeleportEvent;
|
||||
import fr.xephi.authme.events.AuthMeTeleportEvent;
|
||||
import fr.xephi.authme.events.FirstSpawnTeleportEvent;
|
||||
import fr.xephi.authme.events.SpawnTeleportEvent;
|
||||
import fr.xephi.authme.initialization.Reloadable;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.SpawnLoader;
|
||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static fr.xephi.authme.settings.properties.RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN;
|
||||
|
||||
/**
|
||||
* Handles teleportation (placement of player to spawn).
|
||||
*/
|
||||
public class TeleportationService implements Reloadable {
|
||||
|
||||
@Inject
|
||||
private Settings settings;
|
||||
|
||||
@Inject
|
||||
private BukkitService bukkitService;
|
||||
|
||||
@Inject
|
||||
private SpawnLoader spawnLoader;
|
||||
|
||||
@Inject
|
||||
private PlayerCache playerCache;
|
||||
|
||||
@Inject
|
||||
private DataSource dataSource;
|
||||
|
||||
private Set<String> spawnOnLoginWorlds;
|
||||
|
||||
TeleportationService() {
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
@Override
|
||||
public void reload() {
|
||||
// Use a Set for better performance with #contains()
|
||||
spawnOnLoginWorlds = new HashSet<>(settings.getProperty(RestrictionSettings.FORCE_SPAWN_ON_WORLDS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports the player according to the settings when he joins.
|
||||
* <p>
|
||||
* Note: this is triggered by Bukkit's PlayerLoginEvent, during which you cannot use
|
||||
* {@link Player#hasPlayedBefore()}: it always returns {@code false}. We trigger teleportation
|
||||
* from the PlayerLoginEvent and not the PlayerJoinEvent to ensure that the location is overridden
|
||||
* as fast as possible (cf. <a href="https://github.com/Xephi/AuthMeReloaded/issues/682">AuthMe #682</a>).
|
||||
*
|
||||
* @param player the player to process
|
||||
* @see <a href="https://bukkit.atlassian.net/browse/BUKKIT-3521">BUKKIT-3521: Player.hasPlayedBefore()
|
||||
* always false</a>
|
||||
*/
|
||||
public void teleportOnJoin(final Player player) {
|
||||
if (!settings.getProperty(RestrictionSettings.NO_TELEPORT)
|
||||
&& settings.getProperty(TELEPORT_UNAUTHED_TO_SPAWN)) {
|
||||
teleportToSpawn(player, playerCache.isAuthenticated(player.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports the player to the first spawn if he is new and the first spawn is configured.
|
||||
*
|
||||
* @param player the player to process
|
||||
*/
|
||||
public void teleportNewPlayerToFirstSpawn(final Player player) {
|
||||
if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location firstSpawn = spawnLoader.getFirstSpawn();
|
||||
if (firstSpawn == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.hasPlayedBefore() || !dataSource.isAuthAvailable(player.getName())) {
|
||||
performTeleportation(player, new FirstSpawnTeleportEvent(player, firstSpawn));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports the player according to the settings after having successfully logged in.
|
||||
*
|
||||
* @param player the player
|
||||
* @param auth corresponding PlayerAuth object
|
||||
* @param limbo corresponding PlayerData object
|
||||
*/
|
||||
public void teleportOnLogin(final Player player, PlayerAuth auth, PlayerData limbo) {
|
||||
if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// #856: If PlayerData comes from a persisted file, the Location might be null
|
||||
String worldName = (limbo != null && limbo.getLocation() != null)
|
||||
? limbo.getLocation().getWorld().getName()
|
||||
: null;
|
||||
|
||||
// The world in PlayerData is from where the player comes, before any teleportation by AuthMe
|
||||
if (mustForceSpawnAfterLogin(worldName)) {
|
||||
teleportToSpawn(player, true);
|
||||
} else if (settings.getProperty(TELEPORT_UNAUTHED_TO_SPAWN)) {
|
||||
if (settings.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION) && auth.getQuitLocY() != 0) {
|
||||
Location location = buildLocationFromAuth(player, auth);
|
||||
teleportBackFromSpawn(player, location);
|
||||
} else if (limbo != null && limbo.getLocation() != null) {
|
||||
teleportBackFromSpawn(player, limbo.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mustForceSpawnAfterLogin(String worldName) {
|
||||
return worldName != null && settings.getProperty(RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN)
|
||||
&& spawnOnLoginWorlds.contains(worldName);
|
||||
}
|
||||
|
||||
private Location buildLocationFromAuth(Player player, PlayerAuth auth) {
|
||||
World world = bukkitService.getWorld(auth.getWorld());
|
||||
if (world == null) {
|
||||
world = player.getWorld();
|
||||
}
|
||||
return new Location(world, auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ());
|
||||
}
|
||||
|
||||
private void teleportBackFromSpawn(final Player player, final Location location) {
|
||||
performTeleportation(player, new AuthMeTeleportEvent(player, location));
|
||||
}
|
||||
|
||||
private void teleportToSpawn(final Player player, final boolean isAuthenticated) {
|
||||
final Location spawnLoc = spawnLoader.getSpawnLocation(player);
|
||||
performTeleportation(player, new SpawnTeleportEvent(player, spawnLoc, isAuthenticated));
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the teleportation event and performs teleportation according to it (potentially modified
|
||||
* by external listeners). Note that not teleportation is performed if the event's location is empty.
|
||||
*
|
||||
* @param player the player to teleport
|
||||
* @param event the event to emit and according to which to teleport
|
||||
*/
|
||||
private void performTeleportation(final Player player, final AbstractTeleportEvent event) {
|
||||
bukkitService.scheduleSyncDelayedTask(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
bukkitService.callEvent(event);
|
||||
if (player.isOnline() && isEventValid(event)) {
|
||||
player.teleport(event.getTo());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isEventValid(AbstractTeleportEvent event) {
|
||||
return !event.isCancelled() && event.getTo() != null && event.getTo().getWorld() != null;
|
||||
}
|
||||
}
|
||||
@@ -16,26 +16,10 @@ public final class Utils {
|
||||
/** Number of milliseconds in an hour. */
|
||||
public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
|
||||
|
||||
// Utility class
|
||||
private Utils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get player's UUID if can, name otherwise.
|
||||
*
|
||||
* @param player Player to retrieve
|
||||
*
|
||||
* @return player's UUID or Name in String.
|
||||
*/
|
||||
public static String getUUIDorName(OfflinePlayer player) {
|
||||
// We may made this configurable in future
|
||||
// so we can have uuid support.
|
||||
try {
|
||||
return player.getUniqueId().toString();
|
||||
} catch (NoSuchMethodError ignore) {
|
||||
return player.getName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Pattern sneaky without throwing Exception.
|
||||
*
|
||||
@@ -52,17 +36,6 @@ public final class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IP of the given player.
|
||||
*
|
||||
* @param p The player to return the IP address for
|
||||
*
|
||||
* @return The player's IP address
|
||||
*/
|
||||
public static String getPlayerIp(Player p) {
|
||||
return p.getAddress().getAddress().getHostAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the class exists in the current class loader.
|
||||
*
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import com.github.authme.configme.properties.Property;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.initialization.Reloadable;
|
||||
import fr.xephi.authme.output.MessageKey;
|
||||
import fr.xephi.authme.permission.PermissionsManager;
|
||||
import fr.xephi.authme.permission.PlayerStatePermission;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.EmailSettings;
|
||||
import fr.xephi.authme.settings.properties.ProtectionSettings;
|
||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||
import fr.xephi.authme.settings.properties.SecuritySettings;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Validation service.
|
||||
*/
|
||||
public class ValidationService implements Reloadable {
|
||||
|
||||
@Inject
|
||||
private Settings settings;
|
||||
@Inject
|
||||
private DataSource dataSource;
|
||||
@Inject
|
||||
private PermissionsManager permissionsManager;
|
||||
@Inject
|
||||
private GeoLiteAPI geoLiteApi;
|
||||
|
||||
private Pattern passwordRegex;
|
||||
private Set<String> unrestrictedNames;
|
||||
|
||||
ValidationService() { }
|
||||
|
||||
@PostConstruct
|
||||
@Override
|
||||
public void reload() {
|
||||
passwordRegex = Utils.safePatternCompile(settings.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX));
|
||||
// Use Set for more efficient contains() lookup
|
||||
unrestrictedNames = new HashSet<>(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies whether a password is valid according to the plugin settings.
|
||||
*
|
||||
* @param password the password to verify
|
||||
* @param username the username the password is associated with
|
||||
* @return the validation result
|
||||
*/
|
||||
public ValidationResult validatePassword(String password, String username) {
|
||||
String passLow = password.toLowerCase();
|
||||
if (!passwordRegex.matcher(passLow).matches()) {
|
||||
return new ValidationResult(MessageKey.PASSWORD_CHARACTERS_ERROR, passwordRegex.pattern());
|
||||
} else if (passLow.equalsIgnoreCase(username)) {
|
||||
return new ValidationResult(MessageKey.PASSWORD_IS_USERNAME_ERROR);
|
||||
} else if (password.length() < settings.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)
|
||||
|| password.length() > settings.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)) {
|
||||
return new ValidationResult(MessageKey.INVALID_PASSWORD_LENGTH);
|
||||
} else if (settings.getProperty(SecuritySettings.UNSAFE_PASSWORDS).contains(passLow)) {
|
||||
return new ValidationResult(MessageKey.PASSWORD_UNSAFE_ERROR);
|
||||
}
|
||||
return new ValidationResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies whether the email is valid and admitted for use according to the plugin settings.
|
||||
*
|
||||
* @param email the email to verify
|
||||
* @return true if the email is valid, false otherwise
|
||||
*/
|
||||
public boolean validateEmail(String email) {
|
||||
if (!email.contains("@") || "your@email.com".equalsIgnoreCase(email)) {
|
||||
return false;
|
||||
}
|
||||
final String emailDomain = email.split("@")[1];
|
||||
return validateWhitelistAndBlacklist(
|
||||
emailDomain, EmailSettings.DOMAIN_WHITELIST, EmailSettings.DOMAIN_BLACKLIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the database whether the email is still free for registration, i.e. whether the given
|
||||
* command sender may use the email to register a new account (as defined by settings and permissions).
|
||||
*
|
||||
* @param email the email to verify
|
||||
* @param sender the command sender
|
||||
* @return true if the email may be used, false otherwise (registration threshold has been exceeded)
|
||||
*/
|
||||
public boolean isEmailFreeForRegistration(String email, CommandSender sender) {
|
||||
return permissionsManager.hasPermission(sender, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)
|
||||
|| dataSource.countAuthsByEmail(email) < settings.getProperty(EmailSettings.MAX_REG_PER_EMAIL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the player's country is allowed to join the server, based on the given IP address
|
||||
* and the configured country whitelist or blacklist.
|
||||
*
|
||||
* @param hostAddress the IP address to verify
|
||||
* @return true if the IP address' country is allowed, false otherwise
|
||||
*/
|
||||
public boolean isCountryAdmitted(String hostAddress) {
|
||||
// Check if we have restrictions on country, if not return true and avoid the country lookup
|
||||
if (settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST).isEmpty()
|
||||
&& settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST).isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String countryCode = geoLiteApi.getCountryCode(hostAddress);
|
||||
return validateWhitelistAndBlacklist(countryCode,
|
||||
ProtectionSettings.COUNTRIES_WHITELIST,
|
||||
ProtectionSettings.COUNTRIES_BLACKLIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the name is unrestricted according to the configured settings.
|
||||
*
|
||||
* @param name the name to verify
|
||||
* @return true if unrestricted, false otherwise
|
||||
*/
|
||||
public boolean isUnrestricted(String name) {
|
||||
return unrestrictedNames.contains(name.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies whether the given value is allowed according to the given whitelist and blacklist settings.
|
||||
* Whitelist has precedence over blacklist: if a whitelist is set, the value is rejected if not present
|
||||
* in the whitelist.
|
||||
*
|
||||
* @param value the value to verify
|
||||
* @param whitelist the whitelist property
|
||||
* @param blacklist the blacklist property
|
||||
* @return true if the value is admitted by the lists, false otherwise
|
||||
*/
|
||||
private boolean validateWhitelistAndBlacklist(String value, Property<List<String>> whitelist,
|
||||
Property<List<String>> blacklist) {
|
||||
List<String> whitelistValue = settings.getProperty(whitelist);
|
||||
if (!CollectionUtils.isEmpty(whitelistValue)) {
|
||||
return containsIgnoreCase(whitelistValue, value);
|
||||
}
|
||||
List<String> blacklistValue = settings.getProperty(blacklist);
|
||||
return CollectionUtils.isEmpty(blacklistValue) || !containsIgnoreCase(blacklistValue, value);
|
||||
}
|
||||
|
||||
private static boolean containsIgnoreCase(Collection<String> coll, String needle) {
|
||||
for (String entry : coll) {
|
||||
if (entry.equalsIgnoreCase(needle)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final class ValidationResult {
|
||||
private final MessageKey messageKey;
|
||||
private final String[] args;
|
||||
|
||||
/**
|
||||
* Constructor for a successful validation.
|
||||
*/
|
||||
public ValidationResult() {
|
||||
this.messageKey = null;
|
||||
this.args = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for a failed validation.
|
||||
*
|
||||
* @param messageKey message key of the validation error
|
||||
* @param args arguments for the message key
|
||||
*/
|
||||
public ValidationResult(MessageKey messageKey, String... args) {
|
||||
this.messageKey = messageKey;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether an error was found during the validation, i.e. whether the validation failed.
|
||||
*
|
||||
* @return true if there is an error, false if the validation was successful
|
||||
*/
|
||||
public boolean hasError() {
|
||||
return messageKey != null;
|
||||
}
|
||||
|
||||
public MessageKey getMessageKey() {
|
||||
return messageKey;
|
||||
}
|
||||
public String[] getArgs() {
|
||||
return args;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user