From 1af937651c4de0aa5a1decbabbc5fbbffe879c07 Mon Sep 17 00:00:00 2001 From: DNx5 Date: Sun, 15 Nov 2015 04:24:36 +0700 Subject: [PATCH 001/199] fix geoip lagging if server cant download the file. --- src/main/java/fr/xephi/authme/util/Utils.java | 99 ++++++++++--------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 938a0358..1075f2f8 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -1,26 +1,6 @@ package fr.xephi.authme.util; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLConnection; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.zip.GZIPInputStream; - -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; - import com.maxmind.geoip.LookupService; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerCache; @@ -28,6 +8,21 @@ import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.cache.limbo.LimboPlayer; import fr.xephi.authme.events.AuthMeTeleportEvent; import fr.xephi.authme.settings.Settings; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +import java.io.*; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.zip.GZIPInputStream; public class Utils { @@ -52,38 +47,46 @@ public class Utils { if (lookupService != null) { return true; } - ConsoleLogger.info("[LICENSE] This product uses data from the GeoLite API created by MaxMind, available at http://www.maxmind.com"); - File file = new File(Settings.PLUGIN_FOLDER, "GeoIP.dat"); - try { - if (file.exists()) { - if (lookupService == null) { - lookupService = new LookupService(file); + final File data = new File(Settings.PLUGIN_FOLDER, "GeoIP.dat"); + if (data.exists()) { + if (lookupService == null) { + try { + lookupService = new LookupService(data); + ConsoleLogger.info("[LICENSE] This product uses data from the GeoLite API created by MaxMind, available at http://www.maxmind.com"); return true; + } catch (IOException e) { + return false; } } - String url = "http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz"; - URL downloadUrl = new URL(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(file); - 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 (Exception e) { - ConsoleLogger.writeStackTrace(e); - return false; } - return checkGeoIP(); + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { + @Override + public void run() { + try { + String url = "http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz"; + URL downloadUrl = new URL(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(data); + 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.writeStackTrace(e); + } + } + }); + return false; } public static String getCountryCode(String ip) { From 6f1af92a585baf196f68552025f3415c18ad699f Mon Sep 17 00:00:00 2001 From: DNx5 Date: Sun, 15 Nov 2015 04:49:03 +0700 Subject: [PATCH 002/199] remove speed already done in join process --- .../authme/listener/AuthMePlayerListener.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index c393e509..51e1e8fb 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -130,31 +130,27 @@ public class AuthMePlayerListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onPlayerMove(PlayerMoveEvent event) { - if (Settings.isMovementAllowed && Settings.getMovementRadius <= 0) + int radius = Settings.getMovementRadius; + boolean allowMove = Settings.isMovementAllowed; + if (allowMove && radius <= 0) return; Player player = event.getPlayer(); if (Utils.checkAuth(player)) return; - if (!Settings.isMovementAllowed) { + if (!allowMove) { if (event.getFrom().distance(event.getTo()) > 0) { - if (Settings.isRemoveSpeedEnabled) { - player.setWalkSpeed(0.0f); - player.setFlySpeed(0.0f); - } event.setTo(event.getFrom()); return; } } - if (Settings.getMovementRadius <= 0) { + if (radius <= 0) { return; } - int radius = Settings.getMovementRadius; Location spawn = plugin.getSpawnLocation(player); - if (spawn != null && spawn.getWorld() != null) { if (!event.getPlayer().getWorld().equals(spawn.getWorld())) { event.getPlayer().teleport(spawn); From 4a49809ff299dcbb8a060c7fb4d2724a82bd402b Mon Sep 17 00:00:00 2001 From: Xephi Date: Mon, 16 Nov 2015 18:52:54 +0100 Subject: [PATCH 003/199] Change to is Empty --- src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java b/src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java index 73402e4d..dfcb295f 100644 --- a/src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java @@ -56,7 +56,7 @@ public class AsyncronousQuit { if (LimboCache.getInstance().hasLimboPlayer(name)) { LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(name); - if (limbo.getGroup() != null && !limbo.getGroup().equals("")) + if (limbo.getGroup() != null && !limbo.getGroup().isEmpty()) Utils.addNormal(player, limbo.getGroup()); needToChange = true; isOp = limbo.getOperator(); From f6c6a7c6d47f6df5528b08e600e8debf2047561b Mon Sep 17 00:00:00 2001 From: DNx5 Date: Tue, 17 Nov 2015 05:04:34 +0700 Subject: [PATCH 004/199] update and cleanup PlayerAuth --- .../xephi/authme/cache/auth/PlayerAuth.java | 281 +++++++----------- .../xephi/authme/cache/auth/PlayerCache.java | 2 +- 2 files changed, 108 insertions(+), 175 deletions(-) diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java index de9224e4..3046c9c0 100644 --- a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java +++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java @@ -5,60 +5,52 @@ import fr.xephi.authme.settings.Settings; public class PlayerAuth { - private String nickname = ""; - private String hash = ""; - private String ip = "192.168.0.1"; - private long lastLogin = 0; - private double x = 0; - private double y = 0; - private double z = 0; - private String world = "world"; - private String salt = ""; - private String vBhash = null; - private int groupId = -1; - private String email = "your@email.com"; + private String nickname; + private String hash; + private String ip; + private long lastLogin; + private double x; + private double y; + private double z; + private String world; + private String salt; + private int groupId; + private String email; private String realName; - public PlayerAuth(String nickname, String hash, String ip, long lastLogin, - String email, String realName) { - this.nickname = nickname; - this.hash = hash; - this.ip = ip; - this.lastLogin = lastLogin; - this.email = email; - this.realName = realName; + public PlayerAuth(String nickname, String ip, long lastLogin, String realName) { + this(nickname, "", "", -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); } - public PlayerAuth(String nickname, double x, double y, double z, - String world, String realName) { - this.nickname = nickname; - this.x = x; - this.y = y; - this.z = z; - this.world = world; - this.realName = realName; - this.lastLogin = System.currentTimeMillis(); - + public PlayerAuth(String nickname, double x, double y, double z, String world, String realName) { + this(nickname, "", "", -1, "127.0.0.1", System.currentTimeMillis(), x, y, z, world, "your@email.com", realName); } - public PlayerAuth(String nickname, String hash, String ip, long lastLogin, - double x, double y, double z, String world, String email, - String realName) { - this.nickname = nickname; - this.hash = hash; - this.ip = ip; - this.lastLogin = lastLogin; - this.x = x; - this.y = y; - this.z = z; - this.world = world; - this.email = email; - this.realName = realName; + public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String realName) { + this(nickname, hash, "", -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); } - public PlayerAuth(String nickname, String hash, String salt, int groupId, - String ip, long lastLogin, double x, double y, double z, - String world, String email, String realName) { + public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String email, String realName) { + this(nickname, hash, "", -1, ip, lastLogin, 0, 0, 0, "world", email, realName); + } + + public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, String realName) { + this(nickname, hash, salt, -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); + } + + public PlayerAuth(String nickname, String hash, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { + this(nickname, hash, "", -1, ip, lastLogin, x, y, z, world, email, realName); + } + + public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { + this(nickname, hash, salt, -1, ip, lastLogin, x, y, z, world, email, realName); + } + + public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip, long lastLogin, String realName) { + this(nickname, hash, salt, groupId, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); + } + + public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { this.nickname = nickname; this.hash = hash; this.ip = ip; @@ -73,146 +65,117 @@ public class PlayerAuth { this.realName = realName; } - public PlayerAuth(String nickname, String hash, String salt, int groupId, - String ip, long lastLogin, String realName) { - this.nickname = nickname; - this.hash = hash; - this.ip = ip; - this.lastLogin = lastLogin; - this.salt = salt; - this.groupId = groupId; - this.realName = realName; + public void set(PlayerAuth auth) { + this.setEmail(auth.getEmail()); + this.setHash(auth.getHash()); + this.setIp(auth.getIp()); + this.setLastLogin(auth.getLastLogin()); + this.setName(auth.getNickname()); + this.setQuitLocX(auth.getQuitLocX()); + this.setQuitLocY(auth.getQuitLocY()); + this.setQuitLocZ(auth.getQuitLocZ()); + this.setSalt(auth.getSalt()); + this.setWorld(auth.getWorld()); + this.setRealName(auth.getRealName()); } - public PlayerAuth(String nickname, String hash, String salt, String ip, - long lastLogin, String realName) { + public void setName(String nickname) { this.nickname = nickname; - this.hash = hash; - this.ip = ip; - this.lastLogin = lastLogin; - this.salt = salt; - this.realName = realName; - } - - public PlayerAuth(String nickname, String hash, String salt, String ip, - long lastLogin, double x, double y, double z, String world, - String email, String realName) { - this.nickname = nickname; - this.hash = hash; - this.ip = ip; - this.lastLogin = lastLogin; - this.x = x; - this.y = y; - this.z = z; - this.world = world; - this.salt = salt; - this.email = email; - this.realName = realName; - } - - public PlayerAuth(String nickname, String ip, long lastLogin, - String realName) { - this.nickname = nickname; - this.ip = ip; - this.lastLogin = lastLogin; - this.realName = realName; - } - - public PlayerAuth(String nickname, String hash, String ip, long lastLogin, - String realName) { - this.nickname = nickname; - this.ip = ip; - this.lastLogin = lastLogin; - this.hash = hash; - this.realName = realName; - } - - public String getIp() { - if (ip == null || ip.isEmpty()) - ip = "127.0.0.1"; - return ip; } public String getNickname() { return nickname; } - public String getHash() { - if (Settings.getPasswordHash == HashAlgorithm.MD5VB) { - if (salt != null && !salt.isEmpty() && Settings.getPasswordHash == HashAlgorithm.MD5VB) { - vBhash = "$MD5vb$" + salt + "$" + hash; - return vBhash; - } - } - return hash; + public String getRealName() { + return realName; } - public String getSalt() { - return this.salt; + public void setRealName(String realName) { + this.realName = realName; } public int getGroupId() { return groupId; } - public double getQuitLocX() { - return x; - } - - public double getQuitLocY() { - return y; - } - - public double getQuitLocZ() { - return z; - } - - public String getEmail() { - return email; - } - public void setQuitLocX(double d) { this.x = d; } + public double getQuitLocX() { + return x; + } + public void setQuitLocY(double d) { this.y = d; } + public double getQuitLocY() { + return y; + } + public void setQuitLocZ(double d) { this.z = d; } - public long getLastLogin() { - try { - if (Long.valueOf(lastLogin) == null) - lastLogin = 0L; - } catch (NullPointerException e) { - lastLogin = 0L; - } - return lastLogin; + public double getQuitLocZ() { + return z; } - public void setHash(String hash) { - this.hash = hash; + public void setWorld(String world) { + this.world = world; + } + + public String getWorld() { + return world; } public void setIp(String ip) { this.ip = ip; } + public String getIp() { + return ip; + } + public void setLastLogin(long lastLogin) { this.lastLogin = lastLogin; } + public long getLastLogin() { + return lastLogin; + } + public void setEmail(String email) { this.email = email; } + public String getEmail() { + return email; + } + public void setSalt(String salt) { this.salt = salt; } + public String getSalt() { + return this.salt; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public String getHash() { + if (Settings.getPasswordHash == HashAlgorithm.MD5VB) { + if (salt != null && !salt.isEmpty() && Settings.getPasswordHash == HashAlgorithm.MD5VB) { + return "$MD5vb$" + salt + "$" + hash; + } + } + return hash; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof PlayerAuth)) { @@ -230,45 +193,15 @@ public class PlayerAuth { return hashCode; } - public void setWorld(String world) { - this.world = world; - } - - public String getWorld() { - return world; - } - @Override public String toString() { - String s = "Player : " + nickname + " | " + realName + " ! IP : " + ip + " ! LastLogin : " + lastLogin + " ! LastPosition : " + x + "," + y + "," + z + "," + world + " ! Email : " + email + " ! Hash : " + hash + " ! Salt : " + salt; - return s; - - } - - public void setName(String nickname) { - this.nickname = nickname; - } - - public void set(PlayerAuth auth) { - this.setEmail(auth.getEmail()); - this.setHash(auth.getHash()); - this.setIp(auth.getIp()); - this.setLastLogin(auth.getLastLogin()); - this.setName(auth.getNickname()); - this.setQuitLocX(auth.getQuitLocX()); - this.setQuitLocY(auth.getQuitLocY()); - this.setQuitLocZ(auth.getQuitLocZ()); - this.setSalt(auth.getSalt()); - this.setWorld(auth.getWorld()); - this.setRealName(auth.getRealName()); - } - - public String getRealName() { - return realName; - } - - public void setRealName(String realName) { - this.realName = realName; + return ("Player : " + nickname + " | " + realName + + " ! IP : " + ip + + " ! LastLogin : " + lastLogin + + " ! LastPosition : " + x + "," + y + "," + z + "," + world + + " ! Email : " + email + + " ! Hash : " + hash + + " ! Salt : " + salt); } } diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java index 491c33a4..d8b0f404 100644 --- a/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java +++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java @@ -4,7 +4,7 @@ import java.util.concurrent.ConcurrentHashMap; public class PlayerCache { - private volatile static PlayerCache singleton = null; + private volatile static PlayerCache singleton; private ConcurrentHashMap cache; private PlayerCache() { From 6bc5bb3cb1bd7ac0b3d3a19d9b625a8dc38697e7 Mon Sep 17 00:00:00 2001 From: DNx5 Date: Tue, 17 Nov 2015 06:54:24 +0700 Subject: [PATCH 005/199] optimize some code. --- .../authme/listener/AuthMePlayerListener.java | 8 +- .../authme/process/join/AsyncronousJoin.java | 118 ++++++++---------- 2 files changed, 58 insertions(+), 68 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 51e1e8fb..c9cf76b0 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -201,20 +201,22 @@ public class AuthMePlayerListener implements Listener { return; final Player player = event.getPlayer(); - String name = player.getName().toLowerCase(); + final String name = player.getName().toLowerCase(); + final String joinMsg = event.getJoinMessage(); + final boolean delay = Settings.delayJoinLeaveMessages && joinMsg != null; // Remove the join message while the player isn't logging in if (Settings.delayJoinLeaveMessages && event.getJoinMessage() != null) { - joinMessage.put(name, event.getJoinMessage()); event.setJoinMessage(null); } // Shedule login task so works after the prelogin // (Fix found by Koolaid5000) Bukkit.getScheduler().runTask(plugin, new Runnable() { - @Override public void run() { + if (delay) + joinMessage.put(name, joinMsg); plugin.management.performJoin(player); } }); diff --git a/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java index f4831e96..d4678ec1 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java @@ -4,7 +4,6 @@ import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; @@ -31,24 +30,26 @@ import fr.xephi.authme.util.Utils.GroupType; public class AsyncronousJoin { - protected Player player; - protected DataSource database; - protected AuthMe plugin; - protected String name; - private Messages m = Messages.getInstance(); + private final AuthMe plugin; + private final Player player; + private final DataSource database; + private final String name; + private final Messages m; + private final BukkitScheduler sched; public AsyncronousJoin(Player player, AuthMe plugin, DataSource database) { this.player = player; this.plugin = plugin; + this.sched = plugin.getServer().getScheduler(); this.database = database; this.name = player.getName().toLowerCase(); + this.m = Messages.getInstance(); } public void process() { if (AuthMePlayerListener.gameMode.containsKey(name)) AuthMePlayerListener.gameMode.remove(name); AuthMePlayerListener.gameMode.putIfAbsent(name, player.getGameMode()); - BukkitScheduler sched = plugin.getServer().getScheduler(); if (Utils.isNPC(player) || Utils.isUnrestricted(player)) { return; @@ -166,7 +167,7 @@ public class AsyncronousJoin { return; } if (!Settings.noTeleport) - if (!needFirstspawn() && Settings.isTeleportToSpawnEnabled || (Settings.isForceSpawnLocOnJoinEnabled && Settings.getForcedWorlds.contains(player.getWorld().getName()))) { + if (!needFirstSpawn() && Settings.isTeleportToSpawnEnabled || (Settings.isForceSpawnLocOnJoinEnabled && Settings.getForcedWorlds.contains(player.getWorld().getName()))) { sched.scheduleSyncDelayedTask(plugin, new Runnable() { @Override @@ -185,42 +186,34 @@ public class AsyncronousJoin { } } - String[] msg; - if (Settings.emailRegistration) { - msg = isAuthAvailable ? m.send("login_msg") : m.send("reg_email_msg"); - } else { - msg = isAuthAvailable ? m.send("login_msg") : m.send("reg_msg"); + + if (!LimboCache.getInstance().hasLimboPlayer(name)) { + LimboCache.getInstance().addLimboPlayer(player); } - int time = Settings.getRegistrationTimeout * 20; + + final int timeOut = Settings.getRegistrationTimeout * 20; int msgInterval = Settings.getWarnMessageInterval; - if (time != 0) { - BukkitTask id = sched.runTaskLaterAsynchronously(plugin, new TimeoutTask(plugin, name, player), time); - if (!LimboCache.getInstance().hasLimboPlayer(name)) - LimboCache.getInstance().addLimboPlayer(player); + if (timeOut > 0) { + BukkitTask id = sched.runTaskLaterAsynchronously(plugin, new TimeoutTask(plugin, name, player), timeOut); LimboCache.getInstance().getLimboPlayer(name).setTimeoutTaskId(id); } - if (!LimboCache.getInstance().hasLimboPlayer(name)) - LimboCache.getInstance().addLimboPlayer(player); - if (isAuthAvailable) { - Utils.setGroup(player, GroupType.NOTLOGGEDIN); - } else { - Utils.setGroup(player, GroupType.UNREGISTERED); - } - sched.scheduleSyncDelayedTask(plugin, new Runnable() { + Utils.setGroup(player, isAuthAvailable ? GroupType.NOTLOGGEDIN : GroupType.UNREGISTERED); + sched.scheduleSyncDelayedTask(plugin, new Runnable() { @Override public void run() { - if (player.isOp()) - player.setOp(false); - if (player.getGameMode() != GameMode.CREATIVE && !Settings.isMovementAllowed) { + player.setOp(false); + if (!Settings.isMovementAllowed) { player.setAllowFlight(true); player.setFlying(true); } - player.setNoDamageTicks(Settings.getRegistrationTimeout * 20); - if (Settings.useEssentialsMotd) + player.setNoDamageTicks(timeOut); + if (Settings.useEssentialsMotd) { player.performCommand("motd"); - if (Settings.applyBlindEffect) - player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, Settings.getRegistrationTimeout * 20, 2)); + } + if (Settings.applyBlindEffect) { + player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeOut, 2)); + } if (!Settings.isMovementAllowed && Settings.isRemoveSpeedEnabled) { player.setWalkSpeed(0.0f); player.setFlySpeed(0.0f); @@ -228,33 +221,37 @@ public class AsyncronousJoin { } }); + if (Settings.isSessionsEnabled && isAuthAvailable && (PlayerCache.getInstance().isAuthenticated(name) || database.isLogged(name))) { - if (plugin.sessions.containsKey(name)) + if (plugin.sessions.containsKey(name)) { plugin.sessions.get(name).cancel(); - plugin.sessions.remove(name); + plugin.sessions.remove(name); + } PlayerAuth auth = database.getAuth(name); + database.setUnlogged(name); + PlayerCache.getInstance().removePlayer(name); if (auth != null && auth.getIp().equals(ip)) { m.send(player, "valid_session"); - PlayerCache.getInstance().removePlayer(name); - database.setUnlogged(name); plugin.management.performLogin(player, "dontneed", true); + return; } else if (Settings.sessionExpireOnIpChange) { - PlayerCache.getInstance().removePlayer(name); - database.setUnlogged(name); m.send(player, "invalid_session"); } - return; } - BukkitTask msgT = sched.runTaskAsynchronously(plugin, new MessageTask(plugin, name, msg, msgInterval)); - LimboCache.getInstance().getLimboPlayer(name).setMessageTaskId(msgT); + + String[] msg = isAuthAvailable ? m.send("login_msg") : + m.send("reg_" + (Settings.emailRegistration? "email_" : "") + "msg"); + BukkitTask msgTask = sched.runTaskAsynchronously(plugin, new MessageTask(plugin, name, msg, msgInterval)); + LimboCache.getInstance().getLimboPlayer(name).setMessageTaskId(msgTask); } - private boolean needFirstspawn() { + private boolean needFirstSpawn() { if (player.hasPlayedBefore()) return false; - if (Spawn.getInstance().getFirstSpawn() == null || Spawn.getInstance().getFirstSpawn().getWorld() == null) + Location firstspawn = Spawn.getInstance().getFirstSpawn(); + if (firstspawn == null || firstspawn.getWorld() == null) return false; - FirstSpawnTeleportEvent tpEvent = new FirstSpawnTeleportEvent(player, player.getLocation(), Spawn.getInstance().getFirstSpawn()); + FirstSpawnTeleportEvent tpEvent = new FirstSpawnTeleportEvent(player, player.getLocation(), firstspawn); plugin.getServer().getPluginManager().callEvent(tpEvent); if (!tpEvent.isCancelled()) { if (player.isOnline() && tpEvent.getTo() != null && tpEvent.getTo().getWorld() != null) { @@ -270,11 +267,9 @@ public class AsyncronousJoin { } } return true; - } - private void placePlayerSafely(final Player player, - final Location spawnLoc) { + private void placePlayerSafely(final Player player, final Location spawnLoc) { if (spawnLoc == null) return; if (!Settings.noTeleport) @@ -283,26 +278,19 @@ public class AsyncronousJoin { return; if (!player.hasPlayedBefore()) return; - Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { - + sched.scheduleSyncDelayedTask(plugin, new Runnable() { @Override public void run() { - Location loc = null; - Block b = player.getLocation().getBlock(); - if (b.getType() == Material.PORTAL || b.getType() == Material.ENDER_PORTAL) { - m.send(player, "unsafe_spawn"); - if (spawnLoc.getWorld() != null) - loc = spawnLoc; - } else { - Block c = player.getLocation().add(0D, 1D, 0D).getBlock(); - if (c.getType() == Material.PORTAL || c.getType() == Material.ENDER_PORTAL) { - m.send(player, "unsafe_spawn"); - if (spawnLoc.getWorld() != null) - loc = spawnLoc; - } + if (spawnLoc.getWorld() == null) { + return; + } + Material cur = player.getLocation().getBlock().getType(); + Material top = player.getLocation().add(0D, 1D, 0D).getBlock().getType(); + if (cur == Material.PORTAL || cur == Material.ENDER_PORTAL + || top == Material.PORTAL || top == Material.ENDER_PORTAL) { + m.send(player, "unsafe_spawn"); + player.teleport(spawnLoc); } - if (loc != null) - player.teleport(loc); } }); From 3e1c7edb2dcda486fe9d958f8aedaf28e0cfa4dd Mon Sep 17 00:00:00 2001 From: DNx5 Date: Tue, 17 Nov 2015 08:47:14 +0700 Subject: [PATCH 006/199] reorder isAuthAvailable call --- .../fr/xephi/authme/listener/AuthMePlayerListener.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index c9cf76b0..69e1617a 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -252,18 +252,20 @@ public class AuthMePlayerListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerLogin(PlayerLoginEvent event) { final Player player = event.getPlayer(); - if (player == null) + if (player == null) { return; - final String name = player.getName().toLowerCase(); - boolean isAuthAvailable = plugin.database.isAuthAvailable(name); + } + final String name = player.getName().toLowerCase(); if (Utils.isNPC(player) || Utils.isUnrestricted(player)) { return; } - if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) + if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { return; + } + boolean isAuthAvailable = plugin.database.isAuthAvailable(name); if (!Settings.countriesBlacklist.isEmpty() && !isAuthAvailable && !plugin.authmePermissible(player, "authme.bypassantibot")) { String code = Utils.getCountryCode(event.getAddress().getHostAddress()); if (((code == null) || Settings.countriesBlacklist.contains(code))) { From 9a79332933ad6c2374578c44114684487954413d Mon Sep 17 00:00:00 2001 From: DNx5 Date: Tue, 17 Nov 2015 09:32:33 +0700 Subject: [PATCH 007/199] add getPlayer method into Utils class --- .../java/fr/xephi/authme/listener/AuthMePlayerListener.java | 6 +++--- src/main/java/fr/xephi/authme/util/Utils.java | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 69e1617a..bcc5bc8f 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -225,10 +225,10 @@ public class AuthMePlayerListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onPreLogin(AsyncPlayerPreLoginEvent event) { final String name = event.getName().toLowerCase(); - @SuppressWarnings("deprecation") - final Player player = plugin.getServer().getPlayer(name); - if (player == null) + final Player player = Utils.getPlayer(name); + if (player == null) { return; + } // Check if forceSingleSession is set to true, so kick player that has // joined with same nick of online player diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 1075f2f8..2775f694 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -273,6 +273,12 @@ public class Utils { return Collections.emptyList(); } + @SuppressWarnings("deprecation") + public static Player getPlayer(String name) { + name = name.toLowerCase(); + return plugin.getServer().getPlayer(name); + } + public static boolean isNPC(final Entity player) { try { if (player.hasMetadata("NPC")) { From e3c4655b19cffe75c156b7fd9b2027e6c442c4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 19:43:10 +0100 Subject: [PATCH 008/199] Added new permissions manager class, based on DungeonMaze's one --- .../authme/permission/PermissionsManager.java | 442 ++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 src/main/java/fr/xephi/authme/permission/PermissionsManager.java diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java new file mode 100644 index 00000000..dd894559 --- /dev/null +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -0,0 +1,442 @@ +package fr.xephi.authme.permission; + +import com.nijiko.permissions.PermissionHandler; +import com.nijikokun.bukkit.Permissions.Permissions; +import de.bananaco.bpermissions.api.ApiLayer; +import de.bananaco.bpermissions.api.CalculableType; +import net.milkbowl.vault.permission.Permission; +import org.anjocaido.groupmanager.GroupManager; +import org.anjocaido.groupmanager.permissions.AnjoPermissionsHandler; +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.PluginEnableEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService; +import ru.tehkode.permissions.PermissionManager; +import ru.tehkode.permissions.PermissionUser; +import ru.tehkode.permissions.bukkit.PermissionsEx; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +public class PermissionsManager { + + /** + * Server instance. + */ + private Server server; + /** + * Plugin instance. + */ + private Plugin plugin; + /** + * Logger instance. + */ + private Logger log; + + /** + * Type of permissions system that is currently used. + */ + private PermissionsSystemType permsType = PermissionsSystemType.NONE; + + /** + * Essentials group manager instance. + */ + private GroupManager groupManagerPerms; + /** + * Permissions manager instance for the legacy permissions system. + */ + private PermissionHandler defaultPerms; + /** + * zPermissions service instance. + */ + private ZPermissionsService zPermissionsService; + /** + * Vault instance. + */ + public Permission vaultPerms = null; + + /** + * Constructor. + * + * @param server Server instance + * @param plugin Plugin instance + * @param log Logger + */ + public PermissionsManager(Server server, Plugin plugin, Logger log) { + this.server = server; + this.plugin = plugin; + this.log = log; + } + + /** + * Check if the permissions manager is currently hooked into any of the supported permissions systems. + * + * @return False if there isn't any permissions system used. + */ + public boolean isEnabled() { + return !permsType.equals(PermissionsSystemType.NONE); + } + + /** + * Return the permissions system where the permissions manager is currently hooked into. + * + * @return Permissions system type. + */ + public PermissionsSystemType getUsedPermissionsSystemType() { + return this.permsType; + } + + /** + * Setup and hook into the permissions systems. + * + * @return The detected permissions system. + */ + public PermissionsSystemType setup() { + // Define the plugin manager + final PluginManager pm = this.server.getPluginManager(); + + // Reset used permissions system type + permsType = PermissionsSystemType.NONE; + + // PermissionsEx, check if it's available + try { + Plugin pex = pm.getPlugin("PermissionsEx"); + if(pex != null) { + PermissionManager pexPerms = PermissionsEx.getPermissionManager(); + if(pexPerms != null) { + permsType = PermissionsSystemType.PERMISSIONS_EX; + + System.out.println("[" + plugin.getName() + "] Hooked into PermissionsEx!"); + return permsType; + } + } + } catch(Exception ex) { + // An error occurred, show a warning message + System.out.println("[" + plugin.getName() + "] Error while hooking into PermissionsEx!"); + } + + // PermissionsBukkit, check if it's available + try { + Plugin bukkitPerms = pm.getPlugin("PermissionsBukkit"); + if(bukkitPerms != null) { + permsType = PermissionsSystemType.PERMISSIONS_BUKKIT; + System.out.println("[" + plugin.getName() + "] Hooked into PermissionsBukkit!"); + return permsType; + } + } catch(Exception ex) { + // An error occurred, show a warning message + System.out.println("[" + plugin.getName() + "] Error while hooking into PermissionsBukkit!"); + } + + // bPermissions, check if it's available + try { + Plugin bPerms = pm.getPlugin("bPermissions"); + if(bPerms != null) { + permsType = PermissionsSystemType.B_PERMISSIONS; + System.out.println("[" + plugin.getName() + "] Hooked into bPermissions!"); + return permsType; + } + } catch(Exception ex) { + // An error occurred, show a warning message + System.out.println("[" + plugin.getName() + "] Error while hooking into bPermissions!"); + } + + // Essentials Group Manager, check if it's available + try { + final Plugin groupManagerPlugin = pm.getPlugin("GroupManager"); + if(groupManagerPlugin != null && groupManagerPlugin.isEnabled()) { + permsType = PermissionsSystemType.ESSENTIALS_GROUP_MANAGER; + groupManagerPerms = (GroupManager) groupManagerPlugin; + System.out.println("[" + plugin.getName() + "] Hooked into Essentials Group Manager!"); + return permsType; + } + } catch(Exception ex) { + // An error occurred, show a warning message + System.out.println("[" + plugin.getName() + "] Error while hooking into Essentials Group Manager!"); + } + + // zPermissions, check if it's available + try { + Plugin zPerms = pm.getPlugin("zPermissions"); + if(zPerms != null) { + zPermissionsService = Bukkit.getServicesManager().load(ZPermissionsService.class); + if(zPermissionsService != null) { + permsType = PermissionsSystemType.Z_PERMISSIONS; + System.out.println("[" + plugin.getName() + "] Hooked into zPermissions!"); + return permsType; + } + } + } catch(Exception ex) { + // An error occurred, show a warning message + System.out.println("[" + plugin.getName() + "] Error while hooking into zPermissions!"); + } + + // Vault, check if it's available + try { + final Plugin vaultPlugin = pm.getPlugin("Vault"); + if(vaultPlugin != null && vaultPlugin.isEnabled()) { + RegisteredServiceProvider permissionProvider = this.server.getServicesManager().getRegistration(Permission.class); + if(permissionProvider != null) { + vaultPerms = permissionProvider.getProvider(); + if(vaultPerms.isEnabled()) { + permsType = PermissionsSystemType.VAULT; + System.out.println("[" + plugin.getName() + "] Hooked into Vault Permissions!"); + return permsType; + } else { + System.out.println("[" + plugin.getName() + "] Not using Vault Permissions, Vault Permissions is disabled!"); + } + } + } + } catch(Exception ex) { + // An error occurred, show a warning message + System.out.println("[" + plugin.getName() + "] Error while hooking into Vault Permissions!"); + } + + // Permissions, check if it's available + try { + Plugin testPerms = pm.getPlugin("Permissions"); + if(testPerms != null) { + permsType = PermissionsSystemType.PERMISSIONS; + this.defaultPerms = ((Permissions) testPerms).getHandler(); + System.out.println("[" + plugin.getName() + "] Hooked into Permissions!"); + return PermissionsSystemType.PERMISSIONS; + } + } catch(Exception ex) { + // An error occurred, show a warning message + System.out.println("[" + plugin.getName() + "] Error while hooking into Permissions!"); + } + + // No recognized permissions system found + permsType = PermissionsSystemType.NONE; + System.out.println("[" + plugin.getName() + "] No supported permissions system found! Permissions disabled!"); + return PermissionsSystemType.NONE; + } + + /** + * Break the hook with all permission systems. + */ + public void unhook() { + // Reset the current used permissions system + this.permsType = PermissionsSystemType.NONE; + + // Print a status message to the console + this.log.info("Unhooked from Permissions!"); + } + + /** + * Reload the permissions manager, and re-hook all permission plugins. + * + * @return True on success, false on failure. + */ + public boolean reload() { + // Unhook all permission plugins + unhook(); + + // Set up the permissions manager again, return the result + setup(); + return true; + } + + /** + * Method called when a plugin is being enabled. + * + * @param event Event instance. + */ + public void onPluginEnable(PluginEnableEvent event) { + Plugin p = event.getPlugin(); + String pn = p.getName(); + + // Check if any known permissions system is enabling + if(pn.equals("PermissionsEx") || pn.equals("PermissionsBukkit") || + pn.equals("bPermissions") || pn.equals("GroupManager") || + pn.equals("zPermissions") || pn.equals("Vault") || + pn.equals("Permissions")) { + this.log.info(pn + " plugin enabled, updating hooks!"); + setup(); + } + } + + /** + * Method called when a plugin is being disabled. + * + * @param event Event instance. + */ + public void onPluginDisable(PluginDisableEvent event) { + // Get the plugin instance and name + Plugin plugin = event.getPlugin(); + String pluginName = plugin.getName(); + + // Is the WorldGuard plugin disabled + if(pluginName.equals("PermissionsEx") || pluginName.equals("PermissionsBukkit") || + pluginName.equals("bPermissions") || pluginName.equals("GroupManager") || + pluginName.equals("zPermissions") || pluginName.equals("Vault") || + pluginName.equals("Permissions")) { + this.log.info(pluginName + " plugin disabled, updating hooks!"); + setup(); + } + } + + /** + * Get the logger instance. + * + * @return Logger instance. + */ + public Logger getLogger() { + return this.log; + } + + /** + * Set the logger instance. + * + * @param log Logger instance. + */ + public void setLogger(Logger log) { + this.log = log; + } + + /** + * Check if the player has permission. If no permissions system is used, the player has to be OP. + * + * @param player The player. + * @param permsNode Permissions node. + * + * @return True if the player has permission. + */ + public boolean hasPermission(Player player, String permsNode) { + return hasPermission(player, permsNode, player.isOp()); + } + + /** + * Check if a player has permission. + * + * @param player The player. + * @param permsNode The permission node. + * @param def Default returned if no permissions system is used. + * + * @return True if the player has permission. + */ + public boolean hasPermission(Player player, String permsNode, boolean def) { + if(!isEnabled()) + // No permissions system is used, return default + return def; + + switch(this.permsType) { + case PERMISSIONS_EX: + // Permissions Ex + PermissionUser user = PermissionsEx.getUser(player); + return user.has(permsNode); + + case PERMISSIONS_BUKKIT: + // Permissions Bukkit + return player.hasPermission(permsNode); + + case B_PERMISSIONS: + // bPermissions + return ApiLayer.hasPermission(player.getWorld().getName(), CalculableType.USER, player.getName(), permsNode); + + case ESSENTIALS_GROUP_MANAGER: + // Essentials Group Manager + final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); + return handler != null && handler.has(player, permsNode); + + case Z_PERMISSIONS: + // zPermissions + Map perms = zPermissionsService.getPlayerPermissions(player.getWorld().getName(), null, player.getName()); + if(perms.containsKey(permsNode)) + return perms.get(permsNode); + else + return def; + + case VAULT: + // Vault + return vaultPerms.has(player, permsNode); + + case PERMISSIONS: + // Permissions + return this.defaultPerms.has(player, permsNode); + + case NONE: + // Not hooked into any permissions system, return default + return def; + + default: + // Something went wrong, return false to prevent problems + return false; + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public List getGroups(Player player) { + if(!isEnabled()) + // No permissions system is used, return an empty list + return new ArrayList<>(); + + switch(this.permsType) { + case PERMISSIONS_EX: + // Permissions Ex + PermissionUser user = PermissionsEx.getUser(player); + return user.getParentIdentifiers(null); + + case PERMISSIONS_BUKKIT: + // Permissions Bukkit + // Permissions Bukkit doesn't support group, return an empty list + return new ArrayList<>(); + + case B_PERMISSIONS: + // bPermissions + return Arrays.asList(ApiLayer.getGroups(player.getName(), CalculableType.USER, player.getName())); + + case ESSENTIALS_GROUP_MANAGER: + // Essentials Group Manager + final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); + if(handler == null) + return new ArrayList<>(); + return Arrays.asList(handler.getGroups(player.getName())); + + case Z_PERMISSIONS: + //zPermissions + return new ArrayList(zPermissionsService.getPlayerGroups(player.getName())); + + case VAULT: + // Vault + return Arrays.asList(vaultPerms.getPlayerGroups(player)); + + case NONE: + // Not hooked into any permissions system, return an empty list + return new ArrayList<>(); + + default: + // Something went wrong, return an empty list to prevent problems + return new ArrayList<>(); + } + } + + public enum PermissionsSystemType { + NONE("None"), + PERMISSIONS_EX("PermissionsEx"), + PERMISSIONS_BUKKIT("Permissions Bukkit"), + B_PERMISSIONS("bPermissions"), + ESSENTIALS_GROUP_MANAGER("Essentials Group Manager"), + Z_PERMISSIONS("zPermissions"), + VAULT("Vault"), + PERMISSIONS("Permissions"); + + public String name; + + PermissionsSystemType(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + } +} \ No newline at end of file From a629d999224f80498692b73b034fbe0a9e12eef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 19:46:03 +0100 Subject: [PATCH 009/199] Added required permissions libraries --- lib/EssentialsGroupManager-2.13.1.jar | Bin 0 -> 124043 bytes lib/Permission-3.1.6.jar | Bin 0 -> 78579 bytes lib/zPermissions-1.3beta1.jar | Bin 0 -> 453074 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/EssentialsGroupManager-2.13.1.jar create mode 100644 lib/Permission-3.1.6.jar create mode 100644 lib/zPermissions-1.3beta1.jar diff --git a/lib/EssentialsGroupManager-2.13.1.jar b/lib/EssentialsGroupManager-2.13.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..245bcc14417bb6caf31e77e0ffb3e72e73dc97f8 GIT binary patch literal 124043 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

0VzCz8>`YC-0z!?>eh$ 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

c$iPs;^FdZ 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 literal 0 HcmV?d00001 diff --git a/lib/Permission-3.1.6.jar b/lib/Permission-3.1.6.jar new file mode 100644 index 0000000000000000000000000000000000000000..31c3e5d9945be41fadcbb4bf068a28b08f684e90 GIT binary patch literal 78579 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 literal 0 HcmV?d00001 diff --git a/lib/zPermissions-1.3beta1.jar b/lib/zPermissions-1.3beta1.jar new file mode 100644 index 0000000000000000000000000000000000000000..c00f88733293c30e7d4b0bf4e004ead60c25818b GIT binary patch literal 453074 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%0DkmQ<<%BX_xES^naTiwpFVqI#`8*{b(U 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�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

4&X@AYF6IZxT2B?b7B$R;{s5H37Xt-YPf$zJ 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

D#oT7l<=Kq>17MNZtM|%_QxMQeE*LgFRIT+a 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Μc_)LKl=D`>8F&qlh0_LvbzYxx^8yA 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 X3bq5!0=DRG;oh Date: Fri, 20 Nov 2015 19:48:48 +0100 Subject: [PATCH 010/199] Added required permissions library for bPermissions --- lib/bPermissions-2.12-DEV.jar | Bin 0 -> 123792 bytes pom.xml | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 lib/bPermissions-2.12-DEV.jar diff --git a/lib/bPermissions-2.12-DEV.jar b/lib/bPermissions-2.12-DEV.jar new file mode 100644 index 0000000000000000000000000000000000000000..2b63156b95dcc3f46bf1d9cc3758e21af9e499ea GIT binary patch literal 123792 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%0A;~AF&+y&36YXlq)>6zpm@eTBySI1o%0|IYG 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 literal 0 HcmV?d00001 diff --git a/pom.xml b/pom.xml index 7547c90b..d5781572 100644 --- a/pom.xml +++ b/pom.xml @@ -317,6 +317,34 @@ + + + org.anjocaido + groupmanager + 2.13.1 + system + ${basedir}/lib/EssentialsGroupManager-2.13.1.jar + + + + + + org.tyrannyofheaven.bukkit + zPermissions + 1.3-SNAPSHOT + system + ${basedir}/lib/zPermissions-1.3beta1.jar + + + + + com.nijiko + permissions + 3.1.6 + system + ${basedir}/lib/Permission-3.1.6.jar + + net.milkbowl.vault From facc09a9336ff80f3c135865e4893ded6dc2b7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 19:49:55 +0100 Subject: [PATCH 011/199] Added PermissionsEx repository --- pom.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pom.xml b/pom.xml index d5781572..3a68461e 100644 --- a/pom.xml +++ b/pom.xml @@ -180,6 +180,12 @@ xephi-repo http://ci.xephi.fr/plugin/repository/everything/ + + + + pex-repo + http://pex-repo.aoeu.xyz/ + @@ -326,6 +332,14 @@ ${basedir}/lib/EssentialsGroupManager-2.13.1.jar + + + de.bananaco + bpermissions + 1.12-DEV + system + ${basedir}/lib/bPermissions-2.12-DEV.jar + From fa28bb6d201b8348bc1bcd843ccc0bcde5993a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 19:50:20 +0100 Subject: [PATCH 012/199] Added PermissionsEx dependency --- pom.xml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pom.xml b/pom.xml index 3a68461e..3dcdaa12 100644 --- a/pom.xml +++ b/pom.xml @@ -323,6 +323,32 @@ + + + ru.tehkode + PermissionsEx + 1.23.1 + provided + + + org.bukkit + bukkit + + + net.gravitydevelopment.updater + updater + + + commons-dbcp + commons-dbcp + + + AccountsClient + com.mojang + + + + org.anjocaido From 66365d6cf91dd1a8d8d72dbed64839fabf3b2ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 19:53:00 +0100 Subject: [PATCH 013/199] Removed unused code --- src/main/java/fr/xephi/authme/AuthMe.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index d8d80afc..f6804bf1 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -339,20 +339,6 @@ public class AuthMe extends JavaPlugin { pm.registerEvents(new AuthMeEntityListener(this), this); pm.registerEvents(new AuthMeServerListener(this), this); - // TODO: This is moved to CommandManager.registerCommands() handled by - // AuthMe.onCommand() -- timvisee - // Register commands - // getCommand("authme").setExecutor(new AdminCommand(this)); - // getCommand("register").setExecutor(new RegisterCommand(this)); - // getCommand("login").setExecutor(new LoginCommand(this)); - // getCommand("changepassword").setExecutor(new - // ChangePasswordCommand(this)); - // getCommand("logout").setExecutor(new LogoutCommand(this)); - // getCommand("unregister").setExecutor(new UnregisterCommand(this)); - // getCommand("email").setExecutor(new EmailCommand(this)); - // getCommand("captcha").setExecutor(new CaptchaCommand(this)); - // getCommand("converter").setExecutor(new ConverterCommand(this)); - // Purge on start if enabled autoPurge(); From 5df1dc2dfb9e1292f67764ce356395fa7f9f2aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 19:55:19 +0100 Subject: [PATCH 014/199] Created plugin listener --- src/main/java/fr/xephi/authme/AuthMe.java | 9 +-- .../authme/listener/AuthMePluginListener.java | 66 +++++++++++++++++++ 2 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index f6804bf1..a4eb6d41 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; +import fr.xephi.authme.listener.*; import org.apache.logging.log4j.LogManager; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -47,13 +48,6 @@ import fr.xephi.authme.datasource.MySQL; import fr.xephi.authme.datasource.SQLite; import fr.xephi.authme.hooks.BungeeCordMessage; import fr.xephi.authme.hooks.EssSpawn; -import fr.xephi.authme.listener.AuthMeBlockListener; -import fr.xephi.authme.listener.AuthMeEntityListener; -import fr.xephi.authme.listener.AuthMeInventoryPacketAdapter; -import fr.xephi.authme.listener.AuthMePlayerListener; -import fr.xephi.authme.listener.AuthMePlayerListener16; -import fr.xephi.authme.listener.AuthMePlayerListener18; -import fr.xephi.authme.listener.AuthMeServerListener; import fr.xephi.authme.modules.ModuleManager; import fr.xephi.authme.process.Management; import fr.xephi.authme.settings.Messages; @@ -335,6 +329,7 @@ public class AuthMe extends JavaPlugin { pm.registerEvents(new AuthMePlayerListener18(this), this); } catch (ClassNotFoundException ignore) { } + pm.registerEvents(new AuthMePluginListener(this), this); pm.registerEvents(new AuthMeBlockListener(this), this); pm.registerEvents(new AuthMeEntityListener(this), this); pm.registerEvents(new AuthMeServerListener(this), this); diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java new file mode 100644 index 00000000..dc5aff51 --- /dev/null +++ b/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java @@ -0,0 +1,66 @@ +package fr.xephi.authme.listener; + +import fr.xephi.authme.AuthMe; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.PluginEnableEvent; +import org.bukkit.plugin.Plugin; + +import java.util.List; + +/** + * Created by Tim on 20-11-2015. + */ +public class AuthMePluginListener implements Listener { + + /** Plugin instance. */ + public AuthMe instance; + + /** + * Constructor. + * + * @param instance Main plugin instance. + */ + public AuthMePluginListener(AuthMe instance) { + this.instance = instance; + } + + /** + * Called when a plugin is enabled. + * + * @param event Event reference. + */ + @EventHandler + public void onPluginEnable(PluginEnableEvent event) { + // Call the onPluginEnable method in the permissions manager + Core.getPermissionsManager().onPluginEnable(event); + } + + /** + * Called when a plugin is disabled. + * + * @param event Event reference. + */ + @EventHandler + public void onPluginDisable(PluginDisableEvent event) { + // Get the plugin instance + Plugin plugin = event.getPlugin(); + + // Make sure the plugin instance isn't null + if(plugin == null) + return; + + // Make sure it's not Dungeon Maze itself + if(plugin.equals(DungeonMaze.instance)) + return; + + // Call the onPluginDisable method in the permissions manager + Core.getPermissionsManager().onPluginDisable(event); + + // Check if this plugin is hooked in to Dungeon Maze + if(Core.getApiController().isHooked(plugin)) + // Unhook the plugin from Dungeon Maze and unregister it's API sessions + Core.getApiController().unhookPlugin(plugin); + } +} From f17f1fe5fb6e501c9aef2a996bdc88682a667dae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 19:56:06 +0100 Subject: [PATCH 015/199] Fixed plugin listener --- .../xephi/authme/listener/AuthMePluginListener.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java index dc5aff51..4541871a 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java @@ -34,7 +34,7 @@ public class AuthMePluginListener implements Listener { @EventHandler public void onPluginEnable(PluginEnableEvent event) { // Call the onPluginEnable method in the permissions manager - Core.getPermissionsManager().onPluginEnable(event); + this.instance.getPermissionsManager().onPluginEnable(event); } /** @@ -51,16 +51,11 @@ public class AuthMePluginListener implements Listener { if(plugin == null) return; - // Make sure it's not Dungeon Maze itself - if(plugin.equals(DungeonMaze.instance)) + // Make sure it's not this plugin itself + if(plugin.equals(this.instance)) return; // Call the onPluginDisable method in the permissions manager - Core.getPermissionsManager().onPluginDisable(event); - - // Check if this plugin is hooked in to Dungeon Maze - if(Core.getApiController().isHooked(plugin)) - // Unhook the plugin from Dungeon Maze and unregister it's API sessions - Core.getApiController().unhookPlugin(plugin); + this.instance.getPermissionsManager().onPluginDisable(event); } } From 82e65250b8c80cc05e8eedf9b58f06e420fb5f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 20:01:31 +0100 Subject: [PATCH 016/199] Added permissions manager to main class, along with a setup and get method --- src/main/java/fr/xephi/authme/AuthMe.java | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index a4eb6d41..2829b010 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import fr.xephi.authme.listener.*; +import fr.xephi.authme.permission.PermissionsManager; import org.apache.logging.log4j.LogManager; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -76,6 +77,7 @@ public class AuthMe extends JavaPlugin { // TODO: Move this to a better place! -- timvisee private CommandHandler commandHandler = null; + private PermissionsManager permsMan = null; public Management management; public NewAPI api; @@ -140,6 +142,9 @@ public class AuthMe extends JavaPlugin { authmeLogger = Logger.getLogger("AuthMe"); plugin = this; + // Set up the permissions manager + setupPermissionsManager(); + // Set up and initialize the command handler this.commandHandler = new CommandHandler(false); this.commandHandler.init(); @@ -449,6 +454,23 @@ public class AuthMe extends JavaPlugin { } } + /** + * Set up the permissions manager. + */ + public void setupPermissionsManager() { + this.permsMan = new PermissionsManager(Bukkit.getServer(), this, this.authmeLogger); + this.permsMan.setup(); + } + + /** + * Get the permissions manager instance. + * + * @return Permissions Manager instance. + */ + public PermissionsManager getPermissionsManager() { + return this.permsMan; + } + // Set the console filter to remove the passwords private void setLog4JFilter() { Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() { From 71e24fe80a0b552d7f459d9e070cb78a6395e04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 20:01:43 +0100 Subject: [PATCH 017/199] Improved permissions manager --- .../authme/permission/PermissionsManager.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index dd894559..317ad301 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -251,15 +251,16 @@ public class PermissionsManager { * @param event Event instance. */ public void onPluginEnable(PluginEnableEvent event) { - Plugin p = event.getPlugin(); - String pn = p.getName(); + // Get the plugin and it's name + Plugin plugin = event.getPlugin(); + String pluginName = plugin.getName(); // Check if any known permissions system is enabling - if(pn.equals("PermissionsEx") || pn.equals("PermissionsBukkit") || - pn.equals("bPermissions") || pn.equals("GroupManager") || - pn.equals("zPermissions") || pn.equals("Vault") || - pn.equals("Permissions")) { - this.log.info(pn + " plugin enabled, updating hooks!"); + if(pluginName.equals("PermissionsEx") || pluginName.equals("PermissionsBukkit") || + pluginName.equals("bPermissions") || pluginName.equals("GroupManager") || + pluginName.equals("zPermissions") || pluginName.equals("Vault") || + pluginName.equals("Permissions")) { + this.log.info(pluginName + " plugin enabled, dynamically updating permissions hooks!"); setup(); } } From 5e7e7a14fdd9bc63c0d61d9b8e09e36493e849a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 20:03:44 +0100 Subject: [PATCH 018/199] Added comment to permissions manager --- .../xephi/authme/permission/PermissionsManager.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 317ad301..5d4434c4 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -26,6 +26,17 @@ import java.util.List; import java.util.Map; import java.util.logging.Logger; +/** + * PermissionsManager. + * + * A permissions manager, to manage and use various permissions systems. + * This manager supports dynamic plugin hooking and various other features. + * + * Written by Tim Visée. + * + * @author Tim Visée, http://timvisee.com + * @version 0.2.1 + */ public class PermissionsManager { /** From eae6eb384ffa54cfe925930c2a5c80226774269f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 20:04:59 +0100 Subject: [PATCH 019/199] Updated soft depends --- .../java/fr/xephi/authme/listener/AuthMePluginListener.java | 6 ------ src/main/resources/plugin.yml | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java index 4541871a..118a07b0 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java @@ -6,12 +6,6 @@ import org.bukkit.event.Listener; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.event.server.PluginEnableEvent; import org.bukkit.plugin.Plugin; - -import java.util.List; - -/** - * Created by Tim on 20-11-2015. - */ public class AuthMePluginListener implements Listener { /** Plugin instance. */ diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 57459638..6fed05cb 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -4,7 +4,7 @@ website: ${project.url} description: ${project.description} main: ${mainClass} version: ${project.version} -softdepend: [Vault, Multiverse-Core, Essentials, EssentialsSpawn, ProtocolLib] +softdepend: [Vault, PermissionsBukkit, Permissions, PermissionsEX, EssentialsGroupManager, Multiverse-Core, Essentials, EssentialsSpawn, ProtocolLib] commands: register: description: Register an account From ffd36ba325f21d51f7051baf9eee3900cac23ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 20:08:02 +0100 Subject: [PATCH 020/199] Updated authmePermissable methods in main class --- src/main/java/fr/xephi/authme/AuthMe.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 2829b010..7758432c 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -566,15 +566,27 @@ public class AuthMe extends JavaPlugin { // Check if a player/command sender have a permission public boolean authmePermissible(Player player, String perm) { - if (player.hasPermission(perm)) { + return getPermissionsManager().hasPermission(player, perm); + + /*if (player.hasPermission(perm)) { return true; } else if (permission != null) { return permission.playerHas(player, perm); } - return false; + return false;*/ } public boolean authmePermissible(CommandSender sender, String perm) { + // Handle players with the permissions manager + if(sender instanceof Player) { + // Get the player instance + Player player = (Player) sender; + + // Check whether the player has permission, return the result + return getPermissionsManager().hasPermission(player, perm); + } + + // Legacy system: if (sender.hasPermission(perm)) { return true; } else if (permission != null) { From 10b69403fc6732ba47c2371caab41da7a67a0376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 20:11:46 +0100 Subject: [PATCH 021/199] Deprecated legacy permissions methods --- src/main/java/fr/xephi/authme/AuthMe.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 7758432c..44d2fd03 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -564,7 +564,15 @@ public class AuthMe extends JavaPlugin { } } - // Check if a player/command sender have a permission + /** + * Check if a player/command sender have a permission + * + * @deprecated Use the permissions manager instead! See: getPermissionsManager() + * + * @param player + * @param perm + * @return + */ public boolean authmePermissible(Player player, String perm) { return getPermissionsManager().hasPermission(player, perm); @@ -576,6 +584,13 @@ public class AuthMe extends JavaPlugin { return false;*/ } + /** + * @deprecated Use the permissions manager instead! See: getPermissionsManager() + * + * @param sender + * @param perm + * @return + */ public boolean authmePermissible(CommandSender sender, String perm) { // Handle players with the permissions manager if(sender instanceof Player) { From 8ba126b68137052d3820d72d1621e5ffef058fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 20:13:23 +0100 Subject: [PATCH 022/199] Updated permissions checking system in command handler --- .../fr/xephi/authme/command/CommandPermissions.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/fr/xephi/authme/command/CommandPermissions.java b/src/main/java/fr/xephi/authme/command/CommandPermissions.java index 89007835..c794b564 100644 --- a/src/main/java/fr/xephi/authme/command/CommandPermissions.java +++ b/src/main/java/fr/xephi/authme/command/CommandPermissions.java @@ -3,6 +3,7 @@ package fr.xephi.authme.command; import java.util.ArrayList; import java.util.List; +import fr.xephi.authme.permission.PermissionsManager; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -124,14 +125,14 @@ public class CommandPermissions { // Get the player instance Player player = (Player) sender; -// // Get the permissions manager, and make sure it's instance is valid -// PermissionsManager permissionsManager = Core.getPermissionsManager(); -// if(permissionsManager == null) -// return false; + // Get the permissions manager, and make sure it's instance is valid + PermissionsManager permissionsManager = AuthMe.getInstance().getPermissionsManager(); + if(permissionsManager == null) + return false; // Check whether the player has permission, return the result for(String node : this.permissionNodes) - if(!AuthMe.getInstance().authmePermissible(player, node)) + if(!permissionsManager.hasPermission(player, node, defaultPermission)) return false; return true; } From 33ca2691b219dc92990d83fddc39b1152cdb13e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 20:16:02 +0100 Subject: [PATCH 023/199] Updated deprecated information for legacy permission methods --- src/main/java/fr/xephi/authme/AuthMe.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 44d2fd03..270e3ca8 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -567,7 +567,7 @@ public class AuthMe extends JavaPlugin { /** * Check if a player/command sender have a permission * - * @deprecated Use the permissions manager instead! See: getPermissionsManager() + * @deprecated Deprecated since v5.1. Use the permissions manager instead! See: getPermissionsManager() * * @param player * @param perm @@ -585,7 +585,7 @@ public class AuthMe extends JavaPlugin { } /** - * @deprecated Use the permissions manager instead! See: getPermissionsManager() + * @deprecated Deprecated since v5.1. Use the permissions manager instead! See: getPermissionsManager() * * @param sender * @param perm From 3500ee6fb77b3c00dad0c0f8b9f199f8ea1eca3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 20:23:03 +0100 Subject: [PATCH 024/199] Replaced code that used legacy deprecated permissions methods --- src/main/java/fr/xephi/authme/AuthMe.java | 2 +- .../executable/authme/ForceLoginCommand.java | 2 +- .../fr/xephi/authme/command/help/HelpPrinter.java | 2 +- .../authme/listener/AuthMePlayerListener.java | 14 +++++++++----- .../authme/process/email/AsyncChangeEmail.java | 2 +- .../xephi/authme/process/join/AsyncronousJoin.java | 2 +- .../authme/process/login/AsyncronousLogin.java | 4 ++-- .../authme/process/register/AsyncRegister.java | 6 +++--- src/main/java/fr/xephi/authme/util/Utils.java | 2 +- 9 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 270e3ca8..30f68016 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -642,7 +642,7 @@ public class AuthMe extends JavaPlugin { public Player generateKickPlayer(Collection collection) { Player player = null; for (Player p : collection) { - if (!(authmePermissible(p, "authme.vip"))) { + if (!getPermissionsManager().hasPermission(p, "authme.vip")) { player = p; break; } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java index 17c5d8a4..42632a41 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java @@ -37,7 +37,7 @@ public class ForceLoginCommand extends ExecutableCommand { sender.sendMessage("Player needs to be online!"); return true; } - if (!plugin.authmePermissible(player, "authme.canbeforced")) { + if (!plugin.getPermissionsManager().hasPermission(player, "authme.canbeforced")) { sender.sendMessage("You cannot force login for the player " + playerName + "!"); return true; } diff --git a/src/main/java/fr/xephi/authme/command/help/HelpPrinter.java b/src/main/java/fr/xephi/authme/command/help/HelpPrinter.java index 4f28eb29..9ad398c7 100644 --- a/src/main/java/fr/xephi/authme/command/help/HelpPrinter.java +++ b/src/main/java/fr/xephi/authme/command/help/HelpPrinter.java @@ -105,7 +105,7 @@ public class HelpPrinter { for(String node : permissions.getPermissionNodes()) { boolean nodePermission = true; if(sender instanceof Player) - nodePermission = AuthMe.getInstance().authmePermissible((Player) sender, node); + nodePermission = AuthMe.getInstance().getPermissionsManager().hasPermission((Player) sender, node); final String nodePermsString = ChatColor.GRAY + (nodePermission ? ChatColor.ITALIC + " (Permission!)" : ChatColor.ITALIC + " (No Permission!)"); sender.sendMessage(" " + ChatColor.YELLOW + ChatColor.ITALIC + node + nodePermsString); } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index bcc5bc8f..bdb97b07 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.PatternSyntaxException; +import fr.xephi.authme.permission.PermissionsManager; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; @@ -165,7 +166,7 @@ public class AuthMePlayerListener implements Listener { private void checkAntiBotMod(final Player player) { if (plugin.delayedAntiBot || plugin.antibotMod) return; - if (plugin.authmePermissible(player, "authme.bypassantibot")) + if (plugin.getPermissionsManager().hasPermission(player, "authme.bypassantibot")) return; if (antibot.size() > Settings.antiBotSensibility) { plugin.switchAntiBotMod(true); @@ -265,8 +266,11 @@ public class AuthMePlayerListener implements Listener { return; } + // Get the permissions manager + PermissionsManager permsMan = plugin.getPermissionsManager(); + boolean isAuthAvailable = plugin.database.isAuthAvailable(name); - if (!Settings.countriesBlacklist.isEmpty() && !isAuthAvailable && !plugin.authmePermissible(player, "authme.bypassantibot")) { + if (!Settings.countriesBlacklist.isEmpty() && !isAuthAvailable && !permsMan.hasPermission(player, "authme.bypassantibot")) { String code = Utils.getCountryCode(event.getAddress().getHostAddress()); if (((code == null) || Settings.countriesBlacklist.contains(code))) { event.setKickMessage(m.send("country_banned")[0]); @@ -274,7 +278,7 @@ public class AuthMePlayerListener implements Listener { return; } } - if (Settings.enableProtection && !Settings.countries.isEmpty() && !isAuthAvailable && !plugin.authmePermissible(player, "authme.bypassantibot")) { + if (Settings.enableProtection && !Settings.countries.isEmpty() && !isAuthAvailable && !permsMan.hasPermission(player, "authme.bypassantibot")) { String code = Utils.getCountryCode(event.getAddress().getHostAddress()); if (((code == null) || !Settings.countries.contains(code))) { event.setKickMessage(m.send("country_banned")[0]); @@ -343,7 +347,7 @@ public class AuthMePlayerListener implements Listener { } if (event.getResult() != PlayerLoginEvent.Result.KICK_FULL) return; - if (!plugin.authmePermissible(player, "authme.vip")) { + if (!permsMan.hasPermission(player, "authme.vip")) { event.setKickMessage(m.send("kick_fullserver")[0]); event.setResult(PlayerLoginEvent.Result.KICK_FULL); return; @@ -510,7 +514,7 @@ public class AuthMePlayerListener implements Listener { Player player = event.getPlayer(); if (player == null) return; - if (plugin.authmePermissible(player, "authme.bypassforcesurvival")) + if (plugin.getPermissionsManager().hasPermission(player, "authme.bypassforcesurvival")) return; if (Utils.checkAuth(player)) return; diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java index 6fa9ac7e..0b8f7c64 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java @@ -38,7 +38,7 @@ public class AsyncChangeEmail { String playerName = player.getName().toLowerCase(); if (Settings.getmaxRegPerEmail > 0) { - if (!plugin.authmePermissible(player, "authme.allow2accounts") && plugin.database.getAllAuthsByEmail(newEmail).size() >= Settings.getmaxRegPerEmail) { + if (!plugin.getPermissionsManager().hasPermission(player, "authme.allow2accounts") && plugin.database.getAllAuthsByEmail(newEmail).size() >= Settings.getmaxRegPerEmail) { m.send(player, "max_reg"); return; } diff --git a/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java index d4678ec1..60aae943 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java @@ -91,7 +91,7 @@ public class AsyncronousJoin { }); return; } - if (Settings.getMaxJoinPerIp > 0 && !plugin.authmePermissible(player, "authme.allow2accounts") && !ip.equalsIgnoreCase("127.0.0.1") && !ip.equalsIgnoreCase("localhost")) { + if (Settings.getMaxJoinPerIp > 0 && !plugin.getPermissionsManager().hasPermission(player, "authme.allow2accounts") && !ip.equalsIgnoreCase("127.0.0.1") && !ip.equalsIgnoreCase("localhost")) { if (plugin.hasJoinedIp(player.getName(), ip)) { sched.scheduleSyncDelayedTask(plugin, new Runnable() { diff --git a/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java index 4879ea8b..4144bd08 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java @@ -96,7 +96,7 @@ public class AsyncronousLogin { } return null; } - if (Settings.getMaxLoginPerIp > 0 && !plugin.authmePermissible(player, "authme.allow2accounts") && !getIP().equalsIgnoreCase("127.0.0.1") && !getIP().equalsIgnoreCase("localhost")) { + if (Settings.getMaxLoginPerIp > 0 && !plugin.getPermissionsManager().hasPermission(player, "authme.allow2accounts") && !getIP().equalsIgnoreCase("127.0.0.1") && !getIP().equalsIgnoreCase("localhost")) { if (plugin.isLoggedIp(name, getIP())) { m.send(player, "logged_in"); return null; @@ -236,7 +236,7 @@ public class AsyncronousLogin { * uuidaccounts + "."; } } */ for (Player player : Utils.getOnlinePlayers()) { - if (plugin.authmePermissible(player, "authme.seeOtherAccounts")) { + if (plugin.getPermissionsManager().hasPermission(player, "authme.seeOtherAccounts")) { player.sendMessage("[AuthMe] The player " + auth.getNickname() + " has " + auths.size() + " accounts"); player.sendMessage(message.toString()); // player.sendMessage(uuidaccounts.replace("%size%", diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index b7abdf5b..6a42abba 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -62,7 +62,7 @@ public class AsyncRegister { m.send(player, "user_regged"); return false; } else if (Settings.getmaxRegPerIp > 0) { - if (!plugin.authmePermissible(player, "authme.allow2accounts") && database.getAllAuthsByIp(getIp()).size() >= Settings.getmaxRegPerIp && !getIp().equalsIgnoreCase("127.0.0.1") && !getIp().equalsIgnoreCase("localhost")) { + if (!plugin.getPermissionsManager().hasPermission(player, "authme.allow2accounts") && database.getAllAuthsByIp(getIp()).size() >= Settings.getmaxRegPerIp && !getIp().equalsIgnoreCase("127.0.0.1") && !getIp().equalsIgnoreCase("localhost")) { m.send(player, "max_reg"); return false; } @@ -76,7 +76,7 @@ public class AsyncRegister { return; if (!email.isEmpty() && !email.equals("")) { if (Settings.getmaxRegPerEmail > 0) { - if (!plugin.authmePermissible(player, "authme.allow2accounts") && database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) { + if (!plugin.getPermissionsManager().hasPermission(player, "authme.allow2accounts") && database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) { m.send(player, "max_reg"); return; } @@ -94,7 +94,7 @@ public class AsyncRegister { protected void emailRegister() throws Exception { if (Settings.getmaxRegPerEmail > 0) { - if (!plugin.authmePermissible(player, "authme.allow2accounts") && database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) { + if (!plugin.getPermissionsManager().hasPermission(player, "authme.allow2accounts") && database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) { m.send(player, "max_reg"); return; } diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 2775f694..8af65cb8 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -225,7 +225,7 @@ public class Utils { * Used for force player GameMode */ public static void forceGM(Player player) { - if (!plugin.authmePermissible(player, "authme.bypassforcesurvival")) + if (!plugin.getPermissionsManager().hasPermission(player, "authme.bypassforcesurvival")) player.setGameMode(GameMode.SURVIVAL); } From a4b2306e166e113077c75a524da8e343a61af390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 20:23:52 +0100 Subject: [PATCH 025/199] Added some comments to legacy permissions methods --- src/main/java/fr/xephi/authme/AuthMe.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 30f68016..fd9bb52f 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -574,8 +574,10 @@ public class AuthMe extends JavaPlugin { * @return */ public boolean authmePermissible(Player player, String perm) { + // New code: return getPermissionsManager().hasPermission(player, perm); + // Legacy code: /*if (player.hasPermission(perm)) { return true; } else if (permission != null) { @@ -592,6 +594,7 @@ public class AuthMe extends JavaPlugin { * @return */ public boolean authmePermissible(CommandSender sender, String perm) { + // New code: // Handle players with the permissions manager if(sender instanceof Player) { // Get the player instance @@ -601,7 +604,7 @@ public class AuthMe extends JavaPlugin { return getPermissionsManager().hasPermission(player, perm); } - // Legacy system: + // Legacy code: if (sender.hasPermission(perm)) { return true; } else if (permission != null) { From 4978f195f886325aaeb647e24ffd06b7d3fd98eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 20 Nov 2015 20:50:27 +0100 Subject: [PATCH 026/199] Use different logger in permissions manager --- src/main/java/fr/xephi/authme/AuthMe.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index fd9bb52f..db13fe30 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -458,7 +458,7 @@ public class AuthMe extends JavaPlugin { * Set up the permissions manager. */ public void setupPermissionsManager() { - this.permsMan = new PermissionsManager(Bukkit.getServer(), this, this.authmeLogger); + this.permsMan = new PermissionsManager(Bukkit.getServer(), this, getLogger()); this.permsMan.setup(); } From 019390dfe050baa5209b5a1a01f56cba77bc9110 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Fri, 20 Nov 2015 23:00:13 +0100 Subject: [PATCH 027/199] Add unit test dependencies and create test for Log4JFilter Note that the new dependencies in the pom have the scope set to test, so they will not be included into the built artifact. A first test class illustrates the general way unit tests can be set up with JUnit, Mockito and Hamcrest matchers. --- pom.xml | 34 +++- .../java/fr/xephi/authme/Log4JFilterTest.java | 186 ++++++++++++++++++ 2 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 test/main/java/fr/xephi/authme/Log4JFilterTest.java diff --git a/pom.xml b/pom.xml index 3dcdaa12..9b8fd761 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,8 @@ + src/main/java + test/main/java org.apache.maven.plugins @@ -589,13 +591,33 @@ true - - - net.ricecode - string-similarity - 1.0.0 + + + junit + junit + test + 4.12 + + + org.hamcrest + java-hamcrest + test + 2.0.0.0 + + + org.mockito + mockito-core + test + 2.0.5-beta + + + + + net.ricecode + string-similarity + 1.0.0 compile true - + diff --git a/test/main/java/fr/xephi/authme/Log4JFilterTest.java b/test/main/java/fr/xephi/authme/Log4JFilterTest.java new file mode 100644 index 00000000..e7d11410 --- /dev/null +++ b/test/main/java/fr/xephi/authme/Log4JFilterTest.java @@ -0,0 +1,186 @@ +package fr.xephi.authme; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; + +import org.apache.logging.log4j.core.Filter.Result; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.message.Message; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * Test for {@link Log4JFilter}. + */ +public class Log4JFilterTest { + + private final Log4JFilter log4JFilter = new Log4JFilter(); + + private static final String SENSITIVE_COMMAND = "User issued server command: /login pass pass"; + private static final String NORMAL_COMMAND = "User issued server command: /help"; + private static final String OTHER_COMMAND = "Starting the server... Write /l for logs"; + + // --------- + // Test the filter(LogEvent) method + // --------- + @Test + public void shouldFilterSensitiveLogEvent() { + // given + Message message = mockMessage(SENSITIVE_COMMAND); + LogEvent event = Mockito.mock(LogEvent.class); + when(event.getMessage()).thenReturn(message); + + // when + Result result = log4JFilter.filter(event); + + // then + assertThat(result, equalTo(Result.DENY)); + } + + @Test + public void shouldNotFilterIrrelevantLogEvent() { + // given + Message message = mockMessage(NORMAL_COMMAND); + LogEvent event = Mockito.mock(LogEvent.class); + when(event.getMessage()).thenReturn(message); + + // when + Result result = log4JFilter.filter(event); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + + @Test + public void shouldNotFilterLogEventWithNullMessage() { + // given + Message message = mockMessage(null); + LogEvent event = Mockito.mock(LogEvent.class); + when(event.getMessage()).thenReturn(message); + + // when + Result result = log4JFilter.filter(event); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + + @Test + public void shouldNotFilterWhenLogEventIsNull() { + // given / when + Result result = log4JFilter.filter(null); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + + // ---------- + // Test filter(Logger, Level, Marker, String, Object...) + // ---------- + @Test + public void shouldFilterSensitiveStringMessage() { + // given / when + Result result = log4JFilter.filter(null, null, null, SENSITIVE_COMMAND, new Object[0]); + + // then + assertThat(result, equalTo(Result.DENY)); + } + + @Test + public void shouldNotFilterNormalStringMessage() { + // given / when + Result result = log4JFilter.filter(null, null, null, NORMAL_COMMAND, new Object[0]); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + + @Test + public void shouldReturnNeutralForNullMessage() { + // given / when + Result result = log4JFilter.filter(null, null, null, null, new Object[0]); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + + // -------- + // Test filter(Logger, Level, Marker, Object, Throwable) + // -------- + @Test + public void shouldFilterSensitiveObjectMessage() { + // given / when + Result result = log4JFilter.filter(null, null, null, SENSITIVE_COMMAND, new Exception()); + + // then + assertThat(result, equalTo(Result.DENY)); + } + + @Test + public void shouldNotFilterNullObjectParam() { + // given / when + Result result = log4JFilter.filter(null, null, null, null, new Exception()); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + + @Test + public void shouldNotFilterIrrelevantMessage() { + // given / when + Result result = log4JFilter.filter(null, null, null, OTHER_COMMAND, new Exception()); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + + // -------- + // Test filter(Logger, Level, Marker, Message, Throwable) + // -------- + @Test + public void shouldFilterSensitiveMessage() { + // given + Message message = mockMessage(SENSITIVE_COMMAND); + + // when + Result result = log4JFilter.filter(null, null, null, message, new Exception()); + + // then + assertThat(result, equalTo(Result.DENY)); + } + + @Test + public void shouldNotFilterNonSensitiveMessage() { + // given + Message message = mockMessage(NORMAL_COMMAND); + + // when + Result result = log4JFilter.filter(null, null, null, message, new Exception()); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + + @Test + public void shouldNotFilterNullMessage() { + // given / when + Result result = log4JFilter.filter(null, null, null, null, new Exception()); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + + /** + * Mocks a {@link Message} object and makes it return the given formatted message. + * + * @param formattedMessage the formatted message the mock should return + * @return Message mock + */ + private static Message mockMessage(String formattedMessage) { + Message message = Mockito.mock(Message.class); + when(message.getFormattedMessage()).thenReturn(formattedMessage); + return message; + } + +} From 84de22c9c0241361c69a44bba9e0e84605ec9813 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Fri, 20 Nov 2015 23:17:50 +0100 Subject: [PATCH 028/199] Refactor Log4JFilter and improve branch coverage --- .../java/fr/xephi/authme/Log4JFilter.java | 103 +++++++++--------- .../fr/xephi/authme/util/StringUtils.java | 12 ++ .../java/fr/xephi/authme/Log4JFilterTest.java | 48 +++++++- 3 files changed, 112 insertions(+), 51 deletions(-) diff --git a/src/main/java/fr/xephi/authme/Log4JFilter.java b/src/main/java/fr/xephi/authme/Log4JFilter.java index dccaee6f..365b2cec 100644 --- a/src/main/java/fr/xephi/authme/Log4JFilter.java +++ b/src/main/java/fr/xephi/authme/Log4JFilter.java @@ -6,80 +6,50 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.message.Message; +import fr.xephi.authme.util.StringUtils; + /** - * + * Implements a filter for Log4j to skip sensitive AuthMe commands. * @author Xephi59 */ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { + + /** List of commands (lower-case) to skip. */ + private static final String[] COMMANDS_TO_SKIP = { "/login ", "/l ", "/reg ", "/changepassword ", + "/unregister ", "/authme register ", "/authme changepassword ", "/authme reg ", "/authme cp ", + "/register " }; + /** Constructor. */ public Log4JFilter() { } @Override public Result filter(LogEvent record) { - try { - if (record == null || record.getMessage() == null) - return Result.NEUTRAL; - String logM = record.getMessage().getFormattedMessage().toLowerCase(); - if (!logM.contains("issued server command:")) - return Result.NEUTRAL; - if (!logM.contains("/login ") && !logM.contains("/l ") && !logM.contains("/reg ") && !logM.contains("/changepassword ") && !logM.contains("/unregister ") && !logM.contains("/authme register ") && !logM.contains("/authme changepassword ") && !logM.contains("/authme reg ") && !logM.contains("/authme cp ") && !logM.contains("/register ")) - return Result.NEUTRAL; - return Result.DENY; - } catch (NullPointerException npe) { - return Result.NEUTRAL; - } + if (record == null) { + return Result.NEUTRAL; + } + return validateMessage(record.getMessage()); } @Override public Result filter(Logger arg0, Level arg1, Marker arg2, String message, Object... arg4) { - try { - if (message == null) - return Result.NEUTRAL; - String logM = message.toLowerCase(); - if (!logM.contains("issued server command:")) - return Result.NEUTRAL; - if (!logM.contains("/login ") && !logM.contains("/l ") && !logM.contains("/reg ") && !logM.contains("/changepassword ") && !logM.contains("/unregister ") && !logM.contains("/authme register ") && !logM.contains("/authme changepassword ") && !logM.contains("/authme reg ") && !logM.contains("/authme cp ") && !logM.contains("/register ")) - return Result.NEUTRAL; - return Result.DENY; - } catch (NullPointerException npe) { - return Result.NEUTRAL; - } + return validateMessage(message); } @Override public Result filter(Logger arg0, Level arg1, Marker arg2, Object message, Throwable arg4) { - try { - if (message == null) - return Result.NEUTRAL; - String logM = message.toString().toLowerCase(); - if (!logM.contains("issued server command:")) - return Result.NEUTRAL; - if (!logM.contains("/login ") && !logM.contains("/l ") && !logM.contains("/reg ") && !logM.contains("/changepassword ") && !logM.contains("/unregister ") && !logM.contains("/authme register ") && !logM.contains("/authme changepassword ") && !logM.contains("/authme reg ") && !logM.contains("/authme cp ") && !logM.contains("/register ")) - return Result.NEUTRAL; - return Result.DENY; - } catch (NullPointerException npe) { - return Result.NEUTRAL; - } + if (message == null) { + return Result.NEUTRAL; + } + return validateMessage(message.toString()); } @Override public Result filter(Logger arg0, Level arg1, Marker arg2, Message message, Throwable arg4) { - try { - if (message == null) - return Result.NEUTRAL; - String logM = message.getFormattedMessage().toLowerCase(); - if (!logM.contains("issued server command:")) - return Result.NEUTRAL; - if (!logM.contains("/login ") && !logM.contains("/l ") && !logM.contains("/reg ") && !logM.contains("/changepassword ") && !logM.contains("/unregister ") && !logM.contains("/authme register ") && !logM.contains("/authme changepassword ") && !logM.contains("/authme reg ") && !logM.contains("/authme cp ") && !logM.contains("/register ")) - return Result.NEUTRAL; - return Result.DENY; - } catch (NullPointerException npe) { - return Result.NEUTRAL; - } + return validateMessage(message); } @Override @@ -92,4 +62,39 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { return Result.NEUTRAL; } + /** + * Validates a Message instance and returns the {@link Result} value + * depending depending on whether the message contains sensitive AuthMe + * data. + * + * @param message the Message object to verify + * @return the Result value + */ + private static Result validateMessage(Message message) { + if (message == null) { + return Result.NEUTRAL; + } + return validateMessage(message.getFormattedMessage()); + } + + /** + * Validates a message and returns the {@link Result} value depending + * depending on whether the message contains sensitive AuthMe data. + * + * @param message the message to verify + * @return the Result value + */ + private static Result validateMessage(String message) { + if (message == null) { + return Result.NEUTRAL; + } + + String lowerMessage = message.toLowerCase(); + if (lowerMessage.contains("issued server command:") + && StringUtils.containsAny(lowerMessage, COMMANDS_TO_SKIP)) { + return Result.DENY; + } + return Result.NEUTRAL; + } + } diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java index eab7ea15..da6f91ba 100644 --- a/src/main/java/fr/xephi/authme/util/StringUtils.java +++ b/src/main/java/fr/xephi/authme/util/StringUtils.java @@ -25,4 +25,16 @@ public class StringUtils { // Determine the difference value, return the result return Math.abs(service.score(first, second) - 1.0); } + + public static boolean containsAny(String str, String... pieces) { + if (str == null) { + return false; + } + for (String piece : pieces) { + if (str.contains(piece)) { + return true; + } + } + return false; + } } diff --git a/test/main/java/fr/xephi/authme/Log4JFilterTest.java b/test/main/java/fr/xephi/authme/Log4JFilterTest.java index e7d11410..5614eb51 100644 --- a/test/main/java/fr/xephi/authme/Log4JFilterTest.java +++ b/test/main/java/fr/xephi/authme/Log4JFilterTest.java @@ -52,6 +52,20 @@ public class Log4JFilterTest { assertThat(result, equalTo(Result.NEUTRAL)); } + @Test + public void shouldNotFilterNonCommandLogEvent() { + // given + Message message = mockMessage(OTHER_COMMAND); + LogEvent event = Mockito.mock(LogEvent.class); + when(event.getMessage()).thenReturn(message); + + // when + Result result = log4JFilter.filter(event); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + @Test public void shouldNotFilterLogEventWithNullMessage() { // given @@ -96,6 +110,15 @@ public class Log4JFilterTest { assertThat(result, equalTo(Result.NEUTRAL)); } + @Test + public void shouldNotFilterNonCommandStringMessage() { + // given / when + Result result = log4JFilter.filter(null, null, null, OTHER_COMMAND, new Object[0]); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + @Test public void shouldReturnNeutralForNullMessage() { // given / when @@ -120,7 +143,7 @@ public class Log4JFilterTest { @Test public void shouldNotFilterNullObjectParam() { // given / when - Result result = log4JFilter.filter(null, null, null, null, new Exception()); + Result result = log4JFilter.filter(null, null, null, (Object) null, new Exception()); // then assertThat(result, equalTo(Result.NEUTRAL)); @@ -135,6 +158,15 @@ public class Log4JFilterTest { assertThat(result, equalTo(Result.NEUTRAL)); } + @Test + public void shouldNotFilterNonSensitiveCommand() { + // given / when + Result result = log4JFilter.filter(null, null, null, NORMAL_COMMAND, new Exception()); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + // -------- // Test filter(Logger, Level, Marker, Message, Throwable) // -------- @@ -162,10 +194,22 @@ public class Log4JFilterTest { assertThat(result, equalTo(Result.NEUTRAL)); } + @Test + public void shouldNotFilterNonCommandMessage() { + // given + Message message = mockMessage(OTHER_COMMAND); + + // when + Result result = log4JFilter.filter(null, null, null, message, new Exception()); + + // then + assertThat(result, equalTo(Result.NEUTRAL)); + } + @Test public void shouldNotFilterNullMessage() { // given / when - Result result = log4JFilter.filter(null, null, null, null, new Exception()); + Result result = log4JFilter.filter(null, null, null, (Message) null, new Exception()); // then assertThat(result, equalTo(Result.NEUTRAL)); From 35ef562d35ab9b2a2cfb8ae75526929b7d7ed2ea Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Fri, 20 Nov 2015 23:36:25 +0100 Subject: [PATCH 029/199] Update pom.xml --- pom.xml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 9b8fd761..fa4f22f3 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ UTF-8 fr.xephi.authme.AuthMe CustomBuild - [Xephi, sgdc3, DNx5, timvisee, games647] + [Xephi, sgdc3, DNx5, timvisee, games647, ljacqu] 1.7 @@ -58,14 +58,28 @@ AuthMe-${project.version} + src/main/java + test/main/java + . true src/main/resources/ + + plugin.yml + + + + . + false + src/main/resources/ *.yml + + plugin.yml + ./messages/ @@ -76,8 +90,7 @@ - src/main/java - test/main/java + org.apache.maven.plugins @@ -88,6 +101,8 @@ ${javaVersion} + + org.apache.maven.plugins maven-shade-plugin @@ -591,24 +606,27 @@ true - + junit junit test 4.12 + true org.hamcrest java-hamcrest test 2.0.0.0 + true org.mockito mockito-core test 2.0.5-beta + true From ba483af807312622fc6bccb4238252a6c6b5e2c5 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Fri, 20 Nov 2015 23:43:26 +0100 Subject: [PATCH 030/199] fix project layout --- pom.xml | 2 +- .../java/fr/xephi/authme/permission/PermissionsManager.java | 3 ++- .../test}/java/fr/xephi/authme/Log4JFilterTest.java | 0 3 files changed, 3 insertions(+), 2 deletions(-) rename {test/main => src/test}/java/fr/xephi/authme/Log4JFilterTest.java (100%) diff --git a/pom.xml b/pom.xml index fa4f22f3..93fdb5f2 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ AuthMe-${project.version} src/main/java - test/main/java + src/test/java diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 5d4434c4..933ce299 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -361,6 +361,7 @@ public class PermissionsManager { case Z_PERMISSIONS: // zPermissions + @SuppressWarnings("deprecation") Map perms = zPermissionsService.getPlayerPermissions(player.getWorld().getName(), null, player.getName()); if(perms.containsKey(permsNode)) return perms.get(permsNode); @@ -385,7 +386,7 @@ public class PermissionsManager { } } - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public List getGroups(Player player) { if(!isEnabled()) // No permissions system is used, return an empty list diff --git a/test/main/java/fr/xephi/authme/Log4JFilterTest.java b/src/test/java/fr/xephi/authme/Log4JFilterTest.java similarity index 100% rename from test/main/java/fr/xephi/authme/Log4JFilterTest.java rename to src/test/java/fr/xephi/authme/Log4JFilterTest.java From d6355adb51be64faf141898b61ac35db4ff22c8a Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Fri, 20 Nov 2015 23:52:20 +0100 Subject: [PATCH 031/199] cleanup --- src/main/java/fr/xephi/authme/AuthMe.java | 20 ++++-------- .../xephi/authme/cache/limbo/LimboCache.java | 8 ++--- .../authme/listener/AuthMeServerListener.java | 2 +- src/main/java/fr/xephi/authme/util/Utils.java | 32 +++++++++---------- 4 files changed, 27 insertions(+), 35 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index db13fe30..6ad0226b 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -93,7 +93,7 @@ public class AuthMe extends JavaPlugin { public boolean delayedAntiBot = true; // Hooks TODO: move into modules - public Permission permission; + public Permission vaultGroupManagement; public Essentials ess; public MultiverseCore multiverse; public CombatTagPlus combatTagPlus; @@ -488,13 +488,13 @@ public class AuthMe extends JavaPlugin { if (server.getPluginManager().isPluginEnabled("Vault")) { RegisteredServiceProvider permissionProvider = server.getServicesManager().getRegistration(net.milkbowl.vault.permission.Permission.class); if (permissionProvider != null) { - permission = permissionProvider.getProvider(); - ConsoleLogger.info("Vault detected, hooking with the " + permission.getName() + " permissions system..."); + vaultGroupManagement = permissionProvider.getProvider(); + ConsoleLogger.info("Vault detected, hooking with the " + vaultGroupManagement.getName() + " group management system..."); } else { - ConsoleLogger.showError("Vault detected, but I can't find any permissions plugin to hook with!"); + ConsoleLogger.showError("Vault detected, but I can't find any group management plugin to hook with!"); } } else { - permission = null; + vaultGroupManagement = null; } } @@ -594,7 +594,6 @@ public class AuthMe extends JavaPlugin { * @return */ public boolean authmePermissible(CommandSender sender, String perm) { - // New code: // Handle players with the permissions manager if(sender instanceof Player) { // Get the player instance @@ -603,13 +602,6 @@ public class AuthMe extends JavaPlugin { // Check whether the player has permission, return the result return getPermissionsManager().hasPermission(player, perm); } - - // Legacy code: - if (sender.hasPermission(perm)) { - return true; - } else if (permission != null) { - return permission.has(sender, perm); - } return false; } @@ -682,7 +674,7 @@ public class AuthMe extends JavaPlugin { // names... (Actually it purges // only names!) if (Settings.purgePermissions) - dataManager.purgePermissions(cleared, permission); + dataManager.purgePermissions(cleared, vaultGroupManagement); } // Return the spawn location of a player diff --git a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java index 70614107..9a7e02d0 100644 --- a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java @@ -60,12 +60,12 @@ public class LimboCache { operator = player.isOp(); flying = player.isFlying(); - if (plugin.permission != null) { + if (plugin.vaultGroupManagement != null) { try { - playerGroup = plugin.permission.getPrimaryGroup(player); + playerGroup = plugin.vaultGroupManagement.getPrimaryGroup(player); } catch (UnsupportedOperationException e) { - ConsoleLogger.showError("Your permission system (" + plugin.permission.getName() + ") do not support Group system with that config... unhook!"); - plugin.permission = null; + ConsoleLogger.showError("Your permission system (" + plugin.vaultGroupManagement.getName() + ") do not support Group system with that config... unhook!"); + plugin.vaultGroupManagement = null; } } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java index a493080a..15cdf2dc 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java @@ -62,7 +62,7 @@ public class AuthMeServerListener implements Listener { ConsoleLogger.info("CombatTagPlus has been disabled, unhook!"); } if (pluginName.equalsIgnoreCase("Vault")) { - plugin.permission = null; + plugin.vaultGroupManagement = null; ConsoleLogger.showError("Vault has been disabled, unhook permissions!"); } if (pluginName.equalsIgnoreCase("ProtocolLib")) { diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 8af65cb8..15ab11f8 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -106,32 +106,32 @@ public class Utils { public static void setGroup(Player player, GroupType group) { if (!Settings.isPermissionCheckEnabled) return; - if (plugin.permission == null) + if (plugin.vaultGroupManagement == null) return; String currentGroup; try { - currentGroup = plugin.permission.getPrimaryGroup(player); + currentGroup = plugin.vaultGroupManagement.getPrimaryGroup(player); } catch (UnsupportedOperationException e) { - ConsoleLogger.showError("Your permission plugin (" + plugin.permission.getName() + ") doesn't support the Group system... unhook!"); - plugin.permission = null; + ConsoleLogger.showError("Your permission plugin (" + plugin.vaultGroupManagement.getName() + ") doesn't support the Group system... unhook!"); + plugin.vaultGroupManagement = null; return; } switch (group) { case UNREGISTERED: { - plugin.permission.playerRemoveGroup(player, currentGroup); - plugin.permission.playerAddGroup(player, Settings.unRegisteredGroup); + plugin.vaultGroupManagement.playerRemoveGroup(player, currentGroup); + plugin.vaultGroupManagement.playerAddGroup(player, Settings.unRegisteredGroup); break; } case REGISTERED: { - plugin.permission.playerRemoveGroup(player, currentGroup); - plugin.permission.playerAddGroup(player, Settings.getRegisteredGroup); + plugin.vaultGroupManagement.playerRemoveGroup(player, currentGroup); + plugin.vaultGroupManagement.playerAddGroup(player, Settings.getRegisteredGroup); break; } case NOTLOGGEDIN: { if (!useGroupSystem()) break; - plugin.permission.playerRemoveGroup(player, currentGroup); - plugin.permission.playerAddGroup(player, Settings.getUnloggedinGroup); + plugin.vaultGroupManagement.playerRemoveGroup(player, currentGroup); + plugin.vaultGroupManagement.playerAddGroup(player, Settings.getUnloggedinGroup); break; } case LOGGEDIN: { @@ -141,8 +141,8 @@ public class Utils { if (limbo == null) break; String realGroup = limbo.getGroup(); - plugin.permission.playerRemoveGroup(player, currentGroup); - plugin.permission.playerAddGroup(player, realGroup); + plugin.vaultGroupManagement.playerRemoveGroup(player, currentGroup); + plugin.vaultGroupManagement.playerAddGroup(player, realGroup); break; } } @@ -152,15 +152,15 @@ public class Utils { if (!useGroupSystem()) { return false; } - if (plugin.permission == null) + if (plugin.vaultGroupManagement == null) return false; try { - if (plugin.permission.playerRemoveGroup(player, Settings.getUnloggedinGroup) && plugin.permission.playerAddGroup(player, group)) { + if (plugin.vaultGroupManagement.playerRemoveGroup(player, Settings.getUnloggedinGroup) && plugin.vaultGroupManagement.playerAddGroup(player, group)) { return true; } } catch (UnsupportedOperationException e) { - ConsoleLogger.showError("Your permission system (" + plugin.permission.getName() + ") do not support Group system with that config... unhook!"); - plugin.permission = null; + ConsoleLogger.showError("Your permission system (" + plugin.vaultGroupManagement.getName() + ") do not support Group system with that config... unhook!"); + plugin.vaultGroupManagement = null; return false; } return false; From 68ae6ee70105105969bbc7a0e0feed8dac9e1dc8 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Fri, 20 Nov 2015 23:59:25 +0100 Subject: [PATCH 032/199] remove useless inventory store event --- .../xephi/authme/cache/limbo/LimboCache.java | 15 ------ .../authme/events/StoreInventoryEvent.java | 54 ------------------- 2 files changed, 69 deletions(-) delete mode 100644 src/main/java/fr/xephi/authme/events/StoreInventoryEvent.java diff --git a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java index 9a7e02d0..354d4c72 100644 --- a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java @@ -12,7 +12,6 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.backup.DataFileCache; import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.events.ResetInventoryEvent; -import fr.xephi.authme.events.StoreInventoryEvent; import fr.xephi.authme.settings.Settings; public class LimboCache { @@ -37,13 +36,6 @@ public class LimboCache { boolean flying = false; if (playerData.doesCacheExist(player)) { - final StoreInventoryEvent event = new StoreInventoryEvent(player, playerData); - Bukkit.getServer().getPluginManager().callEvent(event); - if (!event.isCancelled() && event.getInventory() != null && event.getArmor() != null) { - player.getInventory().setContents(event.getInventory()); - player.getInventory().setArmorContents(event.getArmor()); - } - DataFileCache cache = playerData.readCache(player); if (cache != null) { playerGroup = cache.getGroup(); @@ -51,13 +43,6 @@ public class LimboCache { flying = cache.isFlying(); } } else { - StoreInventoryEvent event = new StoreInventoryEvent(player); - Bukkit.getServer().getPluginManager().callEvent(event); - if (!event.isCancelled() && event.getInventory() != null && event.getArmor() != null) { - player.getInventory().setContents(event.getInventory()); - player.getInventory().setArmorContents(event.getArmor()); - } - operator = player.isOp(); flying = player.isFlying(); if (plugin.vaultGroupManagement != null) { diff --git a/src/main/java/fr/xephi/authme/events/StoreInventoryEvent.java b/src/main/java/fr/xephi/authme/events/StoreInventoryEvent.java deleted file mode 100644 index 9bfc8696..00000000 --- a/src/main/java/fr/xephi/authme/events/StoreInventoryEvent.java +++ /dev/null @@ -1,54 +0,0 @@ -package fr.xephi.authme.events; - -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -import fr.xephi.authme.cache.backup.JsonCache; - -/** - * This event is call just before write inventory content to cache - * - * @author Xephi59 - */ -public class StoreInventoryEvent extends CustomEvent { - - private ItemStack[] inventory; - private ItemStack[] armor; - private Player player; - - public StoreInventoryEvent(Player player) { - this.player = player; - this.inventory = player.getInventory().getContents(); - this.armor = player.getInventory().getArmorContents(); - } - - public StoreInventoryEvent(Player player, JsonCache jsonCache) { - this.player = player; - this.inventory = player.getInventory().getContents(); - this.armor = player.getInventory().getArmorContents(); - } - - public ItemStack[] getInventory() { - return this.inventory; - } - - public void setInventory(ItemStack[] inventory) { - this.inventory = inventory; - } - - public ItemStack[] getArmor() { - return this.armor; - } - - public void setArmor(ItemStack[] armor) { - this.armor = armor; - } - - public Player getPlayer() { - return this.player; - } - - public void setPlayer(Player player) { - this.player = player; - } -} From ada992785c318bfa94a9aa3911d808b7c9fae458 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sat, 21 Nov 2015 00:36:46 +0100 Subject: [PATCH 033/199] login process cleanup --- .../login/ProcessSyncronousPlayerLogin.java | 72 +++++++++---------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java index ec35d128..41318843 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java @@ -51,15 +51,16 @@ public class ProcessSyncronousPlayerLogin implements Runnable { protected void restoreOpState() { player.setOp(limbo.getOperator()); - if (player.getGameMode() != GameMode.CREATIVE && !Settings.isMovementAllowed) { - if (limbo.getGameMode() != GameMode.CREATIVE) { - player.setAllowFlight(limbo.isFlying()); - player.setFlying(limbo.isFlying()); - } else { - player.setAllowFlight(false); - player.setFlying(false); - } + } + + protected void restoreFlyghtState() { + if(Settings.isForceSurvivalModeEnabled) { + player.setAllowFlight(false); + player.setFlying(false); + return; } + player.setAllowFlight(limbo.isFlying()); + player.setFlying(limbo.isFlying()); } protected void packQuitLocation() { @@ -107,28 +108,12 @@ public class ProcessSyncronousPlayerLogin implements Runnable { public void run() { // Limbo contains the State of the Player before /login if (limbo != null) { - // Op & Flying - restoreOpState(); - /* - * Restore Inventories and GameMode We need to restore them before - * teleport the player Cause in AuthMePlayerListener, we call - * ProtectInventoryEvent after Teleporting Also it's the current - * world inventory ! - */ - player.setGameMode(limbo.getGameMode()); - // Inventory - Make it after restore GameMode , cause we need to - // restore the - // right inventory in the right gamemode - if (Settings.protectInventoryBeforeLogInEnabled && plugin.inventoryProtector != null) { - restoreInventory(); - } - if (Settings.forceOnlyAfterLogin) { - player.setGameMode(GameMode.SURVIVAL); - } + // Restore Op state and Permission Group + restoreOpState(); + Utils.setGroup(player, GroupType.LOGGEDIN); if (!Settings.noTeleport) { - // Teleport if (Settings.isTeleportToSpawnEnabled && !Settings.isForceSpawnLocOnJoinEnabled && Settings.getForcedWorlds.contains(player.getWorld().getName())) { if (Settings.isSaveQuitLocationEnabled && auth.getQuitLocY() != 0) { packQuitLocation(); @@ -145,13 +130,17 @@ public class ProcessSyncronousPlayerLogin implements Runnable { } } - // Re-Force Survival GameMode if we need due to world change - // specification - if (Settings.isForceSurvivalModeEnabled) + if (Settings.isForceSurvivalModeEnabled && Settings.forceOnlyAfterLogin) { Utils.forceGM(player); + } else { + player.setGameMode(limbo.getGameMode()); + } + + restoreFlyghtState(); - // Restore Permission Group - Utils.setGroup(player, GroupType.LOGGEDIN); + if (Settings.protectInventoryBeforeLogInEnabled && plugin.inventoryProtector != null) { + restoreInventory(); + } // Cleanup no longer used temporary data LimboCache.getInstance().deleteLimboPlayer(name); @@ -160,23 +149,28 @@ public class ProcessSyncronousPlayerLogin implements Runnable { } } - // We can now display the join message - if (AuthMePlayerListener.joinMessage.containsKey(name) && AuthMePlayerListener.joinMessage.get(name) != null && !AuthMePlayerListener.joinMessage.get(name).isEmpty()) { - for (Player p : Utils.getOnlinePlayers()) { - if (p.isOnline()) - p.sendMessage(AuthMePlayerListener.joinMessage.get(name)); + // We can now display the join message (if delayed) + String jm = AuthMePlayerListener.joinMessage.get(name); + if (jm != null) { + if(!jm.isEmpty()) { + for (Player p : Utils.getOnlinePlayers()) { + if (p.isOnline()) + p.sendMessage(jm); + } } AuthMePlayerListener.joinMessage.remove(name); } - if (Settings.applyBlindEffect) + if (Settings.applyBlindEffect) { player.removePotionEffect(PotionEffectType.BLINDNESS); + } + if (!Settings.isMovementAllowed && Settings.isRemoveSpeedEnabled) { player.setWalkSpeed(0.2f); player.setFlySpeed(0.1f); } - // The Loginevent now fires (as intended) after everything is processed + // The Login event now fires (as intended) after everything is processed Bukkit.getServer().getPluginManager().callEvent(new LoginEvent(player, true)); player.saveData(); From adcd70b91dc7db2308da8cbe91daf2f4c8e01577 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sat, 21 Nov 2015 01:15:52 +0100 Subject: [PATCH 034/199] auto cleanup --- .../executable/authme/AccountsCommand.java | 4 ++-- .../authme/process/login/AsyncronousLogin.java | 2 +- .../login/ProcessSyncronousPlayerLogin.java | 1 - .../fr/xephi/authme/security/crypts/BCRYPT.java | 16 ++++++++-------- .../fr/xephi/authme/security/crypts/PHPBB.java | 2 +- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java index ad29b365..2145aa91 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java @@ -71,7 +71,7 @@ public class AccountsCommand extends ExecutableCommand { if (i != accountList.size()) { message.append(", "); } else { - message.append("."); + message.append('.'); } } sender.sendMessage("[AuthMe] " + playerQueryFinal + " has " + String.valueOf(accountList.size()) + " accounts"); @@ -109,7 +109,7 @@ public class AccountsCommand extends ExecutableCommand { if (i != accountList.size()) { message.append(", "); } else { - message.append("."); + message.append('.'); } } sender.sendMessage("[AuthMe] " + playerQueryFinal + " has " + String.valueOf(accountList.size()) + " accounts"); diff --git a/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java index 4144bd08..012c69e0 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java @@ -226,7 +226,7 @@ public class AsyncronousLogin { if (i != auths.size()) { message.append(", "); } else { - message.append("."); + message.append('.'); } } /* diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java index 41318843..ab98e281 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java @@ -1,7 +1,6 @@ package fr.xephi.authme.process.login; import org.bukkit.Bukkit; -import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java index 7f420c00..a84d774a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -247,8 +247,8 @@ public class BCRYPT implements EncryptionMethod { * Initialise the Blowfish key schedule */ private void init_key() { - P = (int[]) P_orig.clone(); - S = (int[]) S_orig.clone(); + P = P_orig.clone(); + S = S_orig.clone(); } /** @@ -323,7 +323,7 @@ public class BCRYPT implements EncryptionMethod { */ private byte[] crypt_raw(byte password[], byte salt[], int log_rounds) { int rounds, i, j; - int cdata[] = (int[]) bf_crypt_ciphertext.clone(); + int cdata[] = bf_crypt_ciphertext.clone(); int clen = cdata.length; byte ret[]; @@ -401,11 +401,11 @@ public class BCRYPT implements EncryptionMethod { rs.append("$2"); if (minor >= 'a') rs.append(minor); - rs.append("$"); + rs.append('$'); if (rounds < 10) - rs.append("0"); + rs.append('0'); rs.append(Integer.toString(rounds)); - rs.append("$"); + rs.append('$'); rs.append(encode_base64(saltb, saltb.length)); rs.append(encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1)); return rs.toString(); @@ -427,9 +427,9 @@ public class BCRYPT implements EncryptionMethod { rs.append("$2a$"); if (log_rounds < 10) - rs.append("0"); + rs.append('0'); rs.append(Integer.toString(log_rounds)); - rs.append("$"); + rs.append('$'); rs.append(encode_base64(rnd, rnd.length)); return rs.toString(); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java index cc5d4dc3..6defebf7 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java @@ -122,7 +122,7 @@ public class PHPBB implements EncryptionMethod { for (byte b : bytes) { String x = Integer.toHexString(b & 0xff); if (x.length() < 2) - r.append("0"); + r.append('0'); r.append(x); } return r.toString(); From 118c79401a1a7d71102d90aba3db1bfcf4be5211 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sat, 21 Nov 2015 01:27:06 +0100 Subject: [PATCH 035/199] Prepare the project for javadocs --- src/main/java/fr/xephi/authme/AuthMe.java | 146 ++++++++++++- .../java/fr/xephi/authme/ConsoleFilter.java | 7 + .../java/fr/xephi/authme/ConsoleLogger.java | 18 ++ .../java/fr/xephi/authme/DataManager.java | 43 ++++ .../java/fr/xephi/authme/ImageGenerator.java | 10 + .../java/fr/xephi/authme/Log4JFilter.java | 55 ++++- .../java/fr/xephi/authme/PerformBackup.java | 37 ++++ .../java/fr/xephi/authme/SendMailSSL.java | 10 + src/main/java/fr/xephi/authme/api/API.java | 67 ++++-- src/main/java/fr/xephi/authme/api/NewAPI.java | 77 ++++--- .../xephi/authme/cache/auth/PlayerAuth.java | 205 ++++++++++++++++++ .../xephi/authme/cache/auth/PlayerCache.java | 36 +++ .../authme/cache/backup/DataFileCache.java | 20 ++ .../xephi/authme/cache/backup/JsonCache.java | 41 ++++ .../xephi/authme/cache/limbo/LimboCache.java | 37 ++++ .../xephi/authme/cache/limbo/LimboPlayer.java | 56 +++++ .../command/CommandArgumentDescription.java | 14 +- .../authme/command/CommandDescription.java | 178 +++++++-------- .../xephi/authme/command/CommandHandler.java | 26 ++- .../xephi/authme/command/CommandManager.java | 14 +- .../fr/xephi/authme/command/CommandParts.java | 46 ++-- .../authme/command/CommandPermissions.java | 33 +-- .../authme/command/ExecutableCommand.java | 6 +- .../authme/command/FoundCommandResult.java | 38 ++-- .../command/executable/HelpCommand.java | 6 +- .../executable/authme/AccountsCommand.java | 6 +- .../executable/authme/AuthMeCommand.java | 6 +- .../authme/ChangePasswordCommand.java | 6 +- .../executable/authme/FirstSpawnCommand.java | 6 +- .../executable/authme/ForceLoginCommand.java | 6 +- .../executable/authme/GetEmailCommand.java | 6 +- .../executable/authme/GetIpCommand.java | 6 +- .../executable/authme/LastLoginCommand.java | 6 +- .../authme/PurgeBannedPlayersCommand.java | 6 +- .../executable/authme/PurgeCommand.java | 6 +- .../authme/PurgeLastPositionCommand.java | 6 +- .../executable/authme/RegisterCommand.java | 6 +- .../executable/authme/ReloadCommand.java | 6 +- .../executable/authme/ResetNameCommand.java | 6 +- .../executable/authme/SetEmailCommand.java | 6 +- .../authme/SetFirstSpawnCommand.java | 6 +- .../executable/authme/SetSpawnCommand.java | 6 +- .../executable/authme/SpawnCommand.java | 6 +- .../authme/SwitchAntiBotCommand.java | 6 +- .../executable/authme/UnregisterCommand.java | 6 +- .../executable/authme/VersionCommand.java | 10 +- .../executable/captcha/CaptchaCommand.java | 6 +- .../changepassword/ChangePasswordCommand.java | 6 +- .../converter/ConverterCommand.java | 21 +- .../executable/email/AddEmailCommand.java | 6 +- .../executable/email/ChangeEmailCommand.java | 6 +- .../executable/email/RecoverEmailCommand.java | 6 +- .../executable/login/LoginCommand.java | 6 +- .../executable/logout/LogoutCommand.java | 6 +- .../executable/register/RegisterCommand.java | 6 +- .../unregister/UnregisterCommand.java | 6 +- .../authme/command/help/HelpPrinter.java | 2 + .../authme/command/help/HelpProvider.java | 2 + .../authme/command/help/HelpSyntaxHelper.java | 6 +- .../fr/xephi/authme/converter/Converter.java | 2 + .../authme/converter/CrazyLoginConverter.java | 14 ++ .../fr/xephi/authme/converter/FlatToSql.java | 5 + .../xephi/authme/converter/FlatToSqlite.java | 28 +++ .../authme/converter/ForceFlatToSqlite.java | 11 + .../authme/converter/RakamakConverter.java | 14 ++ .../authme/converter/RoyalAuthConverter.java | 10 + .../authme/converter/RoyalAuthYamlReader.java | 14 ++ .../fr/xephi/authme/converter/SqlToFlat.java | 11 + .../authme/converter/vAuthConverter.java | 11 + .../authme/converter/vAuthFileReader.java | 21 ++ .../authme/converter/xAuthConverter.java | 11 + .../xephi/authme/converter/xAuthToFlat.java | 25 +++ .../authme/datasource/CacheDataSource.java | 158 ++++++++++++++ .../xephi/authme/datasource/DataSource.java | 119 ++++++++++ .../authme/datasource/DatabaseCalls.java | 155 +++++++++++++ .../fr/xephi/authme/datasource/FlatFile.java | 151 +++++++++++++ .../fr/xephi/authme/datasource/MySQL.java | 181 ++++++++++++++++ .../fr/xephi/authme/datasource/SQLite.java | 173 +++++++++++++++ .../events/AuthMeAsyncPreLoginEvent.java | 21 ++ .../authme/events/AuthMeTeleportEvent.java | 22 ++ .../fr/xephi/authme/events/CustomEvent.java | 23 ++ .../events/FirstSpawnTeleportEvent.java | 23 ++ .../fr/xephi/authme/events/LoginEvent.java | 30 +++ .../fr/xephi/authme/events/LogoutEvent.java | 21 ++ .../events/PasswordEncryptionEvent.java | 26 +++ .../authme/events/ProtectInventoryEvent.java | 33 +++ .../authme/events/RegisterTeleportEvent.java | 22 ++ .../authme/events/ResetInventoryEvent.java | 13 ++ .../authme/events/RestoreInventoryEvent.java | 18 ++ .../authme/events/SpawnTeleportEvent.java | 28 +++ .../xephi/authme/hooks/BungeeCordMessage.java | 13 ++ .../java/fr/xephi/authme/hooks/EssSpawn.java | 10 + .../authme/listener/AuthMeBlockListener.java | 14 ++ .../authme/listener/AuthMeEntityListener.java | 42 ++++ .../AuthMeInventoryPacketAdapter.java | 15 ++ .../authme/listener/AuthMePlayerListener.java | 122 +++++++++++ .../listener/AuthMePlayerListener16.java | 10 + .../listener/AuthMePlayerListener18.java | 10 + .../authme/listener/AuthMePluginListener.java | 2 + .../authme/listener/AuthMeServerListener.java | 18 ++ .../java/fr/xephi/authme/modules/Module.java | 12 + .../xephi/authme/modules/ModuleManager.java | 34 +++ .../authme/permission/PermissionsManager.java | 51 +++-- .../fr/xephi/authme/process/Management.java | 52 ++++- .../process/email/AsyncChangeEmail.java | 17 ++ .../authme/process/join/AsyncronousJoin.java | 17 ++ .../process/login/AsyncronousLogin.java | 24 ++ .../login/ProcessSyncronousPlayerLogin.java | 16 ++ .../process/logout/AsyncronousLogout.java | 8 + .../logout/ProcessSyncronousPlayerLogout.java | 11 + .../authme/process/quit/AsyncronousQuit.java | 9 + .../quit/ProcessSyncronousPlayerQuit.java | 14 ++ .../process/register/AsyncRegister.java | 23 ++ .../register/ProcessSyncEmailRegister.java | 11 + .../ProcessSyncronousPasswordRegister.java | 15 ++ .../unregister/AsyncronousUnregister.java | 13 ++ .../xephi/authme/security/HashAlgorithm.java | 10 + .../authme/security/PasswordSecurity.java | 32 +++ .../xephi/authme/security/RandomString.java | 9 + .../xephi/authme/security/crypts/BCRYPT.java | 71 ++++-- .../authme/security/crypts/BCRYPT2Y.java | 20 ++ .../authme/security/crypts/CRAZYCRYPT1.java | 29 ++- .../authme/security/crypts/CryptPBKDF2.java | 20 ++ .../security/crypts/CryptPBKDF2Django.java | 20 ++ .../authme/security/crypts/DOUBLEMD5.java | 26 +++ .../security/crypts/EncryptionMethod.java | 15 +- .../fr/xephi/authme/security/crypts/IPB3.java | 26 +++ .../xephi/authme/security/crypts/JOOMLA.java | 26 +++ .../fr/xephi/authme/security/crypts/MD5.java | 26 +++ .../xephi/authme/security/crypts/MD5VB.java | 26 +++ .../fr/xephi/authme/security/crypts/MYBB.java | 26 +++ .../xephi/authme/security/crypts/PHPBB.java | 73 +++++++ .../authme/security/crypts/PHPFUSION.java | 26 +++ .../authme/security/crypts/PLAINTEXT.java | 20 ++ .../authme/security/crypts/ROYALAUTH.java | 27 +++ .../authme/security/crypts/SALTED2MD5.java | 26 +++ .../authme/security/crypts/SALTEDSHA512.java | 26 +++ .../fr/xephi/authme/security/crypts/SHA1.java | 26 +++ .../xephi/authme/security/crypts/SHA256.java | 26 +++ .../xephi/authme/security/crypts/SHA512.java | 26 +++ .../fr/xephi/authme/security/crypts/SMF.java | 26 +++ .../fr/xephi/authme/security/crypts/WBB3.java | 26 +++ .../fr/xephi/authme/security/crypts/WBB4.java | 20 ++ .../authme/security/crypts/WHIRLPOOL.java | 26 +++ .../authme/security/crypts/WORDPRESS.java | 42 ++++ .../xephi/authme/security/crypts/XAUTH.java | 25 +++ .../fr/xephi/authme/security/crypts/XF.java | 32 +++ .../authme/security/pbkdf2/BinTools.java | 24 +- .../authme/security/pbkdf2/MacBasedPRF.java | 21 ++ .../xephi/authme/security/pbkdf2/PBKDF2.java | 20 +- .../authme/security/pbkdf2/PBKDF2Engine.java | 71 ++++-- .../security/pbkdf2/PBKDF2Formatter.java | 9 +- .../security/pbkdf2/PBKDF2HexFormatter.java | 13 ++ .../security/pbkdf2/PBKDF2Parameters.java | 40 ++++ .../fr/xephi/authme/security/pbkdf2/PRF.java | 8 +- .../authme/settings/CustomConfiguration.java | 19 ++ .../fr/xephi/authme/settings/Messages.java | 21 ++ .../xephi/authme/settings/OtherAccounts.java | 22 ++ .../fr/xephi/authme/settings/Settings.java | 48 +++- .../java/fr/xephi/authme/settings/Spawn.java | 27 +++ .../xephi/authme/task/ChangePasswordTask.java | 13 ++ .../fr/xephi/authme/task/MessageTask.java | 13 ++ .../fr/xephi/authme/task/TimeoutTask.java | 16 ++ .../java/fr/xephi/authme/util/ListUtils.java | 14 +- .../java/fr/xephi/authme/util/Profiler.java | 30 +-- .../fr/xephi/authme/util/StringUtils.java | 12 +- src/main/java/fr/xephi/authme/util/Utils.java | 77 +++++++ .../java/fr/xephi/authme/Log4JFilterTest.java | 6 +- 168 files changed, 4375 insertions(+), 422 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 6ad0226b..bdf8ad09 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -59,6 +59,8 @@ import fr.xephi.authme.util.Utils; import net.milkbowl.vault.permission.Permission; import net.minelink.ctplus.CombatTagPlus; +/** + */ public class AuthMe extends JavaPlugin { /** Defines the name of the plugin. */ @@ -111,30 +113,58 @@ public class AuthMe extends JavaPlugin { // In case we need to cache PlayerAuths, prevent connection before it's done private boolean canConnect = true; + /** + * Method canConnect. + * @return boolean + */ public boolean canConnect() { return canConnect; } + /** + * Method setCanConnect. + * @param canConnect boolean + */ public void setCanConnect(boolean canConnect) { this.canConnect = canConnect; } + /** + * Method getInstance. + * @return AuthMe + */ public static AuthMe getInstance() { return plugin; } + /** + * Method getSettings. + * @return Settings + */ public Settings getSettings() { return settings; } + /** + * Method setMessages. + * @param m Messages + */ public void setMessages(Messages m) { this.m = m; } + /** + * Method getMessages. + * @return Messages + */ public Messages getMessages() { return m; } + /** + * Method onEnable. + * @see org.bukkit.plugin.Plugin#onEnable() + */ @Override public void onEnable() { // Set the Instance @@ -362,6 +392,10 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.info("AuthMe " + this.getDescription().getVersion() + " correctly enabled!"); } + /** + * Method onDisable. + * @see org.bukkit.plugin.Plugin#onDisable() + */ @Override public void onDisable() { // Save player data @@ -404,12 +438,20 @@ public class AuthMe extends JavaPlugin { // Show the exception message and stop/unload the server/plugin as defined // in the configuration + /** + * Method stopOrUnload. + * @param e Exception + */ public void stopOrUnload(Exception e) { ConsoleLogger.showError(e.getMessage()); stopOrUnload(); } // Initialize and setup the database + /** + * Method setupDatabase. + * @throws Exception + */ public void setupDatabase() throws Exception { if (database != null) database.close(); @@ -465,8 +507,8 @@ public class AuthMe extends JavaPlugin { /** * Get the permissions manager instance. * - * @return Permissions Manager instance. - */ + + * @return Permissions Manager instance. */ public PermissionsManager getPermissionsManager() { return this.permsMan; } @@ -571,7 +613,8 @@ public class AuthMe extends JavaPlugin { * * @param player * @param perm - * @return + + * @return boolean */ public boolean authmePermissible(Player player, String perm) { // New code: @@ -591,7 +634,8 @@ public class AuthMe extends JavaPlugin { * * @param sender * @param perm - * @return + + * @return boolean */ public boolean authmePermissible(CommandSender sender, String perm) { // Handle players with the permissions manager @@ -606,6 +650,10 @@ public class AuthMe extends JavaPlugin { } // Save Player Data + /** + * Method savePlayer. + * @param player Player + */ public void savePlayer(Player player) { if ((Utils.isNPC(player)) || (Utils.isUnrestricted(player))) { return; @@ -634,6 +682,11 @@ public class AuthMe extends JavaPlugin { } // Select the player to kick when a vip player join the server when full + /** + * Method generateKickPlayer. + * @param collection Collection + * @return Player + */ public Player generateKickPlayer(Collection collection) { Player player = null; for (Player p : collection) { @@ -678,6 +731,11 @@ public class AuthMe extends JavaPlugin { } // Return the spawn location of a player + /** + * Method getSpawnLocation. + * @param player Player + * @return Location + */ public Location getSpawnLocation(Player player) { World world = player.getWorld(); String[] spawnPriority = Settings.spawnPriority.split(","); @@ -700,11 +758,21 @@ public class AuthMe extends JavaPlugin { } // Return the default spawnpoint of a world + /** + * Method getDefaultSpawn. + * @param world World + * @return Location + */ private Location getDefaultSpawn(World world) { return world.getSpawnLocation(); } // Return the multiverse spawnpoint of a world + /** + * Method getMultiverseSpawn. + * @param world World + * @return Location + */ private Location getMultiverseSpawn(World world) { if (multiverse != null && Settings.multiverse) { try { @@ -717,6 +785,10 @@ public class AuthMe extends JavaPlugin { } // Return the essentials spawnpoint + /** + * Method getEssentialsSpawn. + * @return Location + */ private Location getEssentialsSpawn() { if (essentialsSpawn != null) { return essentialsSpawn; @@ -725,6 +797,11 @@ public class AuthMe extends JavaPlugin { } // Return the authme soawnpoint + /** + * Method getAuthMeSpawn. + * @param player Player + * @return Location + */ private Location getAuthMeSpawn(Player player) { if ((!database.isAuthAvailable(player.getName().toLowerCase()) || !player.hasPlayedBefore()) && (Spawn.getInstance().getFirstSpawn() != null)) { return Spawn.getInstance().getFirstSpawn(); @@ -735,11 +812,19 @@ public class AuthMe extends JavaPlugin { return player.getWorld().getSpawnLocation(); } + /** + * Method switchAntiBotMod. + * @param mode boolean + */ public void switchAntiBotMod(boolean mode) { this.antibotMod = mode; Settings.switchAntiBotMod(mode); } + /** + * Method getAntiBotModMode. + * @return boolean + */ public boolean getAntiBotModMode() { return this.antibotMod; } @@ -766,6 +851,12 @@ public class AuthMe extends JavaPlugin { }, 1, 1200 * Settings.delayRecall); } + /** + * Method replaceAllInfos. + * @param message String + * @param player Player + * @return String + */ public String replaceAllInfos(String message, Player player) { int playersOnline = Utils.getOnlinePlayers().size(); message = message.replace("&", "\u00a7"); @@ -781,6 +872,11 @@ public class AuthMe extends JavaPlugin { return message; } + /** + * Method getIP. + * @param player Player + * @return String + */ public String getIP(Player player) { String name = player.getName().toLowerCase(); String ip = player.getAddress().getAddress().getHostAddress(); @@ -794,6 +890,12 @@ public class AuthMe extends JavaPlugin { return ip; } + /** + * Method isLoggedIp. + * @param name String + * @param ip String + * @return boolean + */ public boolean isLoggedIp(String name, String ip) { int count = 0; for (Player player : Utils.getOnlinePlayers()) { @@ -803,6 +905,12 @@ public class AuthMe extends JavaPlugin { return count >= Settings.getMaxLoginPerIp; } + /** + * Method hasJoinedIp. + * @param name String + * @param ip String + * @return boolean + */ public boolean hasJoinedIp(String name, String ip) { int count = 0; for (Player player : Utils.getOnlinePlayers()) { @@ -812,6 +920,10 @@ public class AuthMe extends JavaPlugin { return count >= Settings.getMaxJoinPerIp; } + /** + * Method getModuleManager. + * @return ModuleManager + */ public ModuleManager getModuleManager() { return moduleManager; } @@ -821,6 +933,7 @@ public class AuthMe extends JavaPlugin { * * @param player * player + * @return String */ @Deprecated public String getVeryGamesIP(Player player) { @@ -840,11 +953,21 @@ public class AuthMe extends JavaPlugin { return realIP; } + /** + * Method getCountryCode. + * @param ip String + * @return String + */ @Deprecated public String getCountryCode(String ip) { return Utils.getCountryCode(ip); } + /** + * Method getCountryName. + * @param ip String + * @return String + */ @Deprecated public String getCountryName(String ip) { return Utils.getCountryName(ip); @@ -853,8 +976,8 @@ public class AuthMe extends JavaPlugin { /** * Get the command handler instance. * - * @return Command handler. - */ + + * @return Command handler. */ public CommandHandler getCommandHandler() { return this.commandHandler; } @@ -871,7 +994,8 @@ public class AuthMe extends JavaPlugin { * @param args * The command arguments (Bukkit). * - * @return True if the command was executed, false otherwise. + + * @return True if the command was executed, false otherwise. * @see org.bukkit.command.CommandExecutor#onCommand(CommandSender, Command, String, String[]) */ @Override public boolean onCommand(CommandSender sender, Command cmd, @@ -888,9 +1012,9 @@ public class AuthMe extends JavaPlugin { /** * Get the current installed AuthMeReloaded version name. * + * @return The version name of the currently installed AuthMeReloaded - * instance. - */ + * instance. */ public static String getVersionName() { return PLUGIN_VERSION_NAME; } @@ -898,9 +1022,9 @@ public class AuthMe extends JavaPlugin { /** * Get the current installed AuthMeReloaded version code. * + * @return The version code of the currently installed AuthMeReloaded - * instance. - */ + * instance. */ public static int getVersionCode() { return PLUGIN_VERSION_CODE; } diff --git a/src/main/java/fr/xephi/authme/ConsoleFilter.java b/src/main/java/fr/xephi/authme/ConsoleFilter.java index 10ac38de..60282088 100644 --- a/src/main/java/fr/xephi/authme/ConsoleFilter.java +++ b/src/main/java/fr/xephi/authme/ConsoleFilter.java @@ -6,12 +6,19 @@ import java.util.logging.LogRecord; /** * * @author Xephi59 + * @version $Revision: 1.0 $ */ public class ConsoleFilter implements Filter { public ConsoleFilter() { } + /** + * Method isLoggable. + * @param record LogRecord + * @return boolean + * @see java.util.logging.Filter#isLoggable(LogRecord) + */ @Override public boolean isLoggable(LogRecord record) { try { diff --git a/src/main/java/fr/xephi/authme/ConsoleLogger.java b/src/main/java/fr/xephi/authme/ConsoleLogger.java index e2c4ba5f..1e58e913 100644 --- a/src/main/java/fr/xephi/authme/ConsoleLogger.java +++ b/src/main/java/fr/xephi/authme/ConsoleLogger.java @@ -13,11 +13,17 @@ import com.google.common.base.Throwables; import fr.xephi.authme.api.NewAPI; import fr.xephi.authme.settings.Settings; +/** + */ public class ConsoleLogger { private static final Logger log = AuthMe.getInstance().getLogger(); private static final DateFormat df = new SimpleDateFormat("[MM-dd HH:mm:ss]"); + /** + * Method info. + * @param message String + */ public static void info(String message) { log.info("[AuthMe] " + message); if (Settings.useLogging) { @@ -29,6 +35,10 @@ public class ConsoleLogger { } } + /** + * Method showError. + * @param message String + */ public static void showError(String message) { log.warning("[AuthMe] " + message); if (Settings.useLogging) { @@ -40,6 +50,10 @@ public class ConsoleLogger { } } + /** + * Method writeLog. + * @param message String + */ public static void writeLog(String message) { try { Files.write(Settings.LOG_FILE.toPath(), (message + NewAPI.newline).getBytes(), @@ -49,6 +63,10 @@ public class ConsoleLogger { } } + /** + * Method writeStackTrace. + * @param ex Exception + */ public static void writeStackTrace(Exception ex) { if (Settings.useLogging) { String dateTime; diff --git a/src/main/java/fr/xephi/authme/DataManager.java b/src/main/java/fr/xephi/authme/DataManager.java index 95a9ab53..fc65845e 100644 --- a/src/main/java/fr/xephi/authme/DataManager.java +++ b/src/main/java/fr/xephi/authme/DataManager.java @@ -15,10 +15,16 @@ import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; import net.milkbowl.vault.permission.Permission; +/** + */ public class DataManager { public AuthMe plugin; + /** + * Constructor for DataManager. + * @param plugin AuthMe + */ public DataManager(AuthMe plugin) { this.plugin = plugin; } @@ -26,6 +32,11 @@ public class DataManager { public void run() { } + /** + * Method getOfflinePlayer. + * @param name String + * @return OfflinePlayer + */ public synchronized OfflinePlayer getOfflinePlayer(final String name) { ExecutorService executor = Executors.newSingleThreadExecutor(); Future result = executor.submit(new Callable() { @@ -52,6 +63,10 @@ public class DataManager { } } + /** + * Method purgeAntiXray. + * @param cleared List + */ public synchronized void purgeAntiXray(List cleared) { int i = 0; for (String name : cleared) { @@ -71,6 +86,10 @@ public class DataManager { ConsoleLogger.info("AutoPurgeDatabase : Remove " + i + " AntiXRayData Files"); } + /** + * Method purgeLimitedCreative. + * @param cleared List + */ public synchronized void purgeLimitedCreative(List cleared) { int i = 0; for (String name : cleared) { @@ -100,6 +119,10 @@ public class DataManager { ConsoleLogger.info("AutoPurgeDatabase : Remove " + i + " LimitedCreative Survival, Creative and Adventure files"); } + /** + * Method purgeDat. + * @param cleared List + */ public synchronized void purgeDat(List cleared) { int i = 0; for (String name : cleared) { @@ -126,6 +149,10 @@ public class DataManager { ConsoleLogger.info("AutoPurgeDatabase : Remove " + i + " .dat Files"); } + /** + * Method purgeEssentials. + * @param cleared List + */ @SuppressWarnings("deprecation") public void purgeEssentials(List cleared) { int i = 0; @@ -145,6 +172,11 @@ public class DataManager { ConsoleLogger.info("AutoPurgeDatabase : Remove " + i + " EssentialsFiles"); } + /** + * Method purgePermissions. + * @param cleared List + * @param permission Permission + */ public synchronized void purgePermissions(List cleared, Permission permission) { int i = 0; @@ -161,6 +193,12 @@ public class DataManager { ConsoleLogger.info("AutoPurgeDatabase : Remove " + i + " Permissions"); } + /** + * Method isOnline. + * @param player Player + * @param name String + * @return boolean + */ public boolean isOnline(Player player, final String name) { if (player.isOnline()) return true; @@ -185,6 +223,11 @@ public class DataManager { } } + /** + * Method getOnlinePlayerLower. + * @param name String + * @return Player + */ public Player getOnlinePlayerLower(String name) { name = name.toLowerCase(); for (Player player : Utils.getOnlinePlayers()) { diff --git a/src/main/java/fr/xephi/authme/ImageGenerator.java b/src/main/java/fr/xephi/authme/ImageGenerator.java index 2ca37cfa..ea3d6634 100644 --- a/src/main/java/fr/xephi/authme/ImageGenerator.java +++ b/src/main/java/fr/xephi/authme/ImageGenerator.java @@ -6,13 +6,23 @@ import java.awt.GradientPaint; import java.awt.Graphics2D; import java.awt.image.BufferedImage; +/** + */ public class ImageGenerator { private String pass; + /** + * Constructor for ImageGenerator. + * @param pass String + */ public ImageGenerator(String pass) { this.pass = pass; } + /** + * Method generateImage. + * @return BufferedImage + */ public BufferedImage generateImage() { BufferedImage image = new BufferedImage(200, 60, BufferedImage.TYPE_BYTE_INDEXED); Graphics2D graphics = image.createGraphics(); diff --git a/src/main/java/fr/xephi/authme/Log4JFilter.java b/src/main/java/fr/xephi/authme/Log4JFilter.java index 365b2cec..ed9ada5e 100644 --- a/src/main/java/fr/xephi/authme/Log4JFilter.java +++ b/src/main/java/fr/xephi/authme/Log4JFilter.java @@ -11,6 +11,7 @@ import fr.xephi.authme.util.StringUtils; /** * Implements a filter for Log4j to skip sensitive AuthMe commands. * @author Xephi59 + * @version $Revision: 1.0 $ */ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { @@ -23,6 +24,12 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { public Log4JFilter() { } + /** + * Method filter. + * @param record LogEvent + * @return Result + * @see org.apache.logging.log4j.core.Filter#filter(LogEvent) + */ @Override public Result filter(LogEvent record) { if (record == null) { @@ -31,12 +38,32 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { return validateMessage(record.getMessage()); } + /** + * Method filter. + * @param arg0 Logger + * @param arg1 Level + * @param arg2 Marker + * @param message String + * @param arg4 Object[] + * @return Result + * @see org.apache.logging.log4j.core.Filter#filter(Logger, Level, Marker, String, Object[]) + */ @Override public Result filter(Logger arg0, Level arg1, Marker arg2, String message, Object... arg4) { return validateMessage(message); } + /** + * Method filter. + * @param arg0 Logger + * @param arg1 Level + * @param arg2 Marker + * @param message Object + * @param arg4 Throwable + * @return Result + * @see org.apache.logging.log4j.core.Filter#filter(Logger, Level, Marker, Object, Throwable) + */ @Override public Result filter(Logger arg0, Level arg1, Marker arg2, Object message, Throwable arg4) { @@ -46,17 +73,37 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { return validateMessage(message.toString()); } + /** + * Method filter. + * @param arg0 Logger + * @param arg1 Level + * @param arg2 Marker + * @param message Message + * @param arg4 Throwable + * @return Result + * @see org.apache.logging.log4j.core.Filter#filter(Logger, Level, Marker, Message, Throwable) + */ @Override public Result filter(Logger arg0, Level arg1, Marker arg2, Message message, Throwable arg4) { return validateMessage(message); } + /** + * Method getOnMatch. + * @return Result + * @see org.apache.logging.log4j.core.Filter#getOnMatch() + */ @Override public Result getOnMatch() { return Result.NEUTRAL; } + /** + * Method getOnMismatch. + * @return Result + * @see org.apache.logging.log4j.core.Filter#getOnMismatch() + */ @Override public Result getOnMismatch() { return Result.NEUTRAL; @@ -68,8 +115,8 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { * data. * * @param message the Message object to verify - * @return the Result value - */ + + * @return the Result value */ private static Result validateMessage(Message message) { if (message == null) { return Result.NEUTRAL; @@ -82,8 +129,8 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { * depending on whether the message contains sensitive AuthMe data. * * @param message the message to verify - * @return the Result value - */ + + * @return the Result value */ private static Result validateMessage(String message) { if (message == null) { return Result.NEUTRAL; diff --git a/src/main/java/fr/xephi/authme/PerformBackup.java b/src/main/java/fr/xephi/authme/PerformBackup.java index 0f7e1fcc..9cda7fac 100644 --- a/src/main/java/fr/xephi/authme/PerformBackup.java +++ b/src/main/java/fr/xephi/authme/PerformBackup.java @@ -14,6 +14,7 @@ import fr.xephi.authme.settings.Settings; /** * * @author stefano + * @version $Revision: 1.0 $ */ public class PerformBackup { @@ -26,10 +27,18 @@ public class PerformBackup { private String path = AuthMe.getInstance().getDataFolder() + File.separator + "backups" + File.separator + "backup" + dateString; private AuthMe instance; + /** + * Constructor for PerformBackup. + * @param instance AuthMe + */ public PerformBackup(AuthMe instance) { this.setInstance(instance); } + /** + * Method doBackup. + * @return boolean + */ public boolean doBackup() { switch (Settings.getDataSource) { @@ -44,6 +53,10 @@ public class PerformBackup { return false; } + /** + * Method MySqlBackup. + * @return boolean + */ private boolean MySqlBackup() { File dirBackup = new File(AuthMe.getInstance().getDataFolder() + "/backups"); @@ -83,6 +96,11 @@ public class PerformBackup { return false; } + /** + * Method FileBackup. + * @param backend String + * @return boolean + */ private boolean FileBackup(String backend) { File dirBackup = new File(AuthMe.getInstance().getDataFolder() + "/backups"); @@ -103,6 +121,11 @@ public class PerformBackup { * Check if we are under Windows and correct location of mysqldump.exe * otherwise return error. */ + /** + * Method checkWindows. + * @param windowsPath String + * @return boolean + */ private boolean checkWindows(String windowsPath) { String isWin = System.getProperty("os.name").toLowerCase(); if (isWin.indexOf("win") >= 0) { @@ -118,6 +141,12 @@ public class PerformBackup { /* * Copyr src bytefile into dst file */ + /** + * Method copy. + * @param src File + * @param dst File + * @throws IOException + */ void copy(File src, File dst) throws IOException { InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst); @@ -132,10 +161,18 @@ public class PerformBackup { out.close(); } + /** + * Method setInstance. + * @param instance AuthMe + */ public void setInstance(AuthMe instance) { this.instance = instance; } + /** + * Method getInstance. + * @return AuthMe + */ public AuthMe getInstance() { return instance; } diff --git a/src/main/java/fr/xephi/authme/SendMailSSL.java b/src/main/java/fr/xephi/authme/SendMailSSL.java index eb4c2b8e..f3ed378d 100644 --- a/src/main/java/fr/xephi/authme/SendMailSSL.java +++ b/src/main/java/fr/xephi/authme/SendMailSSL.java @@ -15,15 +15,25 @@ import fr.xephi.authme.settings.Settings; /** * * @author Xephi59 + * @version $Revision: 1.0 $ */ public class SendMailSSL { public AuthMe plugin; + /** + * Constructor for SendMailSSL. + * @param plugin AuthMe + */ public SendMailSSL(AuthMe plugin) { this.plugin = plugin; } + /** + * Method main. + * @param auth PlayerAuth + * @param newPass String + */ public void main(final PlayerAuth auth, final String newPass) { String sendername; diff --git a/src/main/java/fr/xephi/authme/api/API.java b/src/main/java/fr/xephi/authme/api/API.java index 96dcc2a6..8f5bf87e 100644 --- a/src/main/java/fr/xephi/authme/api/API.java +++ b/src/main/java/fr/xephi/authme/api/API.java @@ -15,11 +15,17 @@ import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; +/** + */ public class API { public static final String newline = System.getProperty("line.separator"); public static AuthMe instance; + /** + * Constructor for API. + * @param instance AuthMe + */ @Deprecated public API(AuthMe instance) { API.instance = instance; @@ -28,8 +34,8 @@ public class API { /** * Hook into AuthMe * - * @return AuthMe instance - */ + + * @return AuthMe instance */ @Deprecated public static AuthMe hookAuthMe() { if (instance != null) @@ -42,6 +48,10 @@ public class API { return instance; } + /** + * Method getPlugin. + * @return AuthMe + */ @Deprecated public AuthMe getPlugin() { return instance; @@ -50,8 +60,8 @@ public class API { /** * * @param player - * @return true if player is authenticate - */ + + * @return true if player is authenticate */ @Deprecated public static boolean isAuthenticated(Player player) { return PlayerCache.getInstance().isAuthenticated(player.getName()); @@ -60,8 +70,8 @@ public class API { /** * * @param player - * @return true if player is a npc - */ + + * @return true if player is a npc */ @Deprecated public boolean isaNPC(Player player) { return Utils.isNPC(player); @@ -70,8 +80,8 @@ public class API { /** * * @param player - * @return true if player is a npc - */ + + * @return true if player is a npc */ @Deprecated public boolean isNPC(Player player) { return Utils.isNPC(player); @@ -80,13 +90,18 @@ public class API { /** * * @param player - * @return true if the player is unrestricted - */ + + * @return true if the player is unrestricted */ @Deprecated public static boolean isUnrestricted(Player player) { return Utils.isUnrestricted(player); } + /** + * Method getLastLocation. + * @param player Player + * @return Location + */ @Deprecated public static Location getLastLocation(Player player) { try { @@ -104,6 +119,12 @@ public class API { } } + /** + * Method setPlayerInventory. + * @param player Player + * @param content ItemStack[] + * @param armor ItemStack[] + */ @Deprecated public static void setPlayerInventory(Player player, ItemStack[] content, ItemStack[] armor) { @@ -117,8 +138,8 @@ public class API { /** * * @param playerName - * @return true if player is registered - */ + + * @return true if player is registered */ @Deprecated public static boolean isRegistered(String playerName) { String player = playerName.toLowerCase(); @@ -126,10 +147,11 @@ public class API { } /** - * @param String - * playerName, String passwordToCheck - * @return true if the password is correct , false else - */ + + + * @param playerName String + * @param passwordToCheck String + * @return true if the password is correct , false else */ @Deprecated public static boolean checkPassword(String playerName, String passwordToCheck) { @@ -147,10 +169,11 @@ public class API { /** * Register a player * - * @param String - * playerName, String password - * @return true if the player is register correctly - */ + + + * @param playerName String + * @param password String + * @return true if the player is register correctly */ @Deprecated public static boolean registerPlayer(String playerName, String password) { try { @@ -172,8 +195,8 @@ public class API { /** * Force a player to login * - * @param Player - * player + + * @param player * player */ @Deprecated public static void forceLogin(Player player) { diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index 6c48286a..ea1fc4ac 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -15,16 +15,26 @@ import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; +/** + */ public class NewAPI { public static final String newline = System.getProperty("line.separator"); public static NewAPI singleton; public AuthMe plugin; + /** + * Constructor for NewAPI. + * @param plugin AuthMe + */ public NewAPI(AuthMe plugin) { this.plugin = plugin; } + /** + * Constructor for NewAPI. + * @param serv Server + */ public NewAPI(Server serv) { this.plugin = (AuthMe) serv.getPluginManager().getPlugin("AuthMe"); } @@ -32,8 +42,8 @@ public class NewAPI { /** * Hook into AuthMe * - * @return AuthMe plugin - */ + + * @return AuthMe plugin */ public static NewAPI getInstance() { if (singleton != null) return singleton; @@ -46,6 +56,10 @@ public class NewAPI { return singleton; } + /** + * Method getPlugin. + * @return AuthMe + */ public AuthMe getPlugin() { return plugin; } @@ -53,8 +67,8 @@ public class NewAPI { /** * * @param player - * @return true if player is authenticate - */ + + * @return true if player is authenticate */ public boolean isAuthenticated(Player player) { return PlayerCache.getInstance().isAuthenticated(player.getName()); } @@ -62,8 +76,8 @@ public class NewAPI { /** * * @param player - * @return true if player is a npc - */ + + * @return true if player is a npc */ public boolean isNPC(Player player) { return Utils.isNPC(player); } @@ -71,12 +85,17 @@ public class NewAPI { /** * * @param player - * @return true if the player is unrestricted - */ + + * @return true if the player is unrestricted */ public boolean isUnrestricted(Player player) { return Utils.isUnrestricted(player); } + /** + * Method getLastLocation. + * @param player Player + * @return Location + */ public Location getLastLocation(Player player) { try { PlayerAuth auth = PlayerCache.getInstance().getAuth(player.getName().toLowerCase()); @@ -95,18 +114,19 @@ public class NewAPI { /** * * @param playerName - * @return true if player is registered - */ + + * @return true if player is registered */ public boolean isRegistered(String playerName) { String player = playerName.toLowerCase(); return plugin.database.isAuthAvailable(player); } /** - * @param String - * playerName, String passwordToCheck - * @return true if the password is correct , false else - */ + + + * @param playerName String + * @param passwordToCheck String + * @return true if the password is correct , false else */ public boolean checkPassword(String playerName, String passwordToCheck) { if (!isRegistered(playerName)) return false; @@ -122,10 +142,11 @@ public class NewAPI { /** * Register a player * - * @param String - * playerName, String password - * @return true if the player is register correctly - */ + + + * @param playerName String + * @param password String + * @return true if the player is register correctly */ public boolean registerPlayer(String playerName, String password) { try { String name = playerName.toLowerCase(); @@ -143,8 +164,8 @@ public class NewAPI { /** * Force a player to login * - * @param Player - * player + + * @param player * player */ public void forceLogin(Player player) { plugin.management.performLogin(player, "dontneed", true); @@ -153,8 +174,8 @@ public class NewAPI { /** * Force a player to logout * - * @param Player - * player + + * @param player * player */ public void forceLogout(Player player) { @@ -164,10 +185,10 @@ public class NewAPI { /** * Force a player to register * - * @param Player - * player - * @param String - * password + + + * @param player * player + * @param password String */ public void forceRegister(Player player, String password) { @@ -177,8 +198,8 @@ public class NewAPI { /** * Force a player to unregister * - * @param Player - * player + + * @param player * player */ public void forceUnregister(Player player) { diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java index 3046c9c0..9ca05728 100644 --- a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java +++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java @@ -3,6 +3,8 @@ package fr.xephi.authme.cache.auth; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.settings.Settings; +/** + */ public class PlayerAuth { private String nickname; @@ -18,38 +20,132 @@ public class PlayerAuth { private String email; private String realName; + /** + * Constructor for PlayerAuth. + * @param nickname String + * @param ip String + * @param lastLogin long + * @param realName String + */ public PlayerAuth(String nickname, String ip, long lastLogin, String realName) { this(nickname, "", "", -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); } + /** + * Constructor for PlayerAuth. + * @param nickname String + * @param x double + * @param y double + * @param z double + * @param world String + * @param realName String + */ public PlayerAuth(String nickname, double x, double y, double z, String world, String realName) { this(nickname, "", "", -1, "127.0.0.1", System.currentTimeMillis(), x, y, z, world, "your@email.com", realName); } + /** + * Constructor for PlayerAuth. + * @param nickname String + * @param hash String + * @param ip String + * @param lastLogin long + * @param realName String + */ public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String realName) { this(nickname, hash, "", -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); } + /** + * Constructor for PlayerAuth. + * @param nickname String + * @param hash String + * @param ip String + * @param lastLogin long + * @param email String + * @param realName String + */ public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String email, String realName) { this(nickname, hash, "", -1, ip, lastLogin, 0, 0, 0, "world", email, realName); } + /** + * Constructor for PlayerAuth. + * @param nickname String + * @param hash String + * @param salt String + * @param ip String + * @param lastLogin long + * @param realName String + */ public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, String realName) { this(nickname, hash, salt, -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); } + /** + * Constructor for PlayerAuth. + * @param nickname String + * @param hash String + * @param ip String + * @param lastLogin long + * @param x double + * @param y double + * @param z double + * @param world String + * @param email String + * @param realName String + */ public PlayerAuth(String nickname, String hash, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { this(nickname, hash, "", -1, ip, lastLogin, x, y, z, world, email, realName); } + /** + * Constructor for PlayerAuth. + * @param nickname String + * @param hash String + * @param salt String + * @param ip String + * @param lastLogin long + * @param x double + * @param y double + * @param z double + * @param world String + * @param email String + * @param realName String + */ public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { this(nickname, hash, salt, -1, ip, lastLogin, x, y, z, world, email, realName); } + /** + * Constructor for PlayerAuth. + * @param nickname String + * @param hash String + * @param salt String + * @param groupId int + * @param ip String + * @param lastLogin long + * @param realName String + */ public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip, long lastLogin, String realName) { this(nickname, hash, salt, groupId, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); } + /** + * Constructor for PlayerAuth. + * @param nickname String + * @param hash String + * @param salt String + * @param groupId int + * @param ip String + * @param lastLogin long + * @param x double + * @param y double + * @param z double + * @param world String + * @param email String + * @param realName String + */ public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { this.nickname = nickname; this.hash = hash; @@ -65,6 +161,10 @@ public class PlayerAuth { this.realName = realName; } + /** + * Method set. + * @param auth PlayerAuth + */ public void set(PlayerAuth auth) { this.setEmail(auth.getEmail()); this.setHash(auth.getHash()); @@ -79,94 +179,186 @@ public class PlayerAuth { this.setRealName(auth.getRealName()); } + /** + * Method setName. + * @param nickname String + */ public void setName(String nickname) { this.nickname = nickname; } + /** + * Method getNickname. + * @return String + */ public String getNickname() { return nickname; } + /** + * Method getRealName. + * @return String + */ public String getRealName() { return realName; } + /** + * Method setRealName. + * @param realName String + */ public void setRealName(String realName) { this.realName = realName; } + /** + * Method getGroupId. + * @return int + */ public int getGroupId() { return groupId; } + /** + * Method setQuitLocX. + * @param d double + */ public void setQuitLocX(double d) { this.x = d; } + /** + * Method getQuitLocX. + * @return double + */ public double getQuitLocX() { return x; } + /** + * Method setQuitLocY. + * @param d double + */ public void setQuitLocY(double d) { this.y = d; } + /** + * Method getQuitLocY. + * @return double + */ public double getQuitLocY() { return y; } + /** + * Method setQuitLocZ. + * @param d double + */ public void setQuitLocZ(double d) { this.z = d; } + /** + * Method getQuitLocZ. + * @return double + */ public double getQuitLocZ() { return z; } + /** + * Method setWorld. + * @param world String + */ public void setWorld(String world) { this.world = world; } + /** + * Method getWorld. + * @return String + */ public String getWorld() { return world; } + /** + * Method setIp. + * @param ip String + */ public void setIp(String ip) { this.ip = ip; } + /** + * Method getIp. + * @return String + */ public String getIp() { return ip; } + /** + * Method setLastLogin. + * @param lastLogin long + */ public void setLastLogin(long lastLogin) { this.lastLogin = lastLogin; } + /** + * Method getLastLogin. + * @return long + */ public long getLastLogin() { return lastLogin; } + /** + * Method setEmail. + * @param email String + */ public void setEmail(String email) { this.email = email; } + /** + * Method getEmail. + * @return String + */ public String getEmail() { return email; } + /** + * Method setSalt. + * @param salt String + */ public void setSalt(String salt) { this.salt = salt; } + /** + * Method getSalt. + * @return String + */ public String getSalt() { return this.salt; } + /** + * Method setHash. + * @param hash String + */ public void setHash(String hash) { this.hash = hash; } + /** + * Method getHash. + * @return String + */ public String getHash() { if (Settings.getPasswordHash == HashAlgorithm.MD5VB) { if (salt != null && !salt.isEmpty() && Settings.getPasswordHash == HashAlgorithm.MD5VB) { @@ -176,6 +368,11 @@ public class PlayerAuth { return hash; } + /** + * Method equals. + * @param obj Object + * @return boolean + */ @Override public boolean equals(Object obj) { if (!(obj instanceof PlayerAuth)) { @@ -185,6 +382,10 @@ public class PlayerAuth { return other.getIp().equals(this.ip) && other.getNickname().equals(this.nickname); } + /** + * Method hashCode. + * @return int + */ @Override public int hashCode() { int hashCode = 7; @@ -193,6 +394,10 @@ public class PlayerAuth { return hashCode; } + /** + * Method toString. + * @return String + */ @Override public String toString() { return ("Player : " + nickname + " | " + realName diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java index d8b0f404..d4df6e06 100644 --- a/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java +++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java @@ -2,6 +2,8 @@ package fr.xephi.authme.cache.auth; import java.util.concurrent.ConcurrentHashMap; +/** + */ public class PlayerCache { private volatile static PlayerCache singleton; @@ -11,27 +13,53 @@ public class PlayerCache { cache = new ConcurrentHashMap<>(); } + /** + * Method addPlayer. + * @param auth PlayerAuth + */ public void addPlayer(PlayerAuth auth) { cache.put(auth.getNickname().toLowerCase(), auth); } + /** + * Method updatePlayer. + * @param auth PlayerAuth + */ public void updatePlayer(PlayerAuth auth) { cache.remove(auth.getNickname().toLowerCase()); cache.put(auth.getNickname().toLowerCase(), auth); } + /** + * Method removePlayer. + * @param user String + */ public void removePlayer(String user) { cache.remove(user.toLowerCase()); } + /** + * Method isAuthenticated. + * @param user String + * @return boolean + */ public boolean isAuthenticated(String user) { return cache.containsKey(user.toLowerCase()); } + /** + * Method getAuth. + * @param user String + * @return PlayerAuth + */ public PlayerAuth getAuth(String user) { return cache.get(user.toLowerCase()); } + /** + * Method getInstance. + * @return PlayerCache + */ public static PlayerCache getInstance() { if (singleton == null) { singleton = new PlayerCache(); @@ -39,10 +67,18 @@ public class PlayerCache { return singleton; } + /** + * Method getLogged. + * @return int + */ public int getLogged() { return cache.size(); } + /** + * Method getCache. + * @return ConcurrentHashMap + */ public ConcurrentHashMap getCache() { return this.cache; } diff --git a/src/main/java/fr/xephi/authme/cache/backup/DataFileCache.java b/src/main/java/fr/xephi/authme/cache/backup/DataFileCache.java index 5aa40dde..07534751 100644 --- a/src/main/java/fr/xephi/authme/cache/backup/DataFileCache.java +++ b/src/main/java/fr/xephi/authme/cache/backup/DataFileCache.java @@ -1,25 +1,45 @@ package fr.xephi.authme.cache.backup; +/** + */ public class DataFileCache { private String group; private boolean operator; private boolean flying; + /** + * Constructor for DataFileCache. + * @param group String + * @param operator boolean + * @param flying boolean + */ public DataFileCache(String group, boolean operator, boolean flying) { this.group = group; this.operator = operator; this.flying = flying; } + /** + * Method getGroup. + * @return String + */ public String getGroup() { return group; } + /** + * Method getOperator. + * @return boolean + */ public boolean getOperator() { return operator; } + /** + * Method isFlying. + * @return boolean + */ public boolean isFlying() { return flying; } diff --git a/src/main/java/fr/xephi/authme/cache/backup/JsonCache.java b/src/main/java/fr/xephi/authme/cache/backup/JsonCache.java index 31c647cb..d3913471 100644 --- a/src/main/java/fr/xephi/authme/cache/backup/JsonCache.java +++ b/src/main/java/fr/xephi/authme/cache/backup/JsonCache.java @@ -22,6 +22,8 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; +/** + */ public class JsonCache { private final Gson gson; @@ -39,6 +41,11 @@ public class JsonCache { .create(); } + /** + * Method createCache. + * @param player Player + * @param playerData DataFileCache + */ public void createCache(Player player, DataFileCache playerData) { if (player == null) { return; @@ -68,6 +75,11 @@ public class JsonCache { } } + /** + * Method readCache. + * @param player Player + * @return DataFileCache + */ public DataFileCache readCache(Player player) { String path; try { @@ -90,7 +102,16 @@ public class JsonCache { } } + /** + */ private class PlayerDataSerializer implements JsonSerializer { + /** + * Method serialize. + * @param dataFileCache DataFileCache + * @param type Type + * @param jsonSerializationContext JsonSerializationContext + * @return JsonElement + */ @Override public JsonElement serialize(DataFileCache dataFileCache, Type type, JsonSerializationContext jsonSerializationContext) { JsonObject jsonObject = new JsonObject(); @@ -102,7 +123,18 @@ public class JsonCache { } } + /** + */ private static class PlayerDataDeserializer implements JsonDeserializer { + /** + * Method deserialize. + * @param jsonElement JsonElement + * @param type Type + * @param jsonDeserializationContext JsonDeserializationContext + * @return DataFileCache + * @throws JsonParseException + * @see com.google.gson.JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext) + */ @Override public DataFileCache deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { JsonObject jsonObject = jsonElement.getAsJsonObject(); @@ -128,6 +160,10 @@ public class JsonCache { } } + /** + * Method removeCache. + * @param player Player + */ public void removeCache(Player player) { String path; try { @@ -144,6 +180,11 @@ public class JsonCache { } } + /** + * Method doesCacheExist. + * @param player Player + * @return boolean + */ public boolean doesCacheExist(Player player) { String path; try { diff --git a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java index 354d4c72..1bfe8164 100644 --- a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java @@ -14,6 +14,8 @@ import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.events.ResetInventoryEvent; import fr.xephi.authme.settings.Settings; +/** + */ public class LimboCache { private volatile static LimboCache singleton; @@ -21,12 +23,20 @@ public class LimboCache { private JsonCache playerData; public AuthMe plugin; + /** + * Constructor for LimboCache. + * @param plugin AuthMe + */ private LimboCache(AuthMe plugin) { this.plugin = plugin; this.cache = new ConcurrentHashMap<>(); this.playerData = new JsonCache(); } + /** + * Method addLimboPlayer. + * @param player Player + */ public void addLimboPlayer(Player player) { String name = player.getName().toLowerCase(); Location loc = player.getLocation(); @@ -75,22 +85,45 @@ public class LimboCache { cache.put(name, new LimboPlayer(name, loc, gameMode, operator, playerGroup, flying)); } + /** + * Method addLimboPlayer. + * @param player Player + * @param group String + */ public void addLimboPlayer(Player player, String group) { cache.put(player.getName().toLowerCase(), new LimboPlayer(player.getName().toLowerCase(), group)); } + /** + * Method deleteLimboPlayer. + * @param name String + */ public void deleteLimboPlayer(String name) { cache.remove(name); } + /** + * Method getLimboPlayer. + * @param name String + * @return LimboPlayer + */ public LimboPlayer getLimboPlayer(String name) { return cache.get(name); } + /** + * Method hasLimboPlayer. + * @param name String + * @return boolean + */ public boolean hasLimboPlayer(String name) { return cache.containsKey(name); } + /** + * Method getInstance. + * @return LimboCache + */ public static LimboCache getInstance() { if (singleton == null) { singleton = new LimboCache(AuthMe.getInstance()); @@ -98,6 +131,10 @@ public class LimboCache { return singleton; } + /** + * Method updateLimboPlayer. + * @param player Player + */ public void updateLimboPlayer(Player player) { if (this.hasLimboPlayer(player.getName().toLowerCase())) { this.deleteLimboPlayer(player.getName().toLowerCase()); diff --git a/src/main/java/fr/xephi/authme/cache/limbo/LimboPlayer.java b/src/main/java/fr/xephi/authme/cache/limbo/LimboPlayer.java index f4ae0466..3d2dc304 100644 --- a/src/main/java/fr/xephi/authme/cache/limbo/LimboPlayer.java +++ b/src/main/java/fr/xephi/authme/cache/limbo/LimboPlayer.java @@ -4,6 +4,8 @@ import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.scheduler.BukkitTask; +/** + */ public class LimboPlayer { private String name; @@ -15,6 +17,15 @@ public class LimboPlayer { private String group = ""; private boolean flying = false; + /** + * Constructor for LimboPlayer. + * @param name String + * @param loc Location + * @param gameMode GameMode + * @param operator boolean + * @param group String + * @param flying boolean + */ public LimboPlayer(String name, Location loc, GameMode gameMode, boolean operator, String group, boolean flying) { this.name = name; @@ -25,51 +36,96 @@ public class LimboPlayer { this.flying = flying; } + /** + * Constructor for LimboPlayer. + * @param name String + * @param group String + */ public LimboPlayer(String name, String group) { this.name = name; this.group = group; } + /** + * Method getName. + * @return String + */ public String getName() { return name; } + /** + * Method getLoc. + * @return Location + */ public Location getLoc() { return loc; } + /** + * Method getGameMode. + * @return GameMode + */ public GameMode getGameMode() { return gameMode; } + /** + * Method getOperator. + * @return boolean + */ public boolean getOperator() { return operator; } + /** + * Method getGroup. + * @return String + */ public String getGroup() { return group; } + /** + * Method setTimeoutTaskId. + * @param i BukkitTask + */ public void setTimeoutTaskId(BukkitTask i) { if (this.timeoutTaskId != null) this.timeoutTaskId.cancel(); this.timeoutTaskId = i; } + /** + * Method getTimeoutTaskId. + * @return BukkitTask + */ public BukkitTask getTimeoutTaskId() { return timeoutTaskId; } + /** + * Method setMessageTaskId. + * @param messageTaskId BukkitTask + */ public void setMessageTaskId(BukkitTask messageTaskId) { if (this.messageTaskId != null) this.messageTaskId.cancel(); this.messageTaskId = messageTaskId; } + /** + * Method getMessageTaskId. + * @return BukkitTask + */ public BukkitTask getMessageTaskId() { return messageTaskId; } + /** + * Method isFlying. + * @return boolean + */ public boolean isFlying() { return flying; } diff --git a/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java b/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java index a5ea63d0..e47311bf 100644 --- a/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java @@ -1,5 +1,7 @@ package fr.xephi.authme.command; +/** + */ public class CommandArgumentDescription { // TODO: Allow argument to consist of infinite parts. Possible + + * @author Gabriele + * @version $Revision: 1.0 $ */ public class Management { @@ -24,11 +25,21 @@ public class Management { private final BukkitScheduler sched; public static RandomString rdm = new RandomString(Settings.captchaLength); + /** + * Constructor for Management. + * @param plugin AuthMe + */ public Management(AuthMe plugin) { this.plugin = plugin; this.sched = this.plugin.getServer().getScheduler(); } + /** + * Method performLogin. + * @param player Player + * @param password String + * @param forceLogin boolean + */ public void performLogin(final Player player, final String password, final boolean forceLogin) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -39,6 +50,10 @@ public class Management { }); } + /** + * Method performLogout. + * @param player Player + */ public void performLogout(final Player player) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -49,6 +64,12 @@ public class Management { }); } + /** + * Method performRegister. + * @param player Player + * @param password String + * @param email String + */ public void performRegister(final Player player, final String password, final String email) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -59,6 +80,12 @@ public class Management { }); } + /** + * Method performUnregister. + * @param player Player + * @param password String + * @param force boolean + */ public void performUnregister(final Player player, final String password, final boolean force) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -69,6 +96,10 @@ public class Management { }); } + /** + * Method performJoin. + * @param player Player + */ public void performJoin(final Player player) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -80,6 +111,11 @@ public class Management { }); } + /** + * Method performQuit. + * @param player Player + * @param isKick boolean + */ public void performQuit(final Player player, final boolean isKick) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -91,6 +127,12 @@ public class Management { }); } + /** + * Method performAddEmail. + * @param player Player + * @param newEmail String + * @param newEmailVerify String + */ public void performAddEmail(final Player player, final String newEmail, final String newEmailVerify) { sched.runTaskAsynchronously(plugin, new Runnable() { @Override @@ -100,6 +142,12 @@ public class Management { }); } + /** + * Method performChangeEmail. + * @param player Player + * @param oldEmail String + * @param newEmail String + */ public void performChangeEmail(final Player player, final String oldEmail, final String newEmail) { sched.runTaskAsynchronously(plugin, new Runnable() { @Override diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java index 0b8f7c64..3fddc162 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java @@ -11,6 +11,8 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; +/** + */ public class AsyncChangeEmail { private final Player player; @@ -20,6 +22,14 @@ public class AsyncChangeEmail { private final String newEmailVerify; private final Messages m; + /** + * Constructor for AsyncChangeEmail. + * @param player Player + * @param plugin AuthMe + * @param oldEmail String + * @param newEmail String + * @param newEmailVerify String + */ public AsyncChangeEmail(Player player, AuthMe plugin, String oldEmail, String newEmail, String newEmailVerify) { this.player = player; this.plugin = plugin; @@ -29,6 +39,13 @@ public class AsyncChangeEmail { this.m = Messages.getInstance(); } + /** + * Constructor for AsyncChangeEmail. + * @param player Player + * @param plugin AuthMe + * @param oldEmail String + * @param newEmail String + */ public AsyncChangeEmail(Player player, AuthMe plugin, String oldEmail, String newEmail) { this(player, plugin, oldEmail, newEmail, newEmail); } diff --git a/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java index 60aae943..aeae0f14 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java @@ -28,6 +28,8 @@ import fr.xephi.authme.task.TimeoutTask; import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.Utils.GroupType; +/** + */ public class AsyncronousJoin { private final AuthMe plugin; @@ -37,6 +39,12 @@ public class AsyncronousJoin { private final Messages m; private final BukkitScheduler sched; + /** + * Constructor for AsyncronousJoin. + * @param player Player + * @param plugin AuthMe + * @param database DataSource + */ public AsyncronousJoin(Player player, AuthMe plugin, DataSource database) { this.player = player; this.plugin = plugin; @@ -245,6 +253,10 @@ public class AsyncronousJoin { LimboCache.getInstance().getLimboPlayer(name).setMessageTaskId(msgTask); } + /** + * Method needFirstSpawn. + * @return boolean + */ private boolean needFirstSpawn() { if (player.hasPlayedBefore()) return false; @@ -269,6 +281,11 @@ public class AsyncronousJoin { return true; } + /** + * Method placePlayerSafely. + * @param player Player + * @param spawnLoc Location + */ private void placePlayerSafely(final Player player, final Location spawnLoc) { if (spawnLoc == null) return; diff --git a/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java index 012c69e0..2181a381 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java @@ -22,6 +22,8 @@ import fr.xephi.authme.settings.Settings; import fr.xephi.authme.task.MessageTask; import fr.xephi.authme.util.Utils; +/** + */ public class AsyncronousLogin { protected Player player; @@ -34,6 +36,14 @@ public class AsyncronousLogin { private static RandomString rdm = new RandomString(Settings.captchaLength); private Messages m = Messages.getInstance(); + /** + * Constructor for AsyncronousLogin. + * @param player Player + * @param password String + * @param forceLogin boolean + * @param plugin AuthMe + * @param data DataSource + */ public AsyncronousLogin(Player player, String password, boolean forceLogin, AuthMe plugin, DataSource data) { this.player = player; @@ -45,10 +55,18 @@ public class AsyncronousLogin { this.database = data; } + /** + * Method getIP. + * @return String + */ protected String getIP() { return plugin.getIP(player); } + /** + * Method needsCaptcha. + * @return boolean + */ protected boolean needsCaptcha() { if (Settings.useCaptcha) { if (!plugin.captcha.containsKey(name)) { @@ -75,6 +93,7 @@ public class AsyncronousLogin { /** * Checks the precondition for authentication (like user known) and returns * the playerAuth-State + * @return PlayerAuth */ protected PlayerAuth preAuth() { if (PlayerCache.getInstance().isAuthenticated(name)) { @@ -200,6 +219,11 @@ public class AsyncronousLogin { } } + /** + * Method displayOtherAccounts. + * @param auth PlayerAuth + * @param p Player + */ public void displayOtherAccounts(PlayerAuth auth, Player p) { if (!Settings.displayOtherAccounts) { return; diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java index ab98e281..953459c2 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java @@ -21,6 +21,8 @@ import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.Utils.GroupType; +/** + */ public class ProcessSyncronousPlayerLogin implements Runnable { private LimboPlayer limbo; @@ -32,6 +34,12 @@ public class ProcessSyncronousPlayerLogin implements Runnable { private PluginManager pm; private JsonCache playerCache; + /** + * Constructor for ProcessSyncronousPlayerLogin. + * @param player Player + * @param plugin AuthMe + * @param data DataSource + */ public ProcessSyncronousPlayerLogin(Player player, AuthMe plugin, DataSource data) { this.plugin = plugin; @@ -44,6 +52,10 @@ public class ProcessSyncronousPlayerLogin implements Runnable { this.playerCache = new JsonCache(); } + /** + * Method getLimbo. + * @return LimboPlayer + */ public LimboPlayer getLimbo() { return limbo; } @@ -103,6 +115,10 @@ public class ProcessSyncronousPlayerLogin implements Runnable { } } + /** + * Method run. + * @see java.lang.Runnable#run() + */ @Override public void run() { // Limbo contains the State of the Player before /login diff --git a/src/main/java/fr/xephi/authme/process/logout/AsyncronousLogout.java b/src/main/java/fr/xephi/authme/process/logout/AsyncronousLogout.java index e728473f..6adb1452 100644 --- a/src/main/java/fr/xephi/authme/process/logout/AsyncronousLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/AsyncronousLogout.java @@ -12,6 +12,8 @@ import fr.xephi.authme.settings.Messages; import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.Utils.GroupType; +/** + */ public class AsyncronousLogout { protected Player player; @@ -21,6 +23,12 @@ public class AsyncronousLogout { protected boolean canLogout = true; private Messages m = Messages.getInstance(); + /** + * Constructor for AsyncronousLogout. + * @param player Player + * @param plugin AuthMe + * @param database DataSource + */ public AsyncronousLogout(Player player, AuthMe plugin, DataSource database) { this.player = player; diff --git a/src/main/java/fr/xephi/authme/process/logout/ProcessSyncronousPlayerLogout.java b/src/main/java/fr/xephi/authme/process/logout/ProcessSyncronousPlayerLogout.java index 7c199201..3281d3e1 100644 --- a/src/main/java/fr/xephi/authme/process/logout/ProcessSyncronousPlayerLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/ProcessSyncronousPlayerLogout.java @@ -16,6 +16,8 @@ import fr.xephi.authme.settings.Settings; import fr.xephi.authme.task.MessageTask; import fr.xephi.authme.task.TimeoutTask; +/** + */ public class ProcessSyncronousPlayerLogout implements Runnable { protected Player player; @@ -23,12 +25,21 @@ public class ProcessSyncronousPlayerLogout implements Runnable { protected String name; private Messages m = Messages.getInstance(); + /** + * Constructor for ProcessSyncronousPlayerLogout. + * @param player Player + * @param plugin AuthMe + */ public ProcessSyncronousPlayerLogout(Player player, AuthMe plugin) { this.player = player; this.plugin = plugin; this.name = player.getName().toLowerCase(); } + /** + * Method run. + * @see java.lang.Runnable#run() + */ @Override public void run() { if (plugin.sessions.containsKey(name)) diff --git a/src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java b/src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java index dfcb295f..5d5e1ebe 100644 --- a/src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java @@ -15,6 +15,8 @@ import fr.xephi.authme.listener.AuthMePlayerListener; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; +/** + */ public class AsyncronousQuit { protected AuthMe plugin; @@ -26,6 +28,13 @@ public class AsyncronousQuit { private boolean needToChange = false; private boolean isKick = false; + /** + * Constructor for AsyncronousQuit. + * @param p Player + * @param plugin AuthMe + * @param database DataSource + * @param isKick boolean + */ public AsyncronousQuit(Player p, AuthMe plugin, DataSource database, boolean isKick) { this.player = p; diff --git a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java index 1b661b6f..125d3021 100644 --- a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java @@ -6,6 +6,8 @@ import org.bukkit.entity.Player; import fr.xephi.authme.AuthMe; import fr.xephi.authme.settings.Settings; +/** + */ public class ProcessSyncronousPlayerQuit implements Runnable { protected AuthMe plugin; @@ -14,6 +16,14 @@ public class ProcessSyncronousPlayerQuit implements Runnable { protected boolean isFlying; protected boolean needToChange; + /** + * Constructor for ProcessSyncronousPlayerQuit. + * @param plugin AuthMe + * @param player Player + * @param isOp boolean + * @param isFlying boolean + * @param needToChange boolean + */ public ProcessSyncronousPlayerQuit(AuthMe plugin, Player player , boolean isOp, boolean isFlying , boolean needToChange) { @@ -24,6 +34,10 @@ public class ProcessSyncronousPlayerQuit implements Runnable { this.needToChange = needToChange; } + /** + * Method run. + * @see java.lang.Runnable#run() + */ @Override public void run() { diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index 6a42abba..12b26327 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -14,6 +14,8 @@ import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; +/** + */ public class AsyncRegister { protected Player player; @@ -24,6 +26,14 @@ public class AsyncRegister { private DataSource database; private Messages m = Messages.getInstance(); + /** + * Constructor for AsyncRegister. + * @param player Player + * @param password String + * @param email String + * @param plugin AuthMe + * @param data DataSource + */ public AsyncRegister(Player player, String password, String email, AuthMe plugin, DataSource data) { this.player = player; @@ -34,10 +44,19 @@ public class AsyncRegister { this.database = data; } + /** + * Method getIp. + * @return String + */ protected String getIp() { return plugin.getIP(player); } + /** + * Method preRegisterCheck. + * @return boolean + * @throws Exception + */ protected boolean preRegisterCheck() throws Exception { String lowpass = password.toLowerCase(); if (PlayerCache.getInstance().isAuthenticated(name)) { @@ -92,6 +111,10 @@ public class AsyncRegister { } } + /** + * Method emailRegister. + * @throws Exception + */ protected void emailRegister() throws Exception { if (Settings.getmaxRegPerEmail > 0) { if (!plugin.getPermissionsManager().hasPermission(player, "authme.allow2accounts") && database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) { diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java index 683320a1..0908d8a4 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java @@ -14,6 +14,8 @@ import fr.xephi.authme.task.MessageTask; import fr.xephi.authme.task.TimeoutTask; import fr.xephi.authme.util.Utils; +/** + */ public class ProcessSyncEmailRegister implements Runnable { protected Player player; @@ -21,12 +23,21 @@ public class ProcessSyncEmailRegister implements Runnable { private AuthMe plugin; private Messages m = Messages.getInstance(); + /** + * Constructor for ProcessSyncEmailRegister. + * @param player Player + * @param plugin AuthMe + */ public ProcessSyncEmailRegister(Player player, AuthMe plugin) { this.player = player; this.name = player.getName().toLowerCase(); this.plugin = plugin; } + /** + * Method run. + * @see java.lang.Runnable#run() + */ @Override public void run() { LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(name); diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncronousPasswordRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncronousPasswordRegister.java index b747394c..ca1a67e0 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncronousPasswordRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncronousPasswordRegister.java @@ -19,6 +19,8 @@ import fr.xephi.authme.task.MessageTask; import fr.xephi.authme.task.TimeoutTask; import fr.xephi.authme.util.Utils; +/** + */ public class ProcessSyncronousPasswordRegister implements Runnable { protected Player player; @@ -26,6 +28,11 @@ public class ProcessSyncronousPasswordRegister implements Runnable { private AuthMe plugin; private Messages m = Messages.getInstance(); + /** + * Constructor for ProcessSyncronousPasswordRegister. + * @param player Player + * @param plugin AuthMe + */ public ProcessSyncronousPasswordRegister(Player player, AuthMe plugin) { this.player = player; this.name = player.getName().toLowerCase(); @@ -44,6 +51,10 @@ public class ProcessSyncronousPasswordRegister implements Runnable { } } + /** + * Method forceLogin. + * @param player Player + */ protected void forceLogin(Player player) { Utils.teleportToSpawn(player); if (LimboCache.getInstance().hasLimboPlayer(name)) @@ -63,6 +74,10 @@ public class ProcessSyncronousPasswordRegister implements Runnable { } } + /** + * Method run. + * @see java.lang.Runnable#run() + */ @Override public void run() { LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(name); diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsyncronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsyncronousUnregister.java index e7aafcb0..0b9f3a48 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsyncronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsyncronousUnregister.java @@ -21,6 +21,8 @@ import fr.xephi.authme.task.TimeoutTask; import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.Utils.GroupType; +/** + */ public class AsyncronousUnregister { protected Player player; @@ -31,6 +33,13 @@ public class AsyncronousUnregister { protected boolean force; private JsonCache playerCache; + /** + * Constructor for AsyncronousUnregister. + * @param player Player + * @param password String + * @param force boolean + * @param plugin AuthMe + */ public AsyncronousUnregister(Player player, String password, boolean force, AuthMe plugin) { this.player = player; @@ -41,6 +50,10 @@ public class AsyncronousUnregister { this.playerCache = new JsonCache(); } + /** + * Method getIp. + * @return String + */ protected String getIp() { return plugin.getIP(player); } diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index 935c9c1c..5ce86359 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -2,6 +2,8 @@ package fr.xephi.authme.security; import org.apache.commons.lang.ObjectUtils.Null; +/** + */ public enum HashAlgorithm { MD5(fr.xephi.authme.security.crypts.MD5.class), @@ -35,10 +37,18 @@ public enum HashAlgorithm { Class classe; + /** + * Constructor for HashAlgorithm. + * @param classe Class + */ HashAlgorithm(Class classe) { this.classe = classe; } + /** + * Method getclasse. + * @return Class + */ public Class getclasse() { return classe; } diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index baf7ae5a..1a0013d0 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -15,11 +15,19 @@ import fr.xephi.authme.security.crypts.BCRYPT; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.settings.Settings; +/** + */ public class PasswordSecurity { private static SecureRandom rnd = new SecureRandom(); public static HashMap userSalt = new HashMap<>(); + /** + * Method createSalt. + * @param length int + * @return String + * @throws NoSuchAlgorithmException + */ public static String createSalt(int length) throws NoSuchAlgorithmException { byte[] msg = new byte[40]; @@ -30,6 +38,14 @@ public class PasswordSecurity { return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)).substring(0, length); } + /** + * Method getHash. + * @param alg HashAlgorithm + * @param password String + * @param playerName String + * @return String + * @throws NoSuchAlgorithmException + */ public static String getHash(HashAlgorithm alg, String password, String playerName) throws NoSuchAlgorithmException { EncryptionMethod method; @@ -125,6 +141,14 @@ public class PasswordSecurity { return method.getHash(password, salt, playerName); } + /** + * Method comparePasswordWithHash. + * @param password String + * @param hash String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + */ public static boolean comparePasswordWithHash(String password, String hash, String playerName) throws NoSuchAlgorithmException { HashAlgorithm algo = Settings.getPasswordHash; @@ -155,6 +179,14 @@ public class PasswordSecurity { return false; } + /** + * Method compareWithAllEncryptionMethod. + * @param password String + * @param hash String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + */ private static boolean compareWithAllEncryptionMethod(String password, String hash, String playerName) throws NoSuchAlgorithmException { for (HashAlgorithm algo : HashAlgorithm.values()) { diff --git a/src/main/java/fr/xephi/authme/security/RandomString.java b/src/main/java/fr/xephi/authme/security/RandomString.java index 96fef22f..86b7683a 100644 --- a/src/main/java/fr/xephi/authme/security/RandomString.java +++ b/src/main/java/fr/xephi/authme/security/RandomString.java @@ -6,6 +6,7 @@ import java.util.Random; /** * * @author Xephi59 + * @version $Revision: 1.0 $ */ public class RandomString { @@ -22,6 +23,10 @@ public class RandomString { private final char[] buf; + /** + * Constructor for RandomString. + * @param length int + */ public RandomString(int length) { if (length < 1) throw new IllegalArgumentException("length < 1: " + length); @@ -29,6 +34,10 @@ public class RandomString { random.setSeed(Calendar.getInstance().getTimeInMillis()); } + /** + * Method nextString. + * @return String + */ public String nextString() { for (int idx = 0; idx < buf.length; ++idx) buf[idx] = chars[random.nextInt(chars.length)]; diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java index a84d774a..7e7a2f77 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -92,9 +92,9 @@ public class BCRYPT implements EncryptionMethod { * * @param d the byte array to encode * @param len the number of bytes to encode - * @return base64-encoded string - * @throws IllegalArgumentException if the length is invalid - */ + + + * @return base64-encoded string * @throws IllegalArgumentException if the length is invalid */ private static String encode_base64(byte d[], int len) throws IllegalArgumentException { int off = 0; @@ -133,8 +133,8 @@ public class BCRYPT implements EncryptionMethod { * range-checking againt conversion table * * @param x the base64-encoded value - * @return the decoded value of x - */ + + * @return the decoded value of x */ private static byte char64(char x) { if ((int) x > index_64.length) return -1; @@ -148,9 +148,9 @@ public class BCRYPT implements EncryptionMethod { * * @param s the string to decode * @param maxolen the maximum number of bytes to decode - * @return an array containing the decoded bytes - * @throws IllegalArgumentException if maxolen is invalid - */ + + + * @return an array containing the decoded bytes * @throws IllegalArgumentException if maxolen is invalid */ private static byte[] decode_base64(String s, int maxolen) throws IllegalArgumentException { StringBuffer rs = new StringBuffer(); @@ -227,8 +227,8 @@ public class BCRYPT implements EncryptionMethod { * @param data the string to extract the data from * @param offp a "pointer" (as a one-entry array) to the current offset into * data - * @return the next word of material from data - */ + + * @return the next word of material from data */ private static int streamtoword(byte data[], int offp[]) { int i; int word = 0; @@ -319,8 +319,8 @@ public class BCRYPT implements EncryptionMethod { * @param salt the binary salt to hash with the password * @param log_rounds the binary logarithm of the number of rounds of hashing to * apply - * @return an array containing the binary hashed password - */ + + * @return an array containing the binary hashed password */ private byte[] crypt_raw(byte password[], byte salt[], int log_rounds) { int rounds, i, j; int cdata[] = bf_crypt_ciphertext.clone(); @@ -360,8 +360,8 @@ public class BCRYPT implements EncryptionMethod { * * @param password the password to hash * @param salt the salt to hash with (perhaps generated using BCrypt.gensalt) - * @return the hashed password - */ + + * @return the hashed password */ public static String hashpw(String password, String salt) { BCRYPT B; String real_salt; @@ -417,8 +417,8 @@ public class BCRYPT implements EncryptionMethod { * @param log_rounds the log2 of the number of rounds of hashing to apply - the * work factor therefore increases as 2**log_rounds. * @param random an instance of SecureRandom to use - * @return an encoded salt value - */ + + * @return an encoded salt value */ public static String gensalt(int log_rounds, SecureRandom random) { StringBuffer rs = new StringBuffer(); byte rnd[] = new byte[BCRYPT_SALT_LEN]; @@ -439,8 +439,8 @@ public class BCRYPT implements EncryptionMethod { * * @param log_rounds the log2 of the number of rounds of hashing to apply - the * work factor therefore increases as 2**log_rounds. - * @return an encoded salt value - */ + + * @return an encoded salt value */ public static String gensalt(int log_rounds) { return gensalt(log_rounds, new SecureRandom()); } @@ -449,8 +449,8 @@ public class BCRYPT implements EncryptionMethod { * Generate a salt for use with the BCrypt.hashpw() method, selecting a * reasonable default for the number of hashing rounds to apply * - * @return an encoded salt value - */ + + * @return an encoded salt value */ public static String gensalt() { return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS); } @@ -460,8 +460,8 @@ public class BCRYPT implements EncryptionMethod { * * @param plaintext the plaintext password to verify * @param hashed the previously-hashed password - * @return true if the passwords match, false otherwise - */ + + * @return true if the passwords match, false otherwise */ public static boolean checkpw(String plaintext, String hashed) { return (hashed.compareTo(hashpw(plaintext, hashed)) == 0); } @@ -473,7 +473,8 @@ public class BCRYPT implements EncryptionMethod { * @param text plaintext or hashed text * @param hashed the previously-hashed password * @param rounds number of rounds to hash the password - * @return + + * @return boolean */ public static boolean checkpw(String text, String hashed, int rounds) { boolean matched = false; @@ -493,18 +494,42 @@ public class BCRYPT implements EncryptionMethod { return matched; } + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return hashpw(password, salt); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { return checkpw(password, hash); } + /** + * Method getDoubleHash. + * @param text String + * @param salt String + * @return String + */ public static String getDoubleHash(String text, String salt) { String hash = hashpw(text, salt); return hashpw(text, hash); diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java index 2c1ae57a..9cbe12fb 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java @@ -2,8 +2,19 @@ package fr.xephi.authme.security.crypts; import java.security.NoSuchAlgorithmException; +/** + */ public class BCRYPT2Y implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -12,6 +23,15 @@ public class BCRYPT2Y implements EncryptionMethod { return (BCRYPT.hashpw(password, salt)); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java index a5688953..174531a8 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java @@ -4,11 +4,22 @@ import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +/** + */ public class CRAZYCRYPT1 implements EncryptionMethod { protected final Charset charset = Charset.forName("UTF-8"); private static final char[] CRYPTCHARS = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -21,13 +32,27 @@ public class CRAZYCRYPT1 implements EncryptionMethod { return null; } } - +/** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { return hash.equals(getHash(password, null, playerName)); } - +/** + * Method byteArrayToHexString. + * @param args byte[] + * @return String + */ + public static String byteArrayToHexString(final byte... args) { final char[] chars = new char[args.length * 2]; for (int i = 0; i < args.length; i++) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java index 2914e47c..c687dff3 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java @@ -5,8 +5,19 @@ import java.security.NoSuchAlgorithmException; import fr.xephi.authme.security.pbkdf2.PBKDF2Engine; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; +/** + */ public class CryptPBKDF2 implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -17,6 +28,15 @@ public class CryptPBKDF2 implements EncryptionMethod { return result + String.valueOf(engine.deriveKey(password, 64)); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java index 574337ed..6d160c53 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java @@ -7,8 +7,19 @@ import javax.xml.bind.DatatypeConverter; import fr.xephi.authme.security.pbkdf2.PBKDF2Engine; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; +/** + */ public class CryptPBKDF2Django implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -19,6 +30,15 @@ public class CryptPBKDF2Django implements EncryptionMethod { return result + String.valueOf(DatatypeConverter.printBase64Binary(engine.deriveKey(password, 32))); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java index 7ab2afb5..7baf131e 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java @@ -4,20 +4,46 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +/** + */ public class DOUBLEMD5 implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return getMD5(getMD5(password)); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { return hash.equals(getHash(password, "", "")); } + /** + * Method getMD5. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java index 40b9b84f..d3084562 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java @@ -13,6 +13,8 @@ import java.security.NoSuchAlgorithmException; *

* The comparePassword is called when we need to match password (/login usually) *

+ * @author Gabriele + * @version $Revision: 1.0 $ */ public interface EncryptionMethod { @@ -21,9 +23,10 @@ public interface EncryptionMethod { * @param salt * (can be an other data like playerName;salt , playerName, * etc... for customs methods) - * @return Hashing password - * @throws NoSuchAlgorithmException - */ + + + * @param name String + * @return Hashing password * @throws NoSuchAlgorithmException */ String getHash(String password, String salt, String name) throws NoSuchAlgorithmException; @@ -31,9 +34,9 @@ public interface EncryptionMethod { * @param hash * @param password * @param playerName - * @return true if password match, false else - * @throws NoSuchAlgorithmException - */ + + + * @return true if password match, false else * @throws NoSuchAlgorithmException */ boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException; diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java index 9484c021..6d2029b7 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java @@ -6,14 +6,34 @@ import java.security.NoSuchAlgorithmException; import fr.xephi.authme.AuthMe; +/** + */ public class IPB3 implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return getMD5(getMD5(salt) + getMD5(password)); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -21,6 +41,12 @@ public class IPB3 implements EncryptionMethod { return hash.equals(getHash(password, salt, playerName)); } + /** + * Method getMD5. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java index e99a01ff..4720213e 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java +++ b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java @@ -4,14 +4,34 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +/** + */ public class JOOMLA implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return getMD5(password + salt) + ":" + salt; } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -19,6 +39,12 @@ public class JOOMLA implements EncryptionMethod { return hash.equals(getMD5(password + salt) + ":" + salt); } + /** + * Method getMD5. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5.java b/src/main/java/fr/xephi/authme/security/crypts/MD5.java index c52c8b50..543e8a71 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5.java @@ -4,20 +4,46 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +/** + */ public class MD5 implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return getMD5(password); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { return hash.equals(getHash(password, "", "")); } + /** + * Method getMD5. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java index 616265be..4c5df204 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java @@ -4,14 +4,34 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +/** + */ public class MD5VB implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return "$MD5vb$" + salt + "$" + getMD5(getMD5(password) + salt); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -19,6 +39,12 @@ public class MD5VB implements EncryptionMethod { return hash.equals(getHash(password, line[2], "")); } + /** + * Method getMD5. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java index bf4b7f55..0988683d 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java @@ -6,14 +6,34 @@ import java.security.NoSuchAlgorithmException; import fr.xephi.authme.AuthMe; +/** + */ public class MYBB implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return getMD5(getMD5(salt) + getMD5(password)); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -21,6 +41,12 @@ public class MYBB implements EncryptionMethod { return hash.equals(getHash(password, salt, playerName)); } + /** + * Method getMD5. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java index 6defebf7..c2860caf 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java @@ -11,11 +11,18 @@ import java.security.NoSuchAlgorithmException; /** * @author stefano + * @version $Revision: 1.0 $ */ public class PHPBB implements EncryptionMethod { private String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + /** + * Method phpbb_hash. + * @param password String + * @param salt String + * @return String + */ public String phpbb_hash(String password, String salt) { String random_state = salt; StringBuilder random = new StringBuilder(); @@ -31,10 +38,23 @@ public class PHPBB implements EncryptionMethod { return md5(password); } + /** + * Method _hash_gensalt_private. + * @param input String + * @param itoa64 String + * @return String + */ private String _hash_gensalt_private(String input, String itoa64) { return _hash_gensalt_private(input, itoa64, 6); } + /** + * Method _hash_gensalt_private. + * @param input String + * @param itoa64 String + * @param iteration_count_log2 int + * @return String + */ private String _hash_gensalt_private(String input, String itoa64, int iteration_count_log2) { if (iteration_count_log2 < 4 || iteration_count_log2 > 31) { @@ -48,6 +68,9 @@ public class PHPBB implements EncryptionMethod { /** * Encode hash + * @param input String + * @param count int + * @return String */ private String _hash_encode64(String input, int count) { StringBuilder output = new StringBuilder(); @@ -70,6 +93,12 @@ public class PHPBB implements EncryptionMethod { return output.toString(); } + /** + * Method _hash_crypt_private. + * @param password String + * @param setting String + * @return String + */ String _hash_crypt_private(String password, String setting) { String output = "*"; if (!setting.substring(0, 3).equals("$H$")) @@ -91,12 +120,23 @@ public class PHPBB implements EncryptionMethod { return output; } + /** + * Method phpbb_check_hash. + * @param password String + * @param hash String + * @return boolean + */ public boolean phpbb_check_hash(String password, String hash) { if (hash.length() == 34) return _hash_crypt_private(password, hash).equals(hash); else return md5(password).equals(hash); } + /** + * Method md5. + * @param data String + * @return String + */ public static String md5(String data) { try { byte[] bytes = data.getBytes("ISO-8859-1"); @@ -108,6 +148,11 @@ public class PHPBB implements EncryptionMethod { } } + /** + * Method hexToInt. + * @param ch char + * @return int + */ static int hexToInt(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; @@ -117,6 +162,11 @@ public class PHPBB implements EncryptionMethod { throw new IllegalArgumentException("Not a hex character: " + ch); } + /** + * Method bytes2hex. + * @param bytes byte[] + * @return String + */ private static String bytes2hex(byte[] bytes) { StringBuilder r = new StringBuilder(32); for (byte b : bytes) { @@ -128,6 +178,11 @@ public class PHPBB implements EncryptionMethod { return r.toString(); } + /** + * Method pack. + * @param hex String + * @return String + */ static String pack(String hex) { StringBuilder buf = new StringBuilder(); for (int i = 0; i < hex.length(); i += 2) { @@ -139,12 +194,30 @@ public class PHPBB implements EncryptionMethod { return buf.toString(); } + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return phpbb_hash(password, salt); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java index 25d9897b..4e9099b2 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java @@ -11,8 +11,19 @@ import javax.crypto.spec.SecretKeySpec; import fr.xephi.authme.AuthMe; +/** + */ public class PHPFUSION implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -40,6 +51,15 @@ public class PHPFUSION implements EncryptionMethod { return digest; } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -47,6 +67,12 @@ public class PHPFUSION implements EncryptionMethod { return hash.equals(getHash(password, salt, "")); } + /** + * Method getSHA1. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getSHA1(String message) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java index 59434b6d..e67fe9c6 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java @@ -2,14 +2,34 @@ package fr.xephi.authme.security.crypts; import java.security.NoSuchAlgorithmException; +/** + */ public class PLAINTEXT implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return password; } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java index a0376426..8388de74 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java @@ -3,8 +3,19 @@ package fr.xephi.authme.security.crypts; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +/** + */ public class ROYALAUTH implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -13,6 +24,13 @@ public class ROYALAUTH implements EncryptionMethod { return password; } + /** + * Method hash. + * @param password String + * @param salt String + * @return String + * @throws NoSuchAlgorithmException + */ public String hash(String password, String salt) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-512"); @@ -24,6 +42,15 @@ public class ROYALAUTH implements EncryptionMethod { return sb.toString(); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java index eac3466c..0daecc04 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java @@ -6,14 +6,34 @@ import java.security.NoSuchAlgorithmException; import fr.xephi.authme.AuthMe; +/** + */ public class SALTED2MD5 implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return getMD5(getMD5(password) + salt); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -21,6 +41,12 @@ public class SALTED2MD5 implements EncryptionMethod { return hash.equals(getMD5(getMD5(password) + salt)); } + /** + * Method getMD5. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java index 0050dd73..61eb9cd5 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java @@ -6,14 +6,34 @@ import java.security.NoSuchAlgorithmException; import fr.xephi.authme.AuthMe; +/** + */ public class SALTEDSHA512 implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return getSHA512(password + salt); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -21,6 +41,12 @@ public class SALTEDSHA512 implements EncryptionMethod { return hash.equals(getHash(password, salt, "")); } + /** + * Method getSHA512. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getSHA512(String message) throws NoSuchAlgorithmException { MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java index ff6ebb0b..2e7968cc 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java @@ -4,20 +4,46 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +/** + */ public class SHA1 implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return getSHA1(password); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { return hash.equals(getHash(password, "", "")); } + /** + * Method getSHA1. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getSHA1(String message) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java index 34967397..d969427b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java @@ -4,14 +4,34 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +/** + */ public class SHA256 implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return "$SHA$" + salt + "$" + getSHA256(getSHA256(password) + salt); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -19,6 +39,12 @@ public class SHA256 implements EncryptionMethod { return hash.equals(getHash(password, line[2], "")); } + /** + * Method getSHA256. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getSHA256(String message) throws NoSuchAlgorithmException { MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java index 9924fa7f..29c80f17 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java @@ -4,20 +4,46 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +/** + */ public class SHA512 implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return getSHA512(password); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { return hash.equals(getHash(password, "", "")); } + /** + * Method getSHA512. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getSHA512(String message) throws NoSuchAlgorithmException { MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/SMF.java index 13b95324..2099a01d 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SMF.java @@ -4,20 +4,46 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +/** + */ public class SMF implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return getSHA1(name.toLowerCase() + password); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { return hash.equals(getHash(password, null, playerName)); } + /** + * Method getSHA1. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getSHA1(String message) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java index bb70e1bc..462b1cce 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java @@ -6,14 +6,34 @@ import java.security.NoSuchAlgorithmException; import fr.xephi.authme.AuthMe; +/** + */ public class WBB3 implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return getSHA1(salt.concat(getSHA1(salt.concat(getSHA1(password))))); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -21,6 +41,12 @@ public class WBB3 implements EncryptionMethod { return hash.equals(getHash(password, salt, "")); } + /** + * Method getSHA1. + * @param message String + * @return String + * @throws NoSuchAlgorithmException + */ private static String getSHA1(String message) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java index 76a0651f..3072f129 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java @@ -2,14 +2,34 @@ package fr.xephi.authme.security.crypts; import java.security.NoSuchAlgorithmException; +/** + */ public class WBB4 implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return BCRYPT.getDoubleHash(password, salt); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java index 7dc84963..dac8d8ab 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java @@ -64,6 +64,8 @@ package fr.xephi.authme.security.crypts; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +/** + */ public class WHIRLPOOL implements EncryptionMethod { /** @@ -319,6 +321,7 @@ public class WHIRLPOOL implements EncryptionMethod { * Get the hash value from the hashing state. * * This method uses the invariant: bufferBits < 512 + * @param digest byte[] */ public void NESSIEfinalize(byte[] digest) { // append a '1'-bit: @@ -373,6 +376,11 @@ public class WHIRLPOOL implements EncryptionMethod { } } + /** + * Method display. + * @param array byte[] + * @return String + */ protected static String display(byte[] array) { char[] val = new char[2 * array.length]; String hex = "0123456789ABCDEF"; @@ -384,6 +392,15 @@ public class WHIRLPOOL implements EncryptionMethod { return String.valueOf(val); } + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -394,6 +411,15 @@ public class WHIRLPOOL implements EncryptionMethod { return display(digest); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java index c0ba2ffe..110abb57 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java @@ -6,11 +6,19 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; +/** + */ public class WORDPRESS implements EncryptionMethod { private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private SecureRandom randomGen = new SecureRandom(); + /** + * Method encode64. + * @param src byte[] + * @param count int + * @return String + */ private String encode64(byte[] src, int count) { int i, value; StringBuilder output = new StringBuilder(); @@ -45,6 +53,12 @@ public class WORDPRESS implements EncryptionMethod { return output.toString(); } + /** + * Method crypt. + * @param password String + * @param setting String + * @return String + */ private String crypt(String password, String setting) { String output = "*0"; if (((setting.length() < 2) ? setting : setting.substring(0, 2)).equalsIgnoreCase(output)) { @@ -83,6 +97,11 @@ public class WORDPRESS implements EncryptionMethod { return output; } + /** + * Method gensaltPrivate. + * @param input byte[] + * @return String + */ private String gensaltPrivate(byte[] input) { String output = "$P$"; int iterationCountLog2 = 8; @@ -91,6 +110,11 @@ public class WORDPRESS implements EncryptionMethod { return output; } + /** + * Method stringToUtf8. + * @param string String + * @return byte[] + */ private byte[] stringToUtf8(String string) { try { return string.getBytes("UTF-8"); @@ -99,6 +123,15 @@ public class WORDPRESS implements EncryptionMethod { } } + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -107,6 +140,15 @@ public class WORDPRESS implements EncryptionMethod { return crypt(password, gensaltPrivate(stringToUtf8(new String(random)))); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java index 8930c162..650c1644 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java @@ -2,8 +2,19 @@ package fr.xephi.authme.security.crypts; import java.security.NoSuchAlgorithmException; +/** + */ public class XAUTH implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -12,6 +23,15 @@ public class XAUTH implements EncryptionMethod { return hash.substring(0, saltPos) + salt + hash.substring(saltPos); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -20,6 +40,11 @@ public class XAUTH implements EncryptionMethod { return hash.equals(getHash(password, salt, "")); } + /** + * Method getWhirlpool. + * @param message String + * @return String + */ public static String getWhirlpool(String message) { WHIRLPOOL w = new WHIRLPOOL(); byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES]; diff --git a/src/main/java/fr/xephi/authme/security/crypts/XF.java b/src/main/java/fr/xephi/authme/security/crypts/XF.java index b6ad9bde..dcd88686 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XF.java @@ -9,14 +9,34 @@ import java.util.regex.Pattern; import fr.xephi.authme.AuthMe; +/** + */ public class XF implements EncryptionMethod { + /** + * Method getHash. + * @param password String + * @param salt String + * @param name String + * @return String + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { return getSHA256(getSHA256(password) + regmatch("\"salt\";.:..:\"(.*)\";.:.:\"hashFunc\"", salt)); } + /** + * Method comparePassword. + * @param hash String + * @param password String + * @param playerName String + * @return boolean + * @throws NoSuchAlgorithmException + * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -24,6 +44,12 @@ public class XF implements EncryptionMethod { return hash.equals(regmatch("\"hash\";.:..:\"(.*)\";.:.:\"salt\"", salt)); } + /** + * Method getSHA256. + * @param password String + * @return String + * @throws NoSuchAlgorithmException + */ public String getSHA256(String password) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(password.getBytes()); @@ -43,6 +69,12 @@ public class XF implements EncryptionMethod { return hexString.toString(); } + /** + * Method regmatch. + * @param pattern String + * @param line String + * @return String + */ public String regmatch(String pattern, String line) { List allMatches = new ArrayList<>(); Matcher m = Pattern.compile(pattern).matcher(line); diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/BinTools.java b/src/main/java/fr/xephi/authme/security/pbkdf2/BinTools.java index 36481ee8..b8c5a198 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/BinTools.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/BinTools.java @@ -39,9 +39,9 @@ public class BinTools { * * @param b * Input bytes. May be null. + * @return Hexadecimal representation of b. Uppercase A-F, two characters - * per byte. Empty string on null input. - */ + * per byte. Empty string on null input. */ public static String bin2hex(final byte[] b) { if (b == null) { return ""; @@ -61,10 +61,10 @@ public class BinTools { * @param s * String containing hexadecimal digits. May be null * . On odd length leading zero will be assumed. - * @return Array on bytes, non-null. - * @throws IllegalArgumentException - * when string contains non-hex character - */ + + + * @return Array on bytes, non-null. * @throws IllegalArgumentException + * when string contains non-hex character */ public static byte[] hex2bin(final String s) { String m = s; if (s == null) { @@ -88,10 +88,10 @@ public class BinTools { * * @param c * 0-9, a-f, A-F allowd. - * @return 0-15 - * @throws IllegalArgumentException - * on non-hex character - */ + + + * @return 0-15 * @throws IllegalArgumentException + * on non-hex character */ public static int hex2bin(char c) { if (c >= '0' && c <= '9') { return (c - '0'); @@ -105,6 +105,10 @@ public class BinTools { throw new IllegalArgumentException("Input string may only contain hex digits, but found '" + c + "'"); } + /** + * Method main. + * @param args String[] + */ public static void main(String[] args) { byte b[] = new byte[256]; byte bb = 0; diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/MacBasedPRF.java b/src/main/java/fr/xephi/authme/security/pbkdf2/MacBasedPRF.java index b7d80382..e244b748 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/MacBasedPRF.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/MacBasedPRF.java @@ -65,6 +65,11 @@ public class MacBasedPRF implements PRF { } } + /** + * Constructor for MacBasedPRF. + * @param macAlgorithm String + * @param provider String + */ public MacBasedPRF(String macAlgorithm, String provider) { this.macAlgorithm = macAlgorithm; try { @@ -75,15 +80,31 @@ public class MacBasedPRF implements PRF { } } + /** + * Method doFinal. + * @param M byte[] + * @return byte[] + * @see fr.xephi.authme.security.pbkdf2.PRF#doFinal(byte[]) + */ public byte[] doFinal(byte[] M) { byte[] r = mac.doFinal(M); return r; } + /** + * Method getHLen. + * @return int + * @see fr.xephi.authme.security.pbkdf2.PRF#getHLen() + */ public int getHLen() { return hLen; } + /** + * Method init. + * @param P byte[] + * @see fr.xephi.authme.security.pbkdf2.PRF#init(byte[]) + */ public void init(byte[] P) { try { mac.init(new SecretKeySpec(P, macAlgorithm)); diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2.java index 6b1c27d6..7a623e06 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2.java @@ -39,8 +39,8 @@ public interface PBKDF2 { * * @param inputPassword * Candidate password to compute the derived key for. - * @return internal byte array - */ + + * @return internal byte array */ public abstract byte[] deriveKey(String inputPassword); /** @@ -50,8 +50,8 @@ public interface PBKDF2 { * Candidate password to compute the derived key for. * @param dkLen * Specify desired key length - * @return internal byte array - */ + + * @return internal byte array */ public abstract byte[] deriveKey(String inputPassword, int dkLen); /** @@ -61,16 +61,16 @@ public interface PBKDF2 { * * @param inputPassword * Candidate password to compute the derived key for. + * @return true password match; false incorrect - * password - */ + * password */ public abstract boolean verifyKey(String inputPassword); /** * Allow reading of configured parameters. * - * @return Currently set parameters. - */ + + * @return Currently set parameters. */ public abstract PBKDF2Parameters getParameters(); /** @@ -83,8 +83,8 @@ public interface PBKDF2 { /** * Get currently set Pseudo Random Function. * - * @return Currently set Pseudo Random Function - */ + + * @return Currently set Pseudo Random Function */ public abstract PRF getPseudoRandomFunction(); /** diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java index e43a754e..ae077dbf 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java @@ -112,10 +112,23 @@ public class PBKDF2Engine implements PBKDF2 { this.prf = prf; } + /** + * Method deriveKey. + * @param inputPassword String + * @return byte[] + * @see fr.xephi.authme.security.pbkdf2.PBKDF2#deriveKey(String) + */ public byte[] deriveKey(String inputPassword) { return deriveKey(inputPassword, 0); } + /** + * Method deriveKey. + * @param inputPassword String + * @param dkLen int + * @return byte[] + * @see fr.xephi.authme.security.pbkdf2.PBKDF2#deriveKey(String, int) + */ public byte[] deriveKey(String inputPassword, int dkLen) { byte[] r = null; byte P[] = null; @@ -140,6 +153,12 @@ public class PBKDF2Engine implements PBKDF2 { return r; } + /** + * Method verifyKey. + * @param inputPassword String + * @return boolean + * @see fr.xephi.authme.security.pbkdf2.PBKDF2#verifyKey(String) + */ public boolean verifyKey(String inputPassword) { byte[] referenceKey = getParameters().getDerivedKey(); if (referenceKey == null || referenceKey.length == 0) { @@ -172,6 +191,11 @@ public class PBKDF2Engine implements PBKDF2 { prf.init(P); } + /** + * Method getPseudoRandomFunction. + * @return PRF + * @see fr.xephi.authme.security.pbkdf2.PBKDF2#getPseudoRandomFunction() + */ public PRF getPseudoRandomFunction() { return prf; } @@ -179,7 +203,7 @@ public class PBKDF2Engine implements PBKDF2 { /** * Core Password Based Key Derivation Function 2. * - * @see RFC 2898 5.2 + * @param prf * Pseudo Random Function (i.e. HmacSHA1) * @param S @@ -188,8 +212,8 @@ public class PBKDF2Engine implements PBKDF2 { * Iteration count (see RFC 2898 4.2) * @param dkLen * desired length of derived key. - * @return internal byte array - */ + + * @return internal byte array * @see RFC 2898 5.2 */ protected byte[] PBKDF2(PRF prf, byte[] S, int c, int dkLen) { if (S == null) { S = new byte[0]; @@ -215,12 +239,12 @@ public class PBKDF2Engine implements PBKDF2 { /** * Integer division with ceiling function. * - * @see RFC 2898 5.2 Step - * 2. + * @param a * @param b - * @return ceil(a/b) - */ + + * @return ceil(a/b) * @see RFC 2898 5.2 Step + * 2. */ protected int ceil(int a, int b) { int m = 0; if (a % b > 0) { @@ -232,8 +256,7 @@ public class PBKDF2Engine implements PBKDF2 { /** * Function F. * - * @see RFC 2898 5.2 Step - * 3. + * @param dest * Destination byte buffer * @param offset @@ -245,7 +268,8 @@ public class PBKDF2Engine implements PBKDF2 { * @param c * Iteration count * @param blockIndex - */ + * @see RFC 2898 5.2 Step + * 3. */ protected void _F(byte[] dest, int offset, PRF prf, byte[] S, int c, int blockIndex) { int hLen = prf.getHLen(); @@ -279,12 +303,12 @@ public class PBKDF2Engine implements PBKDF2 { /** * Four-octet encoding of the integer i, most significant octet first. * - * @see RFC 2898 5.2 Step - * 3. + * @param dest * @param offset * @param i - */ + * @see RFC 2898 5.2 Step + * 3. */ protected void INT(byte[] dest, int offset, int i) { dest[offset + 0] = (byte) (i / (256 * 256 * 256)); dest[offset + 1] = (byte) (i / (256 * 256)); @@ -292,14 +316,29 @@ public class PBKDF2Engine implements PBKDF2 { dest[offset + 3] = (byte) (i); } + /** + * Method getParameters. + * @return PBKDF2Parameters + * @see fr.xephi.authme.security.pbkdf2.PBKDF2#getParameters() + */ public PBKDF2Parameters getParameters() { return parameters; } + /** + * Method setParameters. + * @param parameters PBKDF2Parameters + * @see fr.xephi.authme.security.pbkdf2.PBKDF2#setParameters(PBKDF2Parameters) + */ public void setParameters(PBKDF2Parameters parameters) { this.parameters = parameters; } + /** + * Method setPseudoRandomFunction. + * @param prf PRF + * @see fr.xephi.authme.security.pbkdf2.PBKDF2#setPseudoRandomFunction(PRF) + */ public void setPseudoRandomFunction(PRF prf) { this.prf = prf; } @@ -316,9 +355,9 @@ public class PBKDF2Engine implements PBKDF2 { * * @param args * Supply the password as argument. - * @throws IOException - * @throws NoSuchAlgorithmException - */ + + + * @throws IOException * @throws NoSuchAlgorithmException */ public static void main(String[] args) throws IOException, NoSuchAlgorithmException { String password = "password"; diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Formatter.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Formatter.java index d4e63e89..68f3fa6c 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Formatter.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Formatter.java @@ -38,8 +38,8 @@ public interface PBKDF2Formatter { * * @param p * Parameters object to output. - * @return String representation - */ + + * @return String representation */ public abstract String toString(PBKDF2Parameters p); /** @@ -48,8 +48,9 @@ public interface PBKDF2Formatter { * * @param s * String representation of parameters to decode. + + * @param p PBKDF2Parameters * @return false syntax OK, true some syntax - * issue. - */ + * issue. */ public abstract boolean fromString(PBKDF2Parameters p, String s); } diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2HexFormatter.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2HexFormatter.java index c36b747c..8130c7e9 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2HexFormatter.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2HexFormatter.java @@ -33,6 +33,13 @@ package fr.xephi.authme.security.pbkdf2; */ public class PBKDF2HexFormatter implements PBKDF2Formatter { + /** + * Method fromString. + * @param p PBKDF2Parameters + * @param s String + * @return boolean + * @see fr.xephi.authme.security.pbkdf2.PBKDF2Formatter#fromString(PBKDF2Parameters, String) + */ public boolean fromString(PBKDF2Parameters p, String s) { if (p == null || s == null) { return true; @@ -53,6 +60,12 @@ public class PBKDF2HexFormatter implements PBKDF2Formatter { return false; } + /** + * Method toString. + * @param p PBKDF2Parameters + * @return String + * @see fr.xephi.authme.security.pbkdf2.PBKDF2Formatter#toString(PBKDF2Parameters) + */ public String toString(PBKDF2Parameters p) { String s = BinTools.bin2hex(p.getSalt()) + ":" + String.valueOf(p.getIterationCount()) + ":" + BinTools.bin2hex(p.getDerivedKey()); return s; diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Parameters.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Parameters.java index 84c0978c..5f11cf1b 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Parameters.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Parameters.java @@ -109,42 +109,82 @@ public class PBKDF2Parameters { this.derivedKey = derivedKey; } + /** + * Method getIterationCount. + * @return int + */ public int getIterationCount() { return iterationCount; } + /** + * Method setIterationCount. + * @param iterationCount int + */ public void setIterationCount(int iterationCount) { this.iterationCount = iterationCount; } + /** + * Method getSalt. + * @return byte[] + */ public byte[] getSalt() { return salt; } + /** + * Method setSalt. + * @param salt byte[] + */ public void setSalt(byte[] salt) { this.salt = salt; } + /** + * Method getDerivedKey. + * @return byte[] + */ public byte[] getDerivedKey() { return derivedKey; } + /** + * Method setDerivedKey. + * @param derivedKey byte[] + */ public void setDerivedKey(byte[] derivedKey) { this.derivedKey = derivedKey; } + /** + * Method getHashAlgorithm. + * @return String + */ public String getHashAlgorithm() { return hashAlgorithm; } + /** + * Method setHashAlgorithm. + * @param hashAlgorithm String + */ public void setHashAlgorithm(String hashAlgorithm) { this.hashAlgorithm = hashAlgorithm; } + /** + * Method getHashCharset. + * @return String + */ public String getHashCharset() { return hashCharset; } + /** + * Method setHashCharset. + * @param hashCharset String + */ public void setHashCharset(String hashCharset) { this.hashCharset = hashCharset; } diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PRF.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PRF.java index 733c2211..c7dbf547 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PRF.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PRF.java @@ -48,14 +48,14 @@ public interface PRF { * @param M * Input data/message etc. Together with any data supplied during * initilization. - * @return Random bytes of hLen length. - */ + + * @return Random bytes of hLen length. */ public byte[] doFinal(byte[] M); /** * Query block size of underlying algorithm/mechanism. * - * @return block size - */ + + * @return block size */ public int getHLen(); } diff --git a/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java b/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java index 5ca9422f..0381e7e7 100644 --- a/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java +++ b/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java @@ -13,10 +13,16 @@ import org.bukkit.configuration.file.YamlConfiguration; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; +/** + */ public class CustomConfiguration extends YamlConfiguration { private File configFile; + /** + * Constructor for CustomConfiguration. + * @param file File + */ public CustomConfiguration(File file) { this.configFile = file; load(); @@ -35,6 +41,10 @@ public class CustomConfiguration extends YamlConfiguration { } } + /** + * Method reLoad. + * @return boolean + */ public boolean reLoad() { boolean out = true; if (!configFile.exists()) { @@ -53,10 +63,19 @@ public class CustomConfiguration extends YamlConfiguration { } } + /** + * Method getConfigFile. + * @return File + */ public File getConfigFile() { return configFile; } + /** + * Method loadResource. + * @param file File + * @return boolean + */ public boolean loadResource(File file) { if (!file.exists()) { try { diff --git a/src/main/java/fr/xephi/authme/settings/Messages.java b/src/main/java/fr/xephi/authme/settings/Messages.java index 3514c704..d76633cb 100644 --- a/src/main/java/fr/xephi/authme/settings/Messages.java +++ b/src/main/java/fr/xephi/authme/settings/Messages.java @@ -6,11 +6,18 @@ import org.bukkit.command.CommandSender; import fr.xephi.authme.ConsoleLogger; +/** + */ public class Messages extends CustomConfiguration { private static Messages singleton = null; private String lang = "en"; + /** + * Constructor for Messages. + * @param file File + * @param lang String + */ public Messages(File file, String lang) { super(file); load(); @@ -18,6 +25,11 @@ public class Messages extends CustomConfiguration { this.lang = lang; } + /** + * Method send. + * @param sender CommandSender + * @param msg String + */ public void send(CommandSender sender, String msg) { if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.lang)) singleton.reloadMessages(); @@ -31,6 +43,11 @@ public class Messages extends CustomConfiguration { } } + /** + * Method send. + * @param msg String + * @return String[] + */ public String[] send(String msg) { if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.lang)) { singleton.reloadMessages(); @@ -54,6 +71,10 @@ public class Messages extends CustomConfiguration { return loc; } + /** + * Method getInstance. + * @return Messages + */ public static Messages getInstance() { if (singleton == null) { singleton = new Messages(Settings.messageFile, Settings.messagesLanguage); diff --git a/src/main/java/fr/xephi/authme/settings/OtherAccounts.java b/src/main/java/fr/xephi/authme/settings/OtherAccounts.java index 0154a337..29ae1e9c 100644 --- a/src/main/java/fr/xephi/authme/settings/OtherAccounts.java +++ b/src/main/java/fr/xephi/authme/settings/OtherAccounts.java @@ -11,6 +11,7 @@ import org.bukkit.entity.Player; /** * * @author Xephi59 + * @version $Revision: 1.0 $ */ public class OtherAccounts extends CustomConfiguration { @@ -23,11 +24,19 @@ public class OtherAccounts extends CustomConfiguration { save(); } + /** + * Method clear. + * @param uuid UUID + */ public void clear(UUID uuid) { set(uuid.toString(), new ArrayList()); save(); } + /** + * Method getInstance. + * @return OtherAccounts + */ public static OtherAccounts getInstance() { if (others == null) { others = new OtherAccounts(); @@ -35,6 +44,10 @@ public class OtherAccounts extends CustomConfiguration { return others; } + /** + * Method addPlayer. + * @param uuid UUID + */ public void addPlayer(UUID uuid) { try { Player player = Bukkit.getPlayer(uuid); @@ -49,6 +62,10 @@ public class OtherAccounts extends CustomConfiguration { } } + /** + * Method removePlayer. + * @param uuid UUID + */ public void removePlayer(UUID uuid) { try { Player player = Bukkit.getPlayer(uuid); @@ -63,6 +80,11 @@ public class OtherAccounts extends CustomConfiguration { } } + /** + * Method getAllPlayersByUUID. + * @param uuid UUID + * @return List + */ public List getAllPlayersByUUID(UUID uuid) { return this.getStringList(uuid.toString()); } diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index d1c02610..0e384df3 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -18,6 +18,8 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource.DataSourceType; import fr.xephi.authme.security.HashAlgorithm; +/** + */ public final class Settings extends YamlConfiguration { private static AuthMe plugin; @@ -104,12 +106,20 @@ public final class Settings extends YamlConfiguration { protected static YamlConfiguration configFile; + /** + * Constructor for Settings. + * @param pl AuthMe + */ public Settings(AuthMe pl) { instance = this; plugin = pl; configFile = (YamlConfiguration) plugin.getConfig(); } + /** + * Method reload. + * @throws Exception + */ public static void reload() throws Exception { plugin.getLogger().info("Loading Configuration File..."); boolean exist = SETTINGS_FILE.exists(); @@ -502,11 +512,20 @@ public final class Settings extends YamlConfiguration { } } + /** + * Method setValue. + * @param key String + * @param value Object + */ public static void setValue(String key, Object value) { instance.set(key, value); save(); } + /** + * Method getPasswordHash. + * @return HashAlgorithm + */ private static HashAlgorithm getPasswordHash() { String key = "settings.security.passwordHash"; try { @@ -517,6 +536,10 @@ public final class Settings extends YamlConfiguration { } } + /** + * Method getDataSource. + * @return DataSourceType + */ private static DataSourceType getDataSource() { String key = "DataSource.backend"; try { @@ -531,6 +554,9 @@ public final class Settings extends YamlConfiguration { * Config option for setting and check restricted user by username;ip , * return false if ip and name doesnt amtch with player that join the * server, so player has a restricted access + * @param name String + * @param ip String + * @return boolean */ public static boolean getRestrictedIp(String name, String ip) { @@ -554,8 +580,8 @@ public final class Settings extends YamlConfiguration { /** * Saves the configuration to disk * - * @return True if saved successfully - */ + + * @return True if saved successfully */ public static boolean save() { try { instance.save(SETTINGS_FILE); @@ -570,8 +596,8 @@ public final class Settings extends YamlConfiguration { *

* If defaults and configuration are empty, saves blank file. * - * @return True if saved successfully - */ + + * @return True if saved successfully */ public final boolean saveDefaults() { options().copyDefaults(true); options().copyHeader(true); @@ -581,6 +607,11 @@ public final class Settings extends YamlConfiguration { return success; } + /** + * Method checkLang. + * @param lang String + * @return String + */ public static String checkLang(String lang) { if (new File(PLUGIN_FOLDER, "messages" + File.separator + "messages_" + lang + ".yml").exists()) { ConsoleLogger.info("Set Language to: " + lang); @@ -594,6 +625,10 @@ public final class Settings extends YamlConfiguration { return "en"; } + /** + * Method switchAntiBotMod. + * @param mode boolean + */ public static void switchAntiBotMod(boolean mode) { if (mode) { isKickNonRegisteredEnabled = true; @@ -635,6 +670,11 @@ public final class Settings extends YamlConfiguration { } } + /** + * Method isEmailCorrect. + * @param email String + * @return boolean + */ public static boolean isEmailCorrect(String email) { if (!email.contains("@")) return false; diff --git a/src/main/java/fr/xephi/authme/settings/Spawn.java b/src/main/java/fr/xephi/authme/settings/Spawn.java index 81b0891e..fe53bc2d 100644 --- a/src/main/java/fr/xephi/authme/settings/Spawn.java +++ b/src/main/java/fr/xephi/authme/settings/Spawn.java @@ -8,6 +8,7 @@ import org.bukkit.Location; /** * * @author Xephi59 + * @version $Revision: 1.0 $ */ public class Spawn extends CustomConfiguration { @@ -42,6 +43,10 @@ public class Spawn extends CustomConfiguration { } } + /** + * Method getInstance. + * @return Spawn + */ public static Spawn getInstance() { if (spawn == null) { spawn = new Spawn(); @@ -49,6 +54,11 @@ public class Spawn extends CustomConfiguration { return spawn; } + /** + * Method setSpawn. + * @param location Location + * @return boolean + */ public boolean setSpawn(Location location) { try { set("spawn.world", location.getWorld().getName()); @@ -64,6 +74,11 @@ public class Spawn extends CustomConfiguration { } } + /** + * Method setFirstSpawn. + * @param location Location + * @return boolean + */ public boolean setFirstSpawn(Location location) { try { set("firstspawn.world", location.getWorld().getName()); @@ -79,11 +94,19 @@ public class Spawn extends CustomConfiguration { } } + /** + * Method getLocation. + * @return Location + */ @Deprecated public Location getLocation() { return getSpawn(); } + /** + * Method getSpawn. + * @return Location + */ public Location getSpawn() { try { if (this.getString("spawn.world").isEmpty() || this.getString("spawn.world").equals("")) @@ -95,6 +118,10 @@ public class Spawn extends CustomConfiguration { } } + /** + * Method getFirstSpawn. + * @return Location + */ public Location getFirstSpawn() { try { if (this.getString("firstspawn.world").isEmpty() || this.getString("firstspawn.world").equals("")) diff --git a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java index 2f795273..77a1155e 100644 --- a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java +++ b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java @@ -12,6 +12,8 @@ import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; +/** + */ public class ChangePasswordTask implements Runnable { private final AuthMe plugin; @@ -19,6 +21,13 @@ public class ChangePasswordTask implements Runnable { private final String oldPassword; private final String newPassword; + /** + * Constructor for ChangePasswordTask. + * @param plugin AuthMe + * @param player Player + * @param oldPassword String + * @param newPassword String + */ public ChangePasswordTask(AuthMe plugin, Player player, String oldPassword, String newPassword) { this.plugin = plugin; this.player = player; @@ -26,6 +35,10 @@ public class ChangePasswordTask implements Runnable { this.newPassword = newPassword; } + /** + * Method run. + * @see java.lang.Runnable#run() + */ @Override public void run() { Messages m = Messages.getInstance(); diff --git a/src/main/java/fr/xephi/authme/task/MessageTask.java b/src/main/java/fr/xephi/authme/task/MessageTask.java index 7c92b1c9..af947440 100644 --- a/src/main/java/fr/xephi/authme/task/MessageTask.java +++ b/src/main/java/fr/xephi/authme/task/MessageTask.java @@ -8,6 +8,8 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.util.Utils; +/** + */ public class MessageTask implements Runnable { private AuthMe plugin; @@ -15,6 +17,13 @@ public class MessageTask implements Runnable { private String[] msg; private int interval; + /** + * Constructor for MessageTask. + * @param plugin AuthMe + * @param name String + * @param strings String[] + * @param interval int + */ public MessageTask(AuthMe plugin, String name, String[] strings, int interval) { this.plugin = plugin; @@ -23,6 +32,10 @@ public class MessageTask implements Runnable { this.interval = interval; } + /** + * Method run. + * @see java.lang.Runnable#run() + */ @Override public void run() { if (PlayerCache.getInstance().isAuthenticated(name)) diff --git a/src/main/java/fr/xephi/authme/task/TimeoutTask.java b/src/main/java/fr/xephi/authme/task/TimeoutTask.java index 85867d43..c055c0d4 100644 --- a/src/main/java/fr/xephi/authme/task/TimeoutTask.java +++ b/src/main/java/fr/xephi/authme/task/TimeoutTask.java @@ -7,6 +7,8 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.settings.Messages; +/** + */ public class TimeoutTask implements Runnable { private AuthMe plugin; @@ -14,16 +16,30 @@ public class TimeoutTask implements Runnable { private Messages m = Messages.getInstance(); private Player player; + /** + * Constructor for TimeoutTask. + * @param plugin AuthMe + * @param name String + * @param player Player + */ public TimeoutTask(AuthMe plugin, String name, Player player) { this.plugin = plugin; this.name = name; this.player = player; } + /** + * Method getName. + * @return String + */ public String getName() { return name; } + /** + * Method run. + * @see java.lang.Runnable#run() + */ @Override public void run() { if (PlayerCache.getInstance().isAuthenticated(name)) diff --git a/src/main/java/fr/xephi/authme/util/ListUtils.java b/src/main/java/fr/xephi/authme/util/ListUtils.java index 5adcfef0..05ede1ba 100644 --- a/src/main/java/fr/xephi/authme/util/ListUtils.java +++ b/src/main/java/fr/xephi/authme/util/ListUtils.java @@ -3,6 +3,8 @@ package fr.xephi.authme.util; import java.util.ArrayList; import java.util.List; +/** + */ public class ListUtils { /** @@ -11,8 +13,8 @@ public class ListUtils { * @param elements The elements to implode. * @param separator The separator to use. * - * @return The result string. - */ + + * @return The result string. */ public static String implode(List elements, String separator) { // Create a string builder StringBuilder sb = new StringBuilder(); @@ -42,8 +44,8 @@ public class ListUtils { * @param otherElements The second list of elements to implode. * @param separator The separator to use. * - * @return The result string. - */ + + * @return The result string. */ public static String implode(List elements, List otherElements, String separator) { // Combine the lists List combined = new ArrayList<>(); @@ -61,8 +63,8 @@ public class ListUtils { * @param otherElement The second element to implode. * @param separator The separator to use. * - * @return The result string. - */ + + * @return The result string. */ public static String implode(String element, String otherElement, String separator) { // Combine the lists List combined = new ArrayList<>(); diff --git a/src/main/java/fr/xephi/authme/util/Profiler.java b/src/main/java/fr/xephi/authme/util/Profiler.java index 04255f4a..92e3c51f 100644 --- a/src/main/java/fr/xephi/authme/util/Profiler.java +++ b/src/main/java/fr/xephi/authme/util/Profiler.java @@ -2,6 +2,8 @@ package fr.xephi.authme.util; import java.text.DecimalFormat; +/** + */ @SuppressWarnings("UnusedDeclaration") public class Profiler { @@ -31,9 +33,9 @@ public class Profiler { /** * Start the profiler. * + * @return True if the profiler was started, false otherwise possibly due to an error. - * True will also be returned if the profiler was started already. - */ + * True will also be returned if the profiler was started already. */ public boolean start() { // Make sure the timer isn't started already if(isActive()) @@ -47,8 +49,8 @@ public class Profiler { /** * This will start the profiler if it's not active, or will stop the profiler if it's currently active. * - * @return True if the profiler has been started, false if the profiler has been stopped. - */ + + * @return True if the profiler has been started, false if the profiler has been stopped. */ public boolean pause() { // Toggle the profiler state if(isStarted()) @@ -63,9 +65,9 @@ public class Profiler { /** * Stop the profiler if it's active. * + * @return True will be returned if the profiler was stopped while it was active. False will be returned if the - * profiler was stopped already. - */ + * profiler was stopped already. */ public boolean stop() { // Make sure the profiler is active if(!isActive()) @@ -80,8 +82,8 @@ public class Profiler { /** * Check whether the profiler has been started. The profiler doesn't need to be active right now. * - * @return True if the profiler was started, false otherwise. - */ + + * @return True if the profiler was started, false otherwise. */ public boolean isStarted() { return isActive() || this.time > 0; } @@ -89,8 +91,8 @@ public class Profiler { /** * Check whether the profiler is currently active. * - * @return True if the profiler is active, false otherwise. - */ + + * @return True if the profiler is active, false otherwise. */ public boolean isActive() { return this.start >= 0; } @@ -98,8 +100,8 @@ public class Profiler { /** * Get the passed time in milliseconds. * - * @return The passed time in milliseconds. - */ + + * @return The passed time in milliseconds. */ public long getTime() { // Check whether the profiler is currently active if(isActive()) @@ -110,8 +112,8 @@ public class Profiler { /** * Get the passed time in a formatted string. * - * @return The passed time in a formatted string. - */ + + * @return The passed time in a formatted string. */ public String getTimeFormatted() { // Get the passed time long time = getTime(); diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java index da6f91ba..202b32ce 100644 --- a/src/main/java/fr/xephi/authme/util/StringUtils.java +++ b/src/main/java/fr/xephi/authme/util/StringUtils.java @@ -4,6 +4,8 @@ import net.ricecode.similarity.LevenshteinDistanceStrategy; import net.ricecode.similarity.StringSimilarityService; import net.ricecode.similarity.StringSimilarityServiceImpl; +/** + */ public class StringUtils { /** @@ -12,8 +14,8 @@ public class StringUtils { * @param first First string. * @param second Second string. * - * @return The difference value. - */ + + * @return The difference value. */ public static double getDifference(String first, String second) { // Make sure the strings are valid. if(first == null || second == null) @@ -26,6 +28,12 @@ public class StringUtils { return Math.abs(service.score(first, second) - 1.0); } + /** + * Method containsAny. + * @param str String + * @param pieces String[] + * @return boolean + */ public static boolean containsAny(String str, String... pieces) { if (str == null) { return false; diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 15ab11f8..f18a4047 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -24,6 +24,8 @@ import java.util.Collection; import java.util.Collections; import java.util.zip.GZIPInputStream; +/** + */ public class Utils { public static AuthMe plugin; @@ -43,6 +45,10 @@ public class Utils { } // Check and Download GeoIP data if not exist + /** + * Method checkGeoIP. + * @return boolean + */ public static boolean checkGeoIP() { if (lookupService != null) { return true; @@ -89,6 +95,11 @@ public class Utils { return false; } + /** + * Method getCountryCode. + * @param ip String + * @return String + */ public static String getCountryCode(String ip) { if (checkGeoIP()) { return lookupService.getCountry(ip).getCode(); @@ -96,6 +107,11 @@ public class Utils { return "--"; } + /** + * Method getCountryName. + * @param ip String + * @return String + */ public static String getCountryName(String ip) { if (checkGeoIP()) { return lookupService.getCountry(ip).getName(); @@ -103,6 +119,11 @@ public class Utils { return "N/A"; } + /** + * Method setGroup. + * @param player Player + * @param group GroupType + */ public static void setGroup(Player player, GroupType group) { if (!Settings.isPermissionCheckEnabled) return; @@ -148,6 +169,12 @@ public class Utils { } } + /** + * Method addNormal. + * @param player Player + * @param group String + * @return boolean + */ public static boolean addNormal(Player player, String group) { if (!useGroupSystem()) { return false; @@ -167,6 +194,11 @@ public class Utils { } // TODO: Move to a Manager + /** + * Method checkAuth. + * @param player Player + * @return boolean + */ public static boolean checkAuth(Player player) { if (player == null || Utils.isUnrestricted(player)) { return true; @@ -186,15 +218,32 @@ public class Utils { return false; } + /** + * Method isUnrestricted. + * @param player Player + * @return boolean + */ public static boolean isUnrestricted(Player player) { return Settings.isAllowRestrictedIp && !Settings.getUnrestrictedName.isEmpty() && (Settings.getUnrestrictedName.contains(player.getName())); } + /** + * Method useGroupSystem. + * @return boolean + */ private static boolean useGroupSystem() { return Settings.isPermissionCheckEnabled && !Settings.getUnloggedinGroup.isEmpty(); } + /** + * Method packCoords. + * @param x double + * @param y double + * @param z double + * @param w String + * @param pl Player + */ public static void packCoords(double x, double y, double z, String w, final Player pl) { World theWorld; @@ -224,11 +273,17 @@ public class Utils { /* * Used for force player GameMode */ + /** + * Method forceGM. + * @param player Player + */ public static void forceGM(Player player) { if (!plugin.getPermissionsManager().hasPermission(player, "authme.bypassforcesurvival")) player.setGameMode(GameMode.SURVIVAL); } + /** + */ public enum GroupType { UNREGISTERED, REGISTERED, @@ -236,6 +291,10 @@ public class Utils { LOGGEDIN } + /** + * Method purgeDirectory. + * @param file File + */ public static void purgeDirectory(File file) { if (!file.isDirectory()) { return; @@ -254,6 +313,10 @@ public class Utils { } } + /** + * Method getOnlinePlayers. + * @return Collection + */ @SuppressWarnings("unchecked") public static Collection getOnlinePlayers() { if (getOnlinePlayersIsCollection) { @@ -273,12 +336,22 @@ public class Utils { return Collections.emptyList(); } + /** + * Method getPlayer. + * @param name String + * @return Player + */ @SuppressWarnings("deprecation") public static Player getPlayer(String name) { name = name.toLowerCase(); return plugin.getServer().getPlayer(name); } + /** + * Method isNPC. + * @param player Entity + * @return boolean + */ public static boolean isNPC(final Entity player) { try { if (player.hasMetadata("NPC")) { @@ -294,6 +367,10 @@ public class Utils { } } + /** + * Method teleportToSpawn. + * @param player Player + */ public static void teleportToSpawn(Player player) { if (Settings.isTeleportToSpawnEnabled && !Settings.noTeleport) { Location spawn = plugin.getSpawnLocation(player); diff --git a/src/test/java/fr/xephi/authme/Log4JFilterTest.java b/src/test/java/fr/xephi/authme/Log4JFilterTest.java index 5614eb51..6fb6f058 100644 --- a/src/test/java/fr/xephi/authme/Log4JFilterTest.java +++ b/src/test/java/fr/xephi/authme/Log4JFilterTest.java @@ -12,6 +12,8 @@ import org.mockito.Mockito; /** * Test for {@link Log4JFilter}. + * @author Gabriele + * @version $Revision: 1.0 $ */ public class Log4JFilterTest { @@ -219,8 +221,8 @@ public class Log4JFilterTest { * Mocks a {@link Message} object and makes it return the given formatted message. * * @param formattedMessage the formatted message the mock should return - * @return Message mock - */ + + * @return Message mock */ private static Message mockMessage(String formattedMessage) { Message message = Mockito.mock(Message.class); when(message.getFormattedMessage()).thenReturn(formattedMessage); From bd1f868c6db7742f9f12a3f8ab3eefac4fd0168d Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sat, 21 Nov 2015 01:31:18 +0100 Subject: [PATCH 036/199] standard javadoc --- src/main/java/fr/xephi/authme/AuthMe.java | 91 +++++----- .../java/fr/xephi/authme/ConsoleFilter.java | 6 +- .../java/fr/xephi/authme/DataManager.java | 12 +- .../java/fr/xephi/authme/ImageGenerator.java | 4 +- .../java/fr/xephi/authme/Log4JFilter.java | 36 ++-- .../java/fr/xephi/authme/PerformBackup.java | 24 +-- src/main/java/fr/xephi/authme/api/API.java | 8 +- src/main/java/fr/xephi/authme/api/NewAPI.java | 8 +- .../xephi/authme/cache/auth/PlayerAuth.java | 60 +++---- .../xephi/authme/cache/auth/PlayerCache.java | 20 +-- .../authme/cache/backup/DataFileCache.java | 12 +- .../xephi/authme/cache/backup/JsonCache.java | 20 +-- .../xephi/authme/cache/limbo/LimboCache.java | 12 +- .../xephi/authme/cache/limbo/LimboPlayer.java | 32 ++-- .../authme/command/CommandDescription.java | 4 +- .../converter/ConverterCommand.java | 8 +- .../authme/converter/CrazyLoginConverter.java | 4 +- .../xephi/authme/converter/FlatToSqlite.java | 14 +- .../authme/converter/RakamakConverter.java | 4 +- .../authme/converter/RoyalAuthYamlReader.java | 8 +- .../authme/converter/vAuthFileReader.java | 12 +- .../xephi/authme/converter/xAuthToFlat.java | 16 +- .../authme/datasource/CacheDataSource.java | 140 +++++++-------- .../xephi/authme/datasource/DataSource.java | 84 ++++----- .../authme/datasource/DatabaseCalls.java | 136 +++++++------- .../fr/xephi/authme/datasource/FlatFile.java | 136 +++++++------- .../fr/xephi/authme/datasource/MySQL.java | 168 +++++++++--------- .../fr/xephi/authme/datasource/SQLite.java | 152 ++++++++-------- .../events/AuthMeAsyncPreLoginEvent.java | 12 +- .../authme/events/AuthMeTeleportEvent.java | 12 +- .../fr/xephi/authme/events/CustomEvent.java | 18 +- .../events/FirstSpawnTeleportEvent.java | 12 +- .../fr/xephi/authme/events/LoginEvent.java | 16 +- .../fr/xephi/authme/events/LogoutEvent.java | 12 +- .../events/PasswordEncryptionEvent.java | 16 +- .../authme/events/ProtectInventoryEvent.java | 20 +-- .../authme/events/RegisterTeleportEvent.java | 12 +- .../authme/events/ResetInventoryEvent.java | 4 +- .../authme/events/RestoreInventoryEvent.java | 4 +- .../authme/events/SpawnTeleportEvent.java | 16 +- .../xephi/authme/hooks/BungeeCordMessage.java | 4 +- .../java/fr/xephi/authme/hooks/EssSpawn.java | 8 +- .../AuthMeInventoryPacketAdapter.java | 4 +- .../java/fr/xephi/authme/modules/Module.java | 8 +- .../xephi/authme/modules/ModuleManager.java | 20 +-- .../authme/permission/PermissionsManager.java | 22 +-- .../authme/process/join/AsyncronousJoin.java | 4 +- .../process/login/AsyncronousLogin.java | 12 +- .../login/ProcessSyncronousPlayerLogin.java | 4 +- .../process/register/AsyncRegister.java | 14 +- .../unregister/AsyncronousUnregister.java | 4 +- .../xephi/authme/security/HashAlgorithm.java | 4 +- .../authme/security/PasswordSecurity.java | 24 +-- .../xephi/authme/security/RandomString.java | 4 +- .../xephi/authme/security/crypts/BCRYPT.java | 30 ++-- .../authme/security/crypts/BCRYPT2Y.java | 16 +- .../authme/security/crypts/CRAZYCRYPT1.java | 16 +- .../authme/security/crypts/CryptPBKDF2.java | 16 +- .../security/crypts/CryptPBKDF2Django.java | 16 +- .../authme/security/crypts/DOUBLEMD5.java | 22 +-- .../security/crypts/EncryptionMethod.java | 6 +- .../fr/xephi/authme/security/crypts/IPB3.java | 22 +-- .../xephi/authme/security/crypts/JOOMLA.java | 22 +-- .../fr/xephi/authme/security/crypts/MD5.java | 22 +-- .../xephi/authme/security/crypts/MD5VB.java | 22 +-- .../fr/xephi/authme/security/crypts/MYBB.java | 22 +-- .../xephi/authme/security/crypts/PHPBB.java | 56 +++--- .../authme/security/crypts/PHPFUSION.java | 22 +-- .../authme/security/crypts/PLAINTEXT.java | 16 +- .../authme/security/crypts/ROYALAUTH.java | 22 +-- .../authme/security/crypts/SALTED2MD5.java | 22 +-- .../authme/security/crypts/SALTEDSHA512.java | 22 +-- .../fr/xephi/authme/security/crypts/SHA1.java | 22 +-- .../xephi/authme/security/crypts/SHA256.java | 22 +-- .../xephi/authme/security/crypts/SHA512.java | 22 +-- .../fr/xephi/authme/security/crypts/SMF.java | 22 +-- .../fr/xephi/authme/security/crypts/WBB3.java | 22 +-- .../fr/xephi/authme/security/crypts/WBB4.java | 16 +- .../authme/security/crypts/WHIRLPOOL.java | 20 +-- .../authme/security/crypts/WORDPRESS.java | 32 ++-- .../xephi/authme/security/crypts/XAUTH.java | 20 +-- .../fr/xephi/authme/security/crypts/XF.java | 26 +-- .../authme/security/pbkdf2/MacBasedPRF.java | 16 +- .../authme/security/pbkdf2/PBKDF2Engine.java | 41 ++--- .../security/pbkdf2/PBKDF2HexFormatter.java | 12 +- .../security/pbkdf2/PBKDF2Parameters.java | 20 +-- .../authme/settings/CustomConfiguration.java | 12 +- .../fr/xephi/authme/settings/Messages.java | 8 +- .../xephi/authme/settings/OtherAccounts.java | 8 +- .../fr/xephi/authme/settings/Settings.java | 24 +-- .../java/fr/xephi/authme/settings/Spawn.java | 24 +-- .../fr/xephi/authme/task/TimeoutTask.java | 4 +- .../fr/xephi/authme/util/StringUtils.java | 4 +- src/main/java/fr/xephi/authme/util/Utils.java | 40 ++--- 94 files changed, 1176 insertions(+), 1174 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index bdf8ad09..c4cd327e 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -115,8 +115,8 @@ public class AuthMe extends JavaPlugin { /** * Method canConnect. - * @return boolean - */ + + * @return boolean */ public boolean canConnect() { return canConnect; } @@ -131,16 +131,16 @@ public class AuthMe extends JavaPlugin { /** * Method getInstance. - * @return AuthMe - */ + + * @return AuthMe */ public static AuthMe getInstance() { return plugin; } /** * Method getSettings. - * @return Settings - */ + + * @return Settings */ public Settings getSettings() { return settings; } @@ -155,8 +155,8 @@ public class AuthMe extends JavaPlugin { /** * Method getMessages. - * @return Messages - */ + + * @return Messages */ public Messages getMessages() { return m; } @@ -450,8 +450,8 @@ public class AuthMe extends JavaPlugin { // Initialize and setup the database /** * Method setupDatabase. - * @throws Exception - */ + + * @throws Exception */ public void setupDatabase() throws Exception { if (database != null) database.close(); @@ -614,8 +614,8 @@ public class AuthMe extends JavaPlugin { * @param player * @param perm - * @return boolean - */ + + * @return boolean */ public boolean authmePermissible(Player player, String perm) { // New code: return getPermissionsManager().hasPermission(player, perm); @@ -635,8 +635,8 @@ public class AuthMe extends JavaPlugin { * @param sender * @param perm - * @return boolean - */ + + * @return boolean */ public boolean authmePermissible(CommandSender sender, String perm) { // Handle players with the permissions manager if(sender instanceof Player) { @@ -685,8 +685,8 @@ public class AuthMe extends JavaPlugin { /** * Method generateKickPlayer. * @param collection Collection - * @return Player - */ + + * @return Player */ public Player generateKickPlayer(Collection collection) { Player player = null; for (Player p : collection) { @@ -734,8 +734,8 @@ public class AuthMe extends JavaPlugin { /** * Method getSpawnLocation. * @param player Player - * @return Location - */ + + * @return Location */ public Location getSpawnLocation(Player player) { World world = player.getWorld(); String[] spawnPriority = Settings.spawnPriority.split(","); @@ -761,8 +761,8 @@ public class AuthMe extends JavaPlugin { /** * Method getDefaultSpawn. * @param world World - * @return Location - */ + + * @return Location */ private Location getDefaultSpawn(World world) { return world.getSpawnLocation(); } @@ -771,8 +771,8 @@ public class AuthMe extends JavaPlugin { /** * Method getMultiverseSpawn. * @param world World - * @return Location - */ + + * @return Location */ private Location getMultiverseSpawn(World world) { if (multiverse != null && Settings.multiverse) { try { @@ -787,8 +787,8 @@ public class AuthMe extends JavaPlugin { // Return the essentials spawnpoint /** * Method getEssentialsSpawn. - * @return Location - */ + + * @return Location */ private Location getEssentialsSpawn() { if (essentialsSpawn != null) { return essentialsSpawn; @@ -800,8 +800,8 @@ public class AuthMe extends JavaPlugin { /** * Method getAuthMeSpawn. * @param player Player - * @return Location - */ + + * @return Location */ private Location getAuthMeSpawn(Player player) { if ((!database.isAuthAvailable(player.getName().toLowerCase()) || !player.hasPlayedBefore()) && (Spawn.getInstance().getFirstSpawn() != null)) { return Spawn.getInstance().getFirstSpawn(); @@ -823,8 +823,8 @@ public class AuthMe extends JavaPlugin { /** * Method getAntiBotModMode. - * @return boolean - */ + + * @return boolean */ public boolean getAntiBotModMode() { return this.antibotMod; } @@ -855,8 +855,8 @@ public class AuthMe extends JavaPlugin { * Method replaceAllInfos. * @param message String * @param player Player - * @return String - */ + + * @return String */ public String replaceAllInfos(String message, Player player) { int playersOnline = Utils.getOnlinePlayers().size(); message = message.replace("&", "\u00a7"); @@ -875,8 +875,8 @@ public class AuthMe extends JavaPlugin { /** * Method getIP. * @param player Player - * @return String - */ + + * @return String */ public String getIP(Player player) { String name = player.getName().toLowerCase(); String ip = player.getAddress().getAddress().getHostAddress(); @@ -894,8 +894,8 @@ public class AuthMe extends JavaPlugin { * Method isLoggedIp. * @param name String * @param ip String - * @return boolean - */ + + * @return boolean */ public boolean isLoggedIp(String name, String ip) { int count = 0; for (Player player : Utils.getOnlinePlayers()) { @@ -909,8 +909,8 @@ public class AuthMe extends JavaPlugin { * Method hasJoinedIp. * @param name String * @param ip String - * @return boolean - */ + + * @return boolean */ public boolean hasJoinedIp(String name, String ip) { int count = 0; for (Player player : Utils.getOnlinePlayers()) { @@ -922,8 +922,8 @@ public class AuthMe extends JavaPlugin { /** * Method getModuleManager. - * @return ModuleManager - */ + + * @return ModuleManager */ public ModuleManager getModuleManager() { return moduleManager; } @@ -933,8 +933,8 @@ public class AuthMe extends JavaPlugin { * * @param player * player - * @return String - */ + + * @return String */ @Deprecated public String getVeryGamesIP(Player player) { String realIP = player.getAddress().getAddress().getHostAddress(); @@ -956,8 +956,8 @@ public class AuthMe extends JavaPlugin { /** * Method getCountryCode. * @param ip String - * @return String - */ + + * @return String */ @Deprecated public String getCountryCode(String ip) { return Utils.getCountryCode(ip); @@ -966,8 +966,8 @@ public class AuthMe extends JavaPlugin { /** * Method getCountryName. * @param ip String - * @return String - */ + + * @return String */ @Deprecated public String getCountryName(String ip) { return Utils.getCountryName(ip); @@ -995,7 +995,8 @@ public class AuthMe extends JavaPlugin { * The command arguments (Bukkit). * - * @return True if the command was executed, false otherwise. * @see org.bukkit.command.CommandExecutor#onCommand(CommandSender, Command, String, String[]) + + * @return True if the command was executed, false otherwise. * @see org.bukkit.command.CommandExecutor#onCommand(CommandSender, Command, String, String[]) * @see org.bukkit.command.CommandExecutor#onCommand(CommandSender, Command, String, String[]) */ @Override public boolean onCommand(CommandSender sender, Command cmd, diff --git a/src/main/java/fr/xephi/authme/ConsoleFilter.java b/src/main/java/fr/xephi/authme/ConsoleFilter.java index 60282088..668ff206 100644 --- a/src/main/java/fr/xephi/authme/ConsoleFilter.java +++ b/src/main/java/fr/xephi/authme/ConsoleFilter.java @@ -16,9 +16,9 @@ public class ConsoleFilter implements Filter { /** * Method isLoggable. * @param record LogRecord - * @return boolean - * @see java.util.logging.Filter#isLoggable(LogRecord) - */ + + + * @return boolean * @see java.util.logging.Filter#isLoggable(LogRecord) */ @Override public boolean isLoggable(LogRecord record) { try { diff --git a/src/main/java/fr/xephi/authme/DataManager.java b/src/main/java/fr/xephi/authme/DataManager.java index fc65845e..c808ebc5 100644 --- a/src/main/java/fr/xephi/authme/DataManager.java +++ b/src/main/java/fr/xephi/authme/DataManager.java @@ -35,8 +35,8 @@ public class DataManager { /** * Method getOfflinePlayer. * @param name String - * @return OfflinePlayer - */ + + * @return OfflinePlayer */ public synchronized OfflinePlayer getOfflinePlayer(final String name) { ExecutorService executor = Executors.newSingleThreadExecutor(); Future result = executor.submit(new Callable() { @@ -197,8 +197,8 @@ public class DataManager { * Method isOnline. * @param player Player * @param name String - * @return boolean - */ + + * @return boolean */ public boolean isOnline(Player player, final String name) { if (player.isOnline()) return true; @@ -226,8 +226,8 @@ public class DataManager { /** * Method getOnlinePlayerLower. * @param name String - * @return Player - */ + + * @return Player */ public Player getOnlinePlayerLower(String name) { name = name.toLowerCase(); for (Player player : Utils.getOnlinePlayers()) { diff --git a/src/main/java/fr/xephi/authme/ImageGenerator.java b/src/main/java/fr/xephi/authme/ImageGenerator.java index ea3d6634..2b6a187e 100644 --- a/src/main/java/fr/xephi/authme/ImageGenerator.java +++ b/src/main/java/fr/xephi/authme/ImageGenerator.java @@ -21,8 +21,8 @@ public class ImageGenerator { /** * Method generateImage. - * @return BufferedImage - */ + + * @return BufferedImage */ public BufferedImage generateImage() { BufferedImage image = new BufferedImage(200, 60, BufferedImage.TYPE_BYTE_INDEXED); Graphics2D graphics = image.createGraphics(); diff --git a/src/main/java/fr/xephi/authme/Log4JFilter.java b/src/main/java/fr/xephi/authme/Log4JFilter.java index ed9ada5e..5eebc7cb 100644 --- a/src/main/java/fr/xephi/authme/Log4JFilter.java +++ b/src/main/java/fr/xephi/authme/Log4JFilter.java @@ -27,9 +27,9 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { /** * Method filter. * @param record LogEvent - * @return Result - * @see org.apache.logging.log4j.core.Filter#filter(LogEvent) - */ + + + * @return Result * @see org.apache.logging.log4j.core.Filter#filter(LogEvent) */ @Override public Result filter(LogEvent record) { if (record == null) { @@ -45,9 +45,9 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { * @param arg2 Marker * @param message String * @param arg4 Object[] - * @return Result - * @see org.apache.logging.log4j.core.Filter#filter(Logger, Level, Marker, String, Object[]) - */ + + + * @return Result * @see org.apache.logging.log4j.core.Filter#filter(Logger, Level, Marker, String, Object[]) */ @Override public Result filter(Logger arg0, Level arg1, Marker arg2, String message, Object... arg4) { @@ -61,9 +61,9 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { * @param arg2 Marker * @param message Object * @param arg4 Throwable - * @return Result - * @see org.apache.logging.log4j.core.Filter#filter(Logger, Level, Marker, Object, Throwable) - */ + + + * @return Result * @see org.apache.logging.log4j.core.Filter#filter(Logger, Level, Marker, Object, Throwable) */ @Override public Result filter(Logger arg0, Level arg1, Marker arg2, Object message, Throwable arg4) { @@ -80,9 +80,9 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { * @param arg2 Marker * @param message Message * @param arg4 Throwable - * @return Result - * @see org.apache.logging.log4j.core.Filter#filter(Logger, Level, Marker, Message, Throwable) - */ + + + * @return Result * @see org.apache.logging.log4j.core.Filter#filter(Logger, Level, Marker, Message, Throwable) */ @Override public Result filter(Logger arg0, Level arg1, Marker arg2, Message message, Throwable arg4) { @@ -91,9 +91,9 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { /** * Method getOnMatch. - * @return Result - * @see org.apache.logging.log4j.core.Filter#getOnMatch() - */ + + + * @return Result * @see org.apache.logging.log4j.core.Filter#getOnMatch() */ @Override public Result getOnMatch() { return Result.NEUTRAL; @@ -101,9 +101,9 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { /** * Method getOnMismatch. - * @return Result - * @see org.apache.logging.log4j.core.Filter#getOnMismatch() - */ + + + * @return Result * @see org.apache.logging.log4j.core.Filter#getOnMismatch() */ @Override public Result getOnMismatch() { return Result.NEUTRAL; diff --git a/src/main/java/fr/xephi/authme/PerformBackup.java b/src/main/java/fr/xephi/authme/PerformBackup.java index 9cda7fac..355c1649 100644 --- a/src/main/java/fr/xephi/authme/PerformBackup.java +++ b/src/main/java/fr/xephi/authme/PerformBackup.java @@ -37,8 +37,8 @@ public class PerformBackup { /** * Method doBackup. - * @return boolean - */ + + * @return boolean */ public boolean doBackup() { switch (Settings.getDataSource) { @@ -55,8 +55,8 @@ public class PerformBackup { /** * Method MySqlBackup. - * @return boolean - */ + + * @return boolean */ private boolean MySqlBackup() { File dirBackup = new File(AuthMe.getInstance().getDataFolder() + "/backups"); @@ -99,8 +99,8 @@ public class PerformBackup { /** * Method FileBackup. * @param backend String - * @return boolean - */ + + * @return boolean */ private boolean FileBackup(String backend) { File dirBackup = new File(AuthMe.getInstance().getDataFolder() + "/backups"); @@ -124,8 +124,8 @@ public class PerformBackup { /** * Method checkWindows. * @param windowsPath String - * @return boolean - */ + + * @return boolean */ private boolean checkWindows(String windowsPath) { String isWin = System.getProperty("os.name").toLowerCase(); if (isWin.indexOf("win") >= 0) { @@ -145,8 +145,8 @@ public class PerformBackup { * Method copy. * @param src File * @param dst File - * @throws IOException - */ + + * @throws IOException */ void copy(File src, File dst) throws IOException { InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst); @@ -171,8 +171,8 @@ public class PerformBackup { /** * Method getInstance. - * @return AuthMe - */ + + * @return AuthMe */ public AuthMe getInstance() { return instance; } diff --git a/src/main/java/fr/xephi/authme/api/API.java b/src/main/java/fr/xephi/authme/api/API.java index 8f5bf87e..6119f28e 100644 --- a/src/main/java/fr/xephi/authme/api/API.java +++ b/src/main/java/fr/xephi/authme/api/API.java @@ -50,8 +50,8 @@ public class API { /** * Method getPlugin. - * @return AuthMe - */ + + * @return AuthMe */ @Deprecated public AuthMe getPlugin() { return instance; @@ -100,8 +100,8 @@ public class API { /** * Method getLastLocation. * @param player Player - * @return Location - */ + + * @return Location */ @Deprecated public static Location getLastLocation(Player player) { try { diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index ea1fc4ac..b1aa27a6 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -58,8 +58,8 @@ public class NewAPI { /** * Method getPlugin. - * @return AuthMe - */ + + * @return AuthMe */ public AuthMe getPlugin() { return plugin; } @@ -94,8 +94,8 @@ public class NewAPI { /** * Method getLastLocation. * @param player Player - * @return Location - */ + + * @return Location */ public Location getLastLocation(Player player) { try { PlayerAuth auth = PlayerCache.getInstance().getAuth(player.getName().toLowerCase()); diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java index 9ca05728..5b229823 100644 --- a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java +++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java @@ -189,16 +189,16 @@ public class PlayerAuth { /** * Method getNickname. - * @return String - */ + + * @return String */ public String getNickname() { return nickname; } /** * Method getRealName. - * @return String - */ + + * @return String */ public String getRealName() { return realName; } @@ -213,8 +213,8 @@ public class PlayerAuth { /** * Method getGroupId. - * @return int - */ + + * @return int */ public int getGroupId() { return groupId; } @@ -229,8 +229,8 @@ public class PlayerAuth { /** * Method getQuitLocX. - * @return double - */ + + * @return double */ public double getQuitLocX() { return x; } @@ -245,8 +245,8 @@ public class PlayerAuth { /** * Method getQuitLocY. - * @return double - */ + + * @return double */ public double getQuitLocY() { return y; } @@ -261,8 +261,8 @@ public class PlayerAuth { /** * Method getQuitLocZ. - * @return double - */ + + * @return double */ public double getQuitLocZ() { return z; } @@ -277,8 +277,8 @@ public class PlayerAuth { /** * Method getWorld. - * @return String - */ + + * @return String */ public String getWorld() { return world; } @@ -293,8 +293,8 @@ public class PlayerAuth { /** * Method getIp. - * @return String - */ + + * @return String */ public String getIp() { return ip; } @@ -309,8 +309,8 @@ public class PlayerAuth { /** * Method getLastLogin. - * @return long - */ + + * @return long */ public long getLastLogin() { return lastLogin; } @@ -325,8 +325,8 @@ public class PlayerAuth { /** * Method getEmail. - * @return String - */ + + * @return String */ public String getEmail() { return email; } @@ -341,8 +341,8 @@ public class PlayerAuth { /** * Method getSalt. - * @return String - */ + + * @return String */ public String getSalt() { return this.salt; } @@ -357,8 +357,8 @@ public class PlayerAuth { /** * Method getHash. - * @return String - */ + + * @return String */ public String getHash() { if (Settings.getPasswordHash == HashAlgorithm.MD5VB) { if (salt != null && !salt.isEmpty() && Settings.getPasswordHash == HashAlgorithm.MD5VB) { @@ -371,8 +371,8 @@ public class PlayerAuth { /** * Method equals. * @param obj Object - * @return boolean - */ + + * @return boolean */ @Override public boolean equals(Object obj) { if (!(obj instanceof PlayerAuth)) { @@ -384,8 +384,8 @@ public class PlayerAuth { /** * Method hashCode. - * @return int - */ + + * @return int */ @Override public int hashCode() { int hashCode = 7; @@ -396,8 +396,8 @@ public class PlayerAuth { /** * Method toString. - * @return String - */ + + * @return String */ @Override public String toString() { return ("Player : " + nickname + " | " + realName diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java index d4df6e06..e72fcd68 100644 --- a/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java +++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java @@ -41,8 +41,8 @@ public class PlayerCache { /** * Method isAuthenticated. * @param user String - * @return boolean - */ + + * @return boolean */ public boolean isAuthenticated(String user) { return cache.containsKey(user.toLowerCase()); } @@ -50,16 +50,16 @@ public class PlayerCache { /** * Method getAuth. * @param user String - * @return PlayerAuth - */ + + * @return PlayerAuth */ public PlayerAuth getAuth(String user) { return cache.get(user.toLowerCase()); } /** * Method getInstance. - * @return PlayerCache - */ + + * @return PlayerCache */ public static PlayerCache getInstance() { if (singleton == null) { singleton = new PlayerCache(); @@ -69,16 +69,16 @@ public class PlayerCache { /** * Method getLogged. - * @return int - */ + + * @return int */ public int getLogged() { return cache.size(); } /** * Method getCache. - * @return ConcurrentHashMap - */ + + * @return ConcurrentHashMap */ public ConcurrentHashMap getCache() { return this.cache; } diff --git a/src/main/java/fr/xephi/authme/cache/backup/DataFileCache.java b/src/main/java/fr/xephi/authme/cache/backup/DataFileCache.java index 07534751..01751d78 100644 --- a/src/main/java/fr/xephi/authme/cache/backup/DataFileCache.java +++ b/src/main/java/fr/xephi/authme/cache/backup/DataFileCache.java @@ -22,24 +22,24 @@ public class DataFileCache { /** * Method getGroup. - * @return String - */ + + * @return String */ public String getGroup() { return group; } /** * Method getOperator. - * @return boolean - */ + + * @return boolean */ public boolean getOperator() { return operator; } /** * Method isFlying. - * @return boolean - */ + + * @return boolean */ public boolean isFlying() { return flying; } diff --git a/src/main/java/fr/xephi/authme/cache/backup/JsonCache.java b/src/main/java/fr/xephi/authme/cache/backup/JsonCache.java index d3913471..70d6614c 100644 --- a/src/main/java/fr/xephi/authme/cache/backup/JsonCache.java +++ b/src/main/java/fr/xephi/authme/cache/backup/JsonCache.java @@ -78,8 +78,8 @@ public class JsonCache { /** * Method readCache. * @param player Player - * @return DataFileCache - */ + + * @return DataFileCache */ public DataFileCache readCache(Player player) { String path; try { @@ -110,8 +110,8 @@ public class JsonCache { * @param dataFileCache DataFileCache * @param type Type * @param jsonSerializationContext JsonSerializationContext - * @return JsonElement - */ + + * @return JsonElement */ @Override public JsonElement serialize(DataFileCache dataFileCache, Type type, JsonSerializationContext jsonSerializationContext) { JsonObject jsonObject = new JsonObject(); @@ -131,10 +131,10 @@ public class JsonCache { * @param jsonElement JsonElement * @param type Type * @param jsonDeserializationContext JsonDeserializationContext - * @return DataFileCache - * @throws JsonParseException - * @see com.google.gson.JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext) - */ + + + + * @return DataFileCache * @throws JsonParseException * @see com.google.gson.JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext) */ @Override public DataFileCache deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { JsonObject jsonObject = jsonElement.getAsJsonObject(); @@ -183,8 +183,8 @@ public class JsonCache { /** * Method doesCacheExist. * @param player Player - * @return boolean - */ + + * @return boolean */ public boolean doesCacheExist(Player player) { String path; try { diff --git a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java index 1bfe8164..7205c708 100644 --- a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java @@ -105,8 +105,8 @@ public class LimboCache { /** * Method getLimboPlayer. * @param name String - * @return LimboPlayer - */ + + * @return LimboPlayer */ public LimboPlayer getLimboPlayer(String name) { return cache.get(name); } @@ -114,16 +114,16 @@ public class LimboCache { /** * Method hasLimboPlayer. * @param name String - * @return boolean - */ + + * @return boolean */ public boolean hasLimboPlayer(String name) { return cache.containsKey(name); } /** * Method getInstance. - * @return LimboCache - */ + + * @return LimboCache */ public static LimboCache getInstance() { if (singleton == null) { singleton = new LimboCache(AuthMe.getInstance()); diff --git a/src/main/java/fr/xephi/authme/cache/limbo/LimboPlayer.java b/src/main/java/fr/xephi/authme/cache/limbo/LimboPlayer.java index 3d2dc304..d6f7b02b 100644 --- a/src/main/java/fr/xephi/authme/cache/limbo/LimboPlayer.java +++ b/src/main/java/fr/xephi/authme/cache/limbo/LimboPlayer.java @@ -48,40 +48,40 @@ public class LimboPlayer { /** * Method getName. - * @return String - */ + + * @return String */ public String getName() { return name; } /** * Method getLoc. - * @return Location - */ + + * @return Location */ public Location getLoc() { return loc; } /** * Method getGameMode. - * @return GameMode - */ + + * @return GameMode */ public GameMode getGameMode() { return gameMode; } /** * Method getOperator. - * @return boolean - */ + + * @return boolean */ public boolean getOperator() { return operator; } /** * Method getGroup. - * @return String - */ + + * @return String */ public String getGroup() { return group; } @@ -98,8 +98,8 @@ public class LimboPlayer { /** * Method getTimeoutTaskId. - * @return BukkitTask - */ + + * @return BukkitTask */ public BukkitTask getTimeoutTaskId() { return timeoutTaskId; } @@ -116,16 +116,16 @@ public class LimboPlayer { /** * Method getMessageTaskId. - * @return BukkitTask - */ + + * @return BukkitTask */ public BukkitTask getMessageTaskId() { return messageTaskId; } /** * Method isFlying. - * @return boolean - */ + + * @return boolean */ public boolean isFlying() { return flying; } diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java index 2b02eed0..51ce67b0 100644 --- a/src/main/java/fr/xephi/authme/command/CommandDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandDescription.java @@ -307,8 +307,8 @@ public class CommandDescription { /** * Get the absolute command label, without a slash. - * @return String - */ + + * @return String */ public String getAbsoluteLabel() { return getAbsoluteLabel(false); } diff --git a/src/main/java/fr/xephi/authme/command/executable/converter/ConverterCommand.java b/src/main/java/fr/xephi/authme/command/executable/converter/ConverterCommand.java index ba71ad1c..d18c4065 100644 --- a/src/main/java/fr/xephi/authme/command/executable/converter/ConverterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/converter/ConverterCommand.java @@ -111,8 +111,8 @@ public class ConverterCommand extends ExecutableCommand { /** * Method getName. - * @return String - */ + + * @return String */ String getName() { return this.name; } @@ -120,8 +120,8 @@ public class ConverterCommand extends ExecutableCommand { /** * Method fromName. * @param name String - * @return ConvertType - */ + + * @return ConvertType */ public static ConvertType fromName(String name) { for (ConvertType type : ConvertType.values()) { if (type.getName().equalsIgnoreCase(name)) diff --git a/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java b/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java index 5b2e1ada..e7d7e363 100644 --- a/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java +++ b/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java @@ -36,8 +36,8 @@ public class CrazyLoginConverter implements Converter { /** * Method getInstance. - * @return CrazyLoginConverter - */ + + * @return CrazyLoginConverter */ public CrazyLoginConverter getInstance() { return this; } diff --git a/src/main/java/fr/xephi/authme/converter/FlatToSqlite.java b/src/main/java/fr/xephi/authme/converter/FlatToSqlite.java index 6ace0fff..f137f5f4 100644 --- a/src/main/java/fr/xephi/authme/converter/FlatToSqlite.java +++ b/src/main/java/fr/xephi/authme/converter/FlatToSqlite.java @@ -121,9 +121,9 @@ public class FlatToSqlite implements Converter { /** * Method connect. - * @throws ClassNotFoundException - * @throws SQLException - */ + + + * @throws ClassNotFoundException * @throws SQLException */ private synchronized void connect() throws ClassNotFoundException, SQLException { Class.forName("org.sqlite.JDBC"); con = DriverManager.getConnection("jdbc:sqlite:plugins/AuthMe/" + database + ".db"); @@ -131,8 +131,8 @@ public class FlatToSqlite implements Converter { /** * Method setup. - * @throws SQLException - */ + + * @throws SQLException */ private synchronized void setup() throws SQLException { Statement st = null; ResultSet rs = null; @@ -179,8 +179,8 @@ public class FlatToSqlite implements Converter { /** * Method saveAuth. * @param s String - * @return boolean - */ + + * @return boolean */ private synchronized boolean saveAuth(String s) { PreparedStatement pst = null; try { diff --git a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java index 7a34fa89..383adb40 100644 --- a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java +++ b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java @@ -41,8 +41,8 @@ public class RakamakConverter implements Converter { /** * Method getInstance. - * @return RakamakConverter - */ + + * @return RakamakConverter */ public RakamakConverter getInstance() { return this; } diff --git a/src/main/java/fr/xephi/authme/converter/RoyalAuthYamlReader.java b/src/main/java/fr/xephi/authme/converter/RoyalAuthYamlReader.java index bf1d3e73..75757bc9 100644 --- a/src/main/java/fr/xephi/authme/converter/RoyalAuthYamlReader.java +++ b/src/main/java/fr/xephi/authme/converter/RoyalAuthYamlReader.java @@ -20,16 +20,16 @@ public class RoyalAuthYamlReader extends CustomConfiguration { /** * Method getLastLogin. - * @return long - */ + + * @return long */ public long getLastLogin() { return getLong("timestamps.quit"); } /** * Method getHash. - * @return String - */ + + * @return String */ public String getHash() { return getString("login.password"); } diff --git a/src/main/java/fr/xephi/authme/converter/vAuthFileReader.java b/src/main/java/fr/xephi/authme/converter/vAuthFileReader.java index b0d3874b..8a899acd 100644 --- a/src/main/java/fr/xephi/authme/converter/vAuthFileReader.java +++ b/src/main/java/fr/xephi/authme/converter/vAuthFileReader.java @@ -35,8 +35,8 @@ public class vAuthFileReader { /** * Method convert. - * @throws IOException - */ + + * @throws IOException */ public void convert() throws IOException { final File file = new File(plugin.getDataFolder().getParent() + "" + File.separator + "vAuth" + File.separator + "passwords.yml"); Scanner scanner; @@ -71,8 +71,8 @@ public class vAuthFileReader { /** * Method isUUIDinstance. * @param s String - * @return boolean - */ + + * @return boolean */ private boolean isUUIDinstance(String s) { if (String.valueOf(s.charAt(8)).equalsIgnoreCase("-")) return true; @@ -82,8 +82,8 @@ public class vAuthFileReader { /** * Method getName. * @param uuid UUID - * @return String - */ + + * @return String */ private String getName(UUID uuid) { try { for (OfflinePlayer op : Bukkit.getOfflinePlayers()) { diff --git a/src/main/java/fr/xephi/authme/converter/xAuthToFlat.java b/src/main/java/fr/xephi/authme/converter/xAuthToFlat.java index 9430e4d1..86c88f73 100644 --- a/src/main/java/fr/xephi/authme/converter/xAuthToFlat.java +++ b/src/main/java/fr/xephi/authme/converter/xAuthToFlat.java @@ -38,8 +38,8 @@ public class xAuthToFlat { /** * Method convert. - * @return boolean - */ + + * @return boolean */ public boolean convert() { if (instance.getServer().getPluginManager().getPlugin("xAuth") == null) { sender.sendMessage("[AuthMe] xAuth plugin not found"); @@ -73,8 +73,8 @@ public class xAuthToFlat { /** * Method getIdPlayer. * @param id int - * @return String - */ + + * @return String */ public String getIdPlayer(int id) { String realPass = ""; Connection conn = xAuth.getPlugin().getDatabaseController().getConnection(); @@ -99,8 +99,8 @@ public class xAuthToFlat { /** * Method getXAuthPlayers. - * @return List - */ + + * @return List */ public List getXAuthPlayers() { List xP = new ArrayList<>(); Connection conn = xAuth.getPlugin().getDatabaseController().getConnection(); @@ -125,8 +125,8 @@ public class xAuthToFlat { /** * Method getPassword. * @param accountId int - * @return String - */ + + * @return String */ public String getPassword(int accountId) { String realPass = ""; Connection conn = xAuth.getPlugin().getDatabaseController().getConnection(); diff --git a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java index f497d390..b88d4853 100644 --- a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java @@ -52,9 +52,9 @@ public class CacheDataSource implements DataSource { /** * Method isAuthAvailable. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) */ @Override public synchronized boolean isAuthAvailable(String user) { return cache.containsKey(user.toLowerCase()); @@ -63,9 +63,9 @@ public class CacheDataSource implements DataSource { /** * Method getAuth. * @param user String - * @return PlayerAuth - * @see fr.xephi.authme.datasource.DataSource#getAuth(String) - */ + + + * @return PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getAuth(String) */ @Override public synchronized PlayerAuth getAuth(String user) { user = user.toLowerCase(); @@ -78,9 +78,9 @@ public class CacheDataSource implements DataSource { /** * Method saveAuth. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) */ @Override public synchronized boolean saveAuth(final PlayerAuth auth) { cache.put(auth.getNickname(), auth); @@ -98,9 +98,9 @@ public class CacheDataSource implements DataSource { /** * Method updatePassword. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) */ @Override public synchronized boolean updatePassword(final PlayerAuth auth) { if (!cache.containsKey(auth.getNickname())) { @@ -124,9 +124,9 @@ public class CacheDataSource implements DataSource { /** * Method updateSession. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) */ @Override public boolean updateSession(final PlayerAuth auth) { if (!cache.containsKey(auth.getNickname())) { @@ -159,9 +159,9 @@ public class CacheDataSource implements DataSource { /** * Method updateQuitLoc. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) */ @Override public boolean updateQuitLoc(final PlayerAuth auth) { if (!cache.containsKey(auth.getNickname())) { @@ -197,9 +197,9 @@ public class CacheDataSource implements DataSource { /** * Method getIps. * @param ip String - * @return int - * @see fr.xephi.authme.datasource.DataSource#getIps(String) - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#getIps(String) */ @Override public int getIps(String ip) { int count = 0; @@ -214,9 +214,9 @@ public class CacheDataSource implements DataSource { /** * Method purgeDatabase. * @param until long - * @return int - * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) */ @Override public int purgeDatabase(long until) { int cleared = source.purgeDatabase(until); @@ -233,9 +233,9 @@ public class CacheDataSource implements DataSource { /** * Method autoPurgeDatabase. * @param until long - * @return List - * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) */ @Override public List autoPurgeDatabase(long until) { List cleared = source.autoPurgeDatabase(until); @@ -252,9 +252,9 @@ public class CacheDataSource implements DataSource { /** * Method removeAuth. * @param username String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) */ @Override public synchronized boolean removeAuth(String username) { final String user = username.toLowerCase(); @@ -306,9 +306,9 @@ public class CacheDataSource implements DataSource { /** * Method updateEmail. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) */ @Override public synchronized boolean updateEmail(final PlayerAuth auth) { try { @@ -325,9 +325,9 @@ public class CacheDataSource implements DataSource { /** * Method updateSalt. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) */ @Override public synchronized boolean updateSalt(final PlayerAuth auth) { if (!cache.containsKey(auth.getNickname())) { @@ -352,9 +352,9 @@ public class CacheDataSource implements DataSource { /** * Method getAllAuthsByName. * @param auth PlayerAuth - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) */ @Override public synchronized List getAllAuthsByName(PlayerAuth auth) { List result = new ArrayList<>(); @@ -369,10 +369,10 @@ public class CacheDataSource implements DataSource { /** * Method getAllAuthsByIp. * @param ip String - * @return List - * @throws Exception - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) - */ + + + + * @return List * @throws Exception * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) */ @Override public synchronized List getAllAuthsByIp(final String ip) throws Exception { return exec.submit(new Callable>() { @@ -385,10 +385,10 @@ public class CacheDataSource implements DataSource { /** * Method getAllAuthsByEmail. * @param email String - * @return List - * @throws Exception - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) - */ + + + + * @return List * @throws Exception * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) */ @Override public synchronized List getAllAuthsByEmail(final String email) throws Exception { return exec.submit(new Callable>() { @@ -401,8 +401,8 @@ public class CacheDataSource implements DataSource { /** * Method purgeBanned. * @param banned List - * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) - */ + + * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) */ @Override public synchronized void purgeBanned(final List banned) { exec.execute(new Runnable() { @@ -420,9 +420,9 @@ public class CacheDataSource implements DataSource { /** * Method getType. - * @return DataSourceType - * @see fr.xephi.authme.datasource.DataSource#getType() - */ + + + * @return DataSourceType * @see fr.xephi.authme.datasource.DataSource#getType() */ @Override public DataSourceType getType() { return source.getType(); @@ -431,9 +431,9 @@ public class CacheDataSource implements DataSource { /** * Method isLogged. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#isLogged(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#isLogged(String) */ @Override public boolean isLogged(String user) { user = user.toLowerCase(); @@ -443,8 +443,8 @@ public class CacheDataSource implements DataSource { /** * Method setLogged. * @param user String - * @see fr.xephi.authme.datasource.DataSource#setLogged(String) - */ + + * @see fr.xephi.authme.datasource.DataSource#setLogged(String) */ @Override public void setLogged(final String user) { exec.execute(new Runnable() { @@ -458,8 +458,8 @@ public class CacheDataSource implements DataSource { /** * Method setUnlogged. * @param user String - * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) - */ + + * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) */ @Override public void setUnlogged(final String user) { exec.execute(new Runnable() { @@ -486,9 +486,9 @@ public class CacheDataSource implements DataSource { /** * Method getAccountsRegistered. - * @return int - * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() */ @Override public int getAccountsRegistered() { return cache.size(); @@ -498,8 +498,8 @@ public class CacheDataSource implements DataSource { * Method updateName. * @param oldone String * @param newone String - * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) - */ + + * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) */ @Override public void updateName(final String oldone, final String newone) { if (cache.containsKey(oldone)) { @@ -516,9 +516,9 @@ public class CacheDataSource implements DataSource { /** * Method getAllAuths. - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuths() - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuths() */ @Override public List getAllAuths() { return new ArrayList<>(cache.values()); @@ -526,9 +526,9 @@ public class CacheDataSource implements DataSource { /** * Method getLoggedPlayers. - * @return List - * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() */ @Override public List getLoggedPlayers() { return new ArrayList<>(PlayerCache.getInstance().getCache().values()); diff --git a/src/main/java/fr/xephi/authme/datasource/DataSource.java b/src/main/java/fr/xephi/authme/datasource/DataSource.java index 26745d86..83288751 100644 --- a/src/main/java/fr/xephi/authme/datasource/DataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/DataSource.java @@ -19,108 +19,108 @@ public interface DataSource { /** * Method isAuthAvailable. * @param user String - * @return boolean - */ + + * @return boolean */ boolean isAuthAvailable(String user); /** * Method getAuth. * @param user String - * @return PlayerAuth - */ + + * @return PlayerAuth */ PlayerAuth getAuth(String user); /** * Method saveAuth. * @param auth PlayerAuth - * @return boolean - */ + + * @return boolean */ boolean saveAuth(PlayerAuth auth); /** * Method updateSession. * @param auth PlayerAuth - * @return boolean - */ + + * @return boolean */ boolean updateSession(PlayerAuth auth); /** * Method updatePassword. * @param auth PlayerAuth - * @return boolean - */ + + * @return boolean */ boolean updatePassword(PlayerAuth auth); /** * Method purgeDatabase. * @param until long - * @return int - */ + + * @return int */ int purgeDatabase(long until); /** * Method autoPurgeDatabase. * @param until long - * @return List - */ + + * @return List */ List autoPurgeDatabase(long until); /** * Method removeAuth. * @param user String - * @return boolean - */ + + * @return boolean */ boolean removeAuth(String user); /** * Method updateQuitLoc. * @param auth PlayerAuth - * @return boolean - */ + + * @return boolean */ boolean updateQuitLoc(PlayerAuth auth); /** * Method getIps. * @param ip String - * @return int - */ + + * @return int */ int getIps(String ip); /** * Method getAllAuthsByName. * @param auth PlayerAuth - * @return List - */ + + * @return List */ List getAllAuthsByName(PlayerAuth auth); /** * Method getAllAuthsByIp. * @param ip String - * @return List - * @throws Exception - */ + + + * @return List * @throws Exception */ List getAllAuthsByIp(String ip) throws Exception; /** * Method getAllAuthsByEmail. * @param email String - * @return List - * @throws Exception - */ + + + * @return List * @throws Exception */ List getAllAuthsByEmail(String email) throws Exception; /** * Method updateEmail. * @param auth PlayerAuth - * @return boolean - */ + + * @return boolean */ boolean updateEmail(PlayerAuth auth); /** * Method updateSalt. * @param auth PlayerAuth - * @return boolean - */ + + * @return boolean */ boolean updateSalt(PlayerAuth auth); void close(); @@ -135,15 +135,15 @@ public interface DataSource { /** * Method getType. - * @return DataSourceType - */ + + * @return DataSourceType */ DataSourceType getType(); /** * Method isLogged. * @param user String - * @return boolean - */ + + * @return boolean */ boolean isLogged(String user); /** @@ -162,8 +162,8 @@ public interface DataSource { /** * Method getAccountsRegistered. - * @return int - */ + + * @return int */ int getAccountsRegistered(); /** @@ -175,14 +175,14 @@ public interface DataSource { /** * Method getAllAuths. - * @return List - */ + + * @return List */ List getAllAuths(); /** * Method getLoggedPlayers. - * @return List - */ + + * @return List */ List getLoggedPlayers(); } diff --git a/src/main/java/fr/xephi/authme/datasource/DatabaseCalls.java b/src/main/java/fr/xephi/authme/datasource/DatabaseCalls.java index 316311ae..1e405aae 100644 --- a/src/main/java/fr/xephi/authme/datasource/DatabaseCalls.java +++ b/src/main/java/fr/xephi/authme/datasource/DatabaseCalls.java @@ -27,9 +27,9 @@ public class DatabaseCalls implements DataSource { /** * Method isAuthAvailable. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) */ @Override public synchronized boolean isAuthAvailable(final String user) { try { @@ -46,9 +46,9 @@ public class DatabaseCalls implements DataSource { /** * Method getAuth. * @param user String - * @return PlayerAuth - * @see fr.xephi.authme.datasource.DataSource#getAuth(String) - */ + + + * @return PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getAuth(String) */ @Override public synchronized PlayerAuth getAuth(final String user) { try { @@ -65,9 +65,9 @@ public class DatabaseCalls implements DataSource { /** * Method saveAuth. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) */ @Override public synchronized boolean saveAuth(final PlayerAuth auth) { try { @@ -84,9 +84,9 @@ public class DatabaseCalls implements DataSource { /** * Method updateSession. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) */ @Override public synchronized boolean updateSession(final PlayerAuth auth) { try { @@ -103,9 +103,9 @@ public class DatabaseCalls implements DataSource { /** * Method updatePassword. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) */ @Override public synchronized boolean updatePassword(final PlayerAuth auth) { try { @@ -122,9 +122,9 @@ public class DatabaseCalls implements DataSource { /** * Method purgeDatabase. * @param until long - * @return int - * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) */ @Override public synchronized int purgeDatabase(final long until) { try { @@ -141,9 +141,9 @@ public class DatabaseCalls implements DataSource { /** * Method autoPurgeDatabase. * @param until long - * @return List - * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) */ @Override public synchronized List autoPurgeDatabase(final long until) { try { @@ -160,9 +160,9 @@ public class DatabaseCalls implements DataSource { /** * Method removeAuth. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) */ @Override public synchronized boolean removeAuth(final String user) { try { @@ -179,9 +179,9 @@ public class DatabaseCalls implements DataSource { /** * Method updateQuitLoc. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) */ @Override public synchronized boolean updateQuitLoc(final PlayerAuth auth) { try { @@ -198,9 +198,9 @@ public class DatabaseCalls implements DataSource { /** * Method getIps. * @param ip String - * @return int - * @see fr.xephi.authme.datasource.DataSource#getIps(String) - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#getIps(String) */ @Override public synchronized int getIps(final String ip) { try { @@ -218,9 +218,9 @@ public class DatabaseCalls implements DataSource { /** * Method getAllAuthsByName. * @param auth PlayerAuth - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) */ @Override public synchronized List getAllAuthsByName(final PlayerAuth auth) { try { @@ -237,9 +237,9 @@ public class DatabaseCalls implements DataSource { /** * Method getAllAuthsByIp. * @param ip String - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) */ @Override public synchronized List getAllAuthsByIp(final String ip) { try { @@ -256,9 +256,9 @@ public class DatabaseCalls implements DataSource { /** * Method getAllAuthsByEmail. * @param email String - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) */ @Override public synchronized List getAllAuthsByEmail(final String email) { try { @@ -275,9 +275,9 @@ public class DatabaseCalls implements DataSource { /** * Method updateEmail. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) */ @Override public synchronized boolean updateEmail(final PlayerAuth auth) { try { @@ -294,9 +294,9 @@ public class DatabaseCalls implements DataSource { /** * Method updateSalt. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) */ @Override public synchronized boolean updateSalt(final PlayerAuth auth) { try { @@ -332,8 +332,8 @@ public class DatabaseCalls implements DataSource { /** * Method purgeBanned. * @param banned List - * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) - */ + + * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) */ @Override public synchronized void purgeBanned(final List banned) { new Thread(new Runnable() { @@ -345,9 +345,9 @@ public class DatabaseCalls implements DataSource { /** * Method getType. - * @return DataSourceType - * @see fr.xephi.authme.datasource.DataSource#getType() - */ + + + * @return DataSourceType * @see fr.xephi.authme.datasource.DataSource#getType() */ @Override public synchronized DataSourceType getType() { return database.getType(); @@ -356,9 +356,9 @@ public class DatabaseCalls implements DataSource { /** * Method isLogged. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#isLogged(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#isLogged(String) */ @Override public synchronized boolean isLogged(final String user) { try { @@ -375,8 +375,8 @@ public class DatabaseCalls implements DataSource { /** * Method setLogged. * @param user String - * @see fr.xephi.authme.datasource.DataSource#setLogged(String) - */ + + * @see fr.xephi.authme.datasource.DataSource#setLogged(String) */ @Override public synchronized void setLogged(final String user) { exec.execute(new Runnable() { @@ -389,8 +389,8 @@ public class DatabaseCalls implements DataSource { /** * Method setUnlogged. * @param user String - * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) - */ + + * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) */ @Override public synchronized void setUnlogged(final String user) { exec.execute(new Runnable() { @@ -415,9 +415,9 @@ public class DatabaseCalls implements DataSource { /** * Method getAccountsRegistered. - * @return int - * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() */ @Override public synchronized int getAccountsRegistered() { try { @@ -435,8 +435,8 @@ public class DatabaseCalls implements DataSource { * Method updateName. * @param oldone String * @param newone String - * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) - */ + + * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) */ @Override public synchronized void updateName(final String oldone, final String newone) { exec.execute(new Runnable() { @@ -448,9 +448,9 @@ public class DatabaseCalls implements DataSource { /** * Method getAllAuths. - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuths() - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuths() */ @Override public synchronized List getAllAuths() { try { @@ -466,9 +466,9 @@ public class DatabaseCalls implements DataSource { /** * Method getLoggedPlayers. - * @return List - * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() */ @Override public List getLoggedPlayers() { try { diff --git a/src/main/java/fr/xephi/authme/datasource/FlatFile.java b/src/main/java/fr/xephi/authme/datasource/FlatFile.java index 3d9486b7..b13be8fc 100644 --- a/src/main/java/fr/xephi/authme/datasource/FlatFile.java +++ b/src/main/java/fr/xephi/authme/datasource/FlatFile.java @@ -53,9 +53,9 @@ public class FlatFile implements DataSource { /** * Method isAuthAvailable. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) */ @Override public synchronized boolean isAuthAvailable(String user) { BufferedReader br = null; @@ -88,9 +88,9 @@ public class FlatFile implements DataSource { /** * Method saveAuth. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) */ @Override public synchronized boolean saveAuth(PlayerAuth auth) { if (isAuthAvailable(auth.getNickname())) { @@ -117,9 +117,9 @@ public class FlatFile implements DataSource { /** * Method updatePassword. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) */ @Override public synchronized boolean updatePassword(PlayerAuth auth) { if (!isAuthAvailable(auth.getNickname())) { @@ -182,9 +182,9 @@ public class FlatFile implements DataSource { /** * Method updateSession. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) */ @Override public boolean updateSession(PlayerAuth auth) { if (!isAuthAvailable(auth.getNickname())) { @@ -247,9 +247,9 @@ public class FlatFile implements DataSource { /** * Method updateQuitLoc. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) */ @Override public boolean updateQuitLoc(PlayerAuth auth) { if (!isAuthAvailable(auth.getNickname())) { @@ -291,9 +291,9 @@ public class FlatFile implements DataSource { /** * Method getIps. * @param ip String - * @return int - * @see fr.xephi.authme.datasource.DataSource#getIps(String) - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#getIps(String) */ @Override public int getIps(String ip) { BufferedReader br = null; @@ -327,9 +327,9 @@ public class FlatFile implements DataSource { /** * Method purgeDatabase. * @param until long - * @return int - * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) */ @Override public int purgeDatabase(long until) { BufferedReader br = null; @@ -379,9 +379,9 @@ public class FlatFile implements DataSource { /** * Method autoPurgeDatabase. * @param until long - * @return List - * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) */ @Override public List autoPurgeDatabase(long until) { BufferedReader br = null; @@ -431,9 +431,9 @@ public class FlatFile implements DataSource { /** * Method removeAuth. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) */ @Override public synchronized boolean removeAuth(String user) { if (!isAuthAvailable(user)) { @@ -481,9 +481,9 @@ public class FlatFile implements DataSource { /** * Method getAuth. * @param user String - * @return PlayerAuth - * @see fr.xephi.authme.datasource.DataSource#getAuth(String) - */ + + + * @return PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getAuth(String) */ @Override public synchronized PlayerAuth getAuth(String user) { BufferedReader br = null; @@ -545,9 +545,9 @@ public class FlatFile implements DataSource { /** * Method updateEmail. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) */ @Override public boolean updateEmail(PlayerAuth auth) { if (!isAuthAvailable(auth.getNickname())) { @@ -589,9 +589,9 @@ public class FlatFile implements DataSource { /** * Method updateSalt. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) */ @Override public boolean updateSalt(PlayerAuth auth) { return false; @@ -600,9 +600,9 @@ public class FlatFile implements DataSource { /** * Method getAllAuthsByName. * @param auth PlayerAuth - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) */ @Override public List getAllAuthsByName(PlayerAuth auth) { BufferedReader br = null; @@ -636,9 +636,9 @@ public class FlatFile implements DataSource { /** * Method getAllAuthsByIp. * @param ip String - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) */ @Override public List getAllAuthsByIp(String ip) { BufferedReader br = null; @@ -672,9 +672,9 @@ public class FlatFile implements DataSource { /** * Method getAllAuthsByEmail. * @param email String - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) */ @Override public List getAllAuthsByEmail(String email) { BufferedReader br = null; @@ -708,8 +708,8 @@ public class FlatFile implements DataSource { /** * Method purgeBanned. * @param banned List - * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) - */ + + * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) */ @Override public void purgeBanned(List banned) { BufferedReader br = null; @@ -756,9 +756,9 @@ public class FlatFile implements DataSource { /** * Method getType. - * @return DataSourceType - * @see fr.xephi.authme.datasource.DataSource#getType() - */ + + + * @return DataSourceType * @see fr.xephi.authme.datasource.DataSource#getType() */ @Override public DataSourceType getType() { return DataSourceType.FILE; @@ -767,9 +767,9 @@ public class FlatFile implements DataSource { /** * Method isLogged. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#isLogged(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#isLogged(String) */ @Override public boolean isLogged(String user) { return PlayerCache.getInstance().isAuthenticated(user); @@ -778,8 +778,8 @@ public class FlatFile implements DataSource { /** * Method setLogged. * @param user String - * @see fr.xephi.authme.datasource.DataSource#setLogged(String) - */ + + * @see fr.xephi.authme.datasource.DataSource#setLogged(String) */ @Override public void setLogged(String user) { } @@ -787,8 +787,8 @@ public class FlatFile implements DataSource { /** * Method setUnlogged. * @param user String - * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) - */ + + * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) */ @Override public void setUnlogged(String user) { } @@ -803,9 +803,9 @@ public class FlatFile implements DataSource { /** * Method getAccountsRegistered. - * @return int - * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() */ @Override public int getAccountsRegistered() { BufferedReader br = null; @@ -833,8 +833,8 @@ public class FlatFile implements DataSource { * Method updateName. * @param oldone String * @param newone String - * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) - */ + + * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) */ @Override public void updateName(String oldone, String newone) { PlayerAuth auth = this.getAuth(oldone); @@ -845,9 +845,9 @@ public class FlatFile implements DataSource { /** * Method getAllAuths. - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuths() - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuths() */ @Override public List getAllAuths() { BufferedReader br = null; @@ -897,9 +897,9 @@ public class FlatFile implements DataSource { /** * Method getLoggedPlayers. - * @return List - * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() */ @Override public List getLoggedPlayers() { return new ArrayList<>(); diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 4cd9b3f6..e90ff2a9 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -49,10 +49,10 @@ public class MySQL implements DataSource { /** * Constructor for MySQL. - * @throws ClassNotFoundException - * @throws SQLException - * @throws PoolInitializationException - */ + + + + * @throws ClassNotFoundException * @throws SQLException * @throws PoolInitializationException */ public MySQL() throws ClassNotFoundException, SQLException, PoolInitializationException { this.host = Settings.getMySQLHost; this.port = Settings.getMySQLPort; @@ -108,9 +108,9 @@ public class MySQL implements DataSource { /** * Method setConnectionArguments. - * @throws ClassNotFoundException - * @throws IllegalArgumentException - */ + + + * @throws ClassNotFoundException * @throws IllegalArgumentException */ private synchronized void setConnectionArguments() throws ClassNotFoundException, IllegalArgumentException { HikariConfig config = new HikariConfig(); @@ -132,9 +132,9 @@ public class MySQL implements DataSource { /** * Method reloadArguments. - * @throws ClassNotFoundException - * @throws IllegalArgumentException - */ + + + * @throws ClassNotFoundException * @throws IllegalArgumentException */ private synchronized void reloadArguments() throws ClassNotFoundException, IllegalArgumentException { if (ds != null) { @@ -146,17 +146,17 @@ public class MySQL implements DataSource { /** * Method getConnection. - * @return Connection - * @throws SQLException - */ + + + * @return Connection * @throws SQLException */ private synchronized Connection getConnection() throws SQLException { return ds.getConnection(); } /** * Method setupConnection. - * @throws SQLException - */ + + * @throws SQLException */ private synchronized void setupConnection() throws SQLException { Connection con = null; Statement st = null; @@ -223,9 +223,9 @@ public class MySQL implements DataSource { /** * Method isAuthAvailable. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) */ @Override public synchronized boolean isAuthAvailable(String user) { Connection con = null; @@ -251,9 +251,9 @@ public class MySQL implements DataSource { /** * Method getAuth. * @param user String - * @return PlayerAuth - * @see fr.xephi.authme.datasource.DataSource#getAuth(String) - */ + + + * @return PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getAuth(String) */ @Override public synchronized PlayerAuth getAuth(String user) { Connection con = null; @@ -310,9 +310,9 @@ public class MySQL implements DataSource { /** * Method saveAuth. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) */ @Override public synchronized boolean saveAuth(PlayerAuth auth) { Connection con = null; @@ -523,9 +523,9 @@ public class MySQL implements DataSource { /** * Method updatePassword. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) */ @Override public synchronized boolean updatePassword(PlayerAuth auth) { Connection con = null; @@ -574,9 +574,9 @@ public class MySQL implements DataSource { /** * Method updateSession. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) */ @Override public synchronized boolean updateSession(PlayerAuth auth) { Connection con = null; @@ -603,9 +603,9 @@ public class MySQL implements DataSource { /** * Method purgeDatabase. * @param until long - * @return int - * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) */ @Override public synchronized int purgeDatabase(long until) { Connection con = null; @@ -628,9 +628,9 @@ public class MySQL implements DataSource { /** * Method autoPurgeDatabase. * @param until long - * @return List - * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) */ @Override public synchronized List autoPurgeDatabase(long until) { Connection con = null; @@ -664,9 +664,9 @@ public class MySQL implements DataSource { /** * Method removeAuth. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) */ @Override public synchronized boolean removeAuth(String user) { Connection con = null; @@ -707,9 +707,9 @@ public class MySQL implements DataSource { /** * Method updateQuitLoc. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) */ @Override public synchronized boolean updateQuitLoc(PlayerAuth auth) { Connection con = null; @@ -737,9 +737,9 @@ public class MySQL implements DataSource { /** * Method getIps. * @param ip String - * @return int - * @see fr.xephi.authme.datasource.DataSource#getIps(String) - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#getIps(String) */ @Override public synchronized int getIps(String ip) { Connection con = null; @@ -769,9 +769,9 @@ public class MySQL implements DataSource { /** * Method updateEmail. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) */ @Override public synchronized boolean updateEmail(PlayerAuth auth) { Connection con = null; @@ -797,9 +797,9 @@ public class MySQL implements DataSource { /** * Method updateSalt. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) */ @Override public synchronized boolean updateSalt(PlayerAuth auth) { if (columnSalt.isEmpty()) { @@ -871,9 +871,9 @@ public class MySQL implements DataSource { /** * Method getAllAuthsByName. * @param auth PlayerAuth - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) */ @Override public synchronized List getAllAuthsByName(PlayerAuth auth) { Connection con = null; @@ -903,9 +903,9 @@ public class MySQL implements DataSource { /** * Method getAllAuthsByIp. * @param ip String - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) */ @Override public synchronized List getAllAuthsByIp(String ip) { Connection con = null; @@ -935,10 +935,10 @@ public class MySQL implements DataSource { /** * Method getAllAuthsByEmail. * @param email String - * @return List - * @throws SQLException - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) - */ + + + + * @return List * @throws SQLException * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) */ @Override public synchronized List getAllAuthsByEmail(String email) throws SQLException { final Connection con = getConnection(); @@ -964,8 +964,8 @@ public class MySQL implements DataSource { /** * Method purgeBanned. * @param banned List - * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) - */ + + * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) */ @Override public synchronized void purgeBanned(List banned) { Connection con = null; @@ -988,9 +988,9 @@ public class MySQL implements DataSource { /** * Method getType. - * @return DataSourceType - * @see fr.xephi.authme.datasource.DataSource#getType() - */ + + + * @return DataSourceType * @see fr.xephi.authme.datasource.DataSource#getType() */ @Override public DataSourceType getType() { return DataSourceType.MYSQL; @@ -999,9 +999,9 @@ public class MySQL implements DataSource { /** * Method isLogged. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#isLogged(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#isLogged(String) */ @Override public boolean isLogged(String user) { Connection con = null; @@ -1029,8 +1029,8 @@ public class MySQL implements DataSource { /** * Method setLogged. * @param user String - * @see fr.xephi.authme.datasource.DataSource#setLogged(String) - */ + + * @see fr.xephi.authme.datasource.DataSource#setLogged(String) */ @Override public void setLogged(String user) { Connection con = null; @@ -1053,8 +1053,8 @@ public class MySQL implements DataSource { /** * Method setUnlogged. * @param user String - * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) - */ + + * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) */ @Override public void setUnlogged(String user) { Connection con = null; @@ -1100,9 +1100,9 @@ public class MySQL implements DataSource { /** * Method getAccountsRegistered. - * @return int - * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() */ @Override public int getAccountsRegistered() { int result = 0; @@ -1131,8 +1131,8 @@ public class MySQL implements DataSource { * Method updateName. * @param oldone String * @param newone String - * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) - */ + + * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) */ @Override public void updateName(String oldone, String newone) { Connection con = null; @@ -1154,9 +1154,9 @@ public class MySQL implements DataSource { /** * Method getAllAuths. - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuths() - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuths() */ @Override public List getAllAuths() { List auths = new ArrayList<>(); @@ -1210,9 +1210,9 @@ public class MySQL implements DataSource { /** * Method getLoggedPlayers. - * @return List - * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() */ @Override public List getLoggedPlayers() { List auths = new ArrayList<>(); diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java index 6b5cae2b..5b102bdf 100644 --- a/src/main/java/fr/xephi/authme/datasource/SQLite.java +++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java @@ -37,9 +37,9 @@ public class SQLite implements DataSource { /** * Constructor for SQLite. - * @throws ClassNotFoundException - * @throws SQLException - */ + + + * @throws ClassNotFoundException * @throws SQLException */ public SQLite() throws ClassNotFoundException, SQLException { this.database = Settings.getMySQLDatabase; this.tableName = Settings.getMySQLTablename; @@ -69,9 +69,9 @@ public class SQLite implements DataSource { /** * Method connect. - * @throws ClassNotFoundException - * @throws SQLException - */ + + + * @throws ClassNotFoundException * @throws SQLException */ private synchronized void connect() throws ClassNotFoundException, SQLException { Class.forName("org.sqlite.JDBC"); ConsoleLogger.info("SQLite driver loaded"); @@ -81,8 +81,8 @@ public class SQLite implements DataSource { /** * Method setup. - * @throws SQLException - */ + + * @throws SQLException */ private synchronized void setup() throws SQLException { Statement st = null; ResultSet rs = null; @@ -140,9 +140,9 @@ public class SQLite implements DataSource { /** * Method isAuthAvailable. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) */ @Override public synchronized boolean isAuthAvailable(String user) { PreparedStatement pst = null; @@ -164,9 +164,9 @@ public class SQLite implements DataSource { /** * Method getAuth. * @param user String - * @return PlayerAuth - * @see fr.xephi.authme.datasource.DataSource#getAuth(String) - */ + + + * @return PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getAuth(String) */ @Override public synchronized PlayerAuth getAuth(String user) { PreparedStatement pst = null; @@ -200,9 +200,9 @@ public class SQLite implements DataSource { /** * Method saveAuth. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) */ @Override public synchronized boolean saveAuth(PlayerAuth auth) { PreparedStatement pst = null; @@ -237,9 +237,9 @@ public class SQLite implements DataSource { /** * Method updatePassword. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) */ @Override public synchronized boolean updatePassword(PlayerAuth auth) { PreparedStatement pst = null; @@ -260,9 +260,9 @@ public class SQLite implements DataSource { /** * Method updateSession. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) */ @Override public boolean updateSession(PlayerAuth auth) { PreparedStatement pst = null; @@ -285,9 +285,9 @@ public class SQLite implements DataSource { /** * Method purgeDatabase. * @param until long - * @return int - * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) */ @Override public int purgeDatabase(long until) { PreparedStatement pst = null; @@ -307,9 +307,9 @@ public class SQLite implements DataSource { /** * Method autoPurgeDatabase. * @param until long - * @return List - * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) */ @Override public List autoPurgeDatabase(long until) { PreparedStatement pst = null; @@ -335,9 +335,9 @@ public class SQLite implements DataSource { /** * Method removeAuth. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) */ @Override public synchronized boolean removeAuth(String user) { PreparedStatement pst = null; @@ -357,9 +357,9 @@ public class SQLite implements DataSource { /** * Method updateQuitLoc. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) */ @Override public boolean updateQuitLoc(PlayerAuth auth) { PreparedStatement pst = null; @@ -383,9 +383,9 @@ public class SQLite implements DataSource { /** * Method getIps. * @param ip String - * @return int - * @see fr.xephi.authme.datasource.DataSource#getIps(String) - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#getIps(String) */ @Override public int getIps(String ip) { PreparedStatement pst = null; @@ -411,9 +411,9 @@ public class SQLite implements DataSource { /** * Method updateEmail. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) */ @Override public boolean updateEmail(PlayerAuth auth) { PreparedStatement pst = null; @@ -434,9 +434,9 @@ public class SQLite implements DataSource { /** * Method updateSalt. * @param auth PlayerAuth - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) */ @Override public boolean updateSalt(PlayerAuth auth) { if (columnSalt.isEmpty()) { @@ -509,9 +509,9 @@ public class SQLite implements DataSource { /** * Method getAllAuthsByName. * @param auth PlayerAuth - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) */ @Override public List getAllAuthsByName(PlayerAuth auth) { PreparedStatement pst = null; @@ -539,9 +539,9 @@ public class SQLite implements DataSource { /** * Method getAllAuthsByIp. * @param ip String - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) */ @Override public List getAllAuthsByIp(String ip) { PreparedStatement pst = null; @@ -569,9 +569,9 @@ public class SQLite implements DataSource { /** * Method getAllAuthsByEmail. * @param email String - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) */ @Override public List getAllAuthsByEmail(String email) { PreparedStatement pst = null; @@ -599,8 +599,8 @@ public class SQLite implements DataSource { /** * Method purgeBanned. * @param banned List - * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) - */ + + * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) */ @Override public void purgeBanned(List banned) { PreparedStatement pst = null; @@ -619,9 +619,9 @@ public class SQLite implements DataSource { /** * Method getType. - * @return DataSourceType - * @see fr.xephi.authme.datasource.DataSource#getType() - */ + + + * @return DataSourceType * @see fr.xephi.authme.datasource.DataSource#getType() */ @Override public DataSourceType getType() { return DataSourceType.SQLITE; @@ -630,9 +630,9 @@ public class SQLite implements DataSource { /** * Method isLogged. * @param user String - * @return boolean - * @see fr.xephi.authme.datasource.DataSource#isLogged(String) - */ + + + * @return boolean * @see fr.xephi.authme.datasource.DataSource#isLogged(String) */ @Override public boolean isLogged(String user) { PreparedStatement pst = null; @@ -656,8 +656,8 @@ public class SQLite implements DataSource { /** * Method setLogged. * @param user String - * @see fr.xephi.authme.datasource.DataSource#setLogged(String) - */ + + * @see fr.xephi.authme.datasource.DataSource#setLogged(String) */ @Override public void setLogged(String user) { PreparedStatement pst = null; @@ -676,8 +676,8 @@ public class SQLite implements DataSource { /** * Method setUnlogged. * @param user String - * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) - */ + + * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) */ @Override public void setUnlogged(String user) { PreparedStatement pst = null; @@ -715,9 +715,9 @@ public class SQLite implements DataSource { /** * Method getAccountsRegistered. - * @return int - * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() - */ + + + * @return int * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() */ @Override public int getAccountsRegistered() { int result = 0; @@ -742,8 +742,8 @@ public class SQLite implements DataSource { * Method updateName. * @param oldone String * @param newone String - * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) - */ + + * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) */ @Override public void updateName(String oldone, String newone) { PreparedStatement pst = null; @@ -761,9 +761,9 @@ public class SQLite implements DataSource { /** * Method getAllAuths. - * @return List - * @see fr.xephi.authme.datasource.DataSource#getAllAuths() - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuths() */ @Override public List getAllAuths() { List auths = new ArrayList<>(); @@ -796,9 +796,9 @@ public class SQLite implements DataSource { /** * Method getLoggedPlayers. - * @return List - * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() - */ + + + * @return List * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() */ @Override public List getLoggedPlayers() { List auths = new ArrayList<>(); diff --git a/src/main/java/fr/xephi/authme/events/AuthMeAsyncPreLoginEvent.java b/src/main/java/fr/xephi/authme/events/AuthMeAsyncPreLoginEvent.java index 13258bae..a0c5cb0b 100644 --- a/src/main/java/fr/xephi/authme/events/AuthMeAsyncPreLoginEvent.java +++ b/src/main/java/fr/xephi/authme/events/AuthMeAsyncPreLoginEvent.java @@ -28,16 +28,16 @@ public class AuthMeAsyncPreLoginEvent extends Event { /** * Method getPlayer. - * @return Player - */ + + * @return Player */ public Player getPlayer() { return player; } /** * Method canLogin. - * @return boolean - */ + + * @return boolean */ public boolean canLogin() { return canLogin; } @@ -52,8 +52,8 @@ public class AuthMeAsyncPreLoginEvent extends Event { /** * Method getHandlers. - * @return HandlerList - */ + + * @return HandlerList */ @Override public HandlerList getHandlers() { return handlers; diff --git a/src/main/java/fr/xephi/authme/events/AuthMeTeleportEvent.java b/src/main/java/fr/xephi/authme/events/AuthMeTeleportEvent.java index a5d21368..9bd901c0 100644 --- a/src/main/java/fr/xephi/authme/events/AuthMeTeleportEvent.java +++ b/src/main/java/fr/xephi/authme/events/AuthMeTeleportEvent.java @@ -29,8 +29,8 @@ public class AuthMeTeleportEvent extends CustomEvent { /** * Method getPlayer. - * @return Player - */ + + * @return Player */ public Player getPlayer() { return player; } @@ -45,16 +45,16 @@ public class AuthMeTeleportEvent extends CustomEvent { /** * Method getTo. - * @return Location - */ + + * @return Location */ public Location getTo() { return to; } /** * Method getFrom. - * @return Location - */ + + * @return Location */ public Location getFrom() { return from; } diff --git a/src/main/java/fr/xephi/authme/events/CustomEvent.java b/src/main/java/fr/xephi/authme/events/CustomEvent.java index bdd4daf5..221bba63 100644 --- a/src/main/java/fr/xephi/authme/events/CustomEvent.java +++ b/src/main/java/fr/xephi/authme/events/CustomEvent.java @@ -28,25 +28,25 @@ public class CustomEvent extends Event implements Cancellable { /** * Method getHandlers. - * @return HandlerList - */ + + * @return HandlerList */ public HandlerList getHandlers() { return handlers; } /** * Method getHandlerList. - * @return HandlerList - */ + + * @return HandlerList */ public static HandlerList getHandlerList() { return handlers; } /** * Method isCancelled. - * @return boolean - * @see org.bukkit.event.Cancellable#isCancelled() - */ + + + * @return boolean * @see org.bukkit.event.Cancellable#isCancelled() */ public boolean isCancelled() { return this.isCancelled; } @@ -54,8 +54,8 @@ public class CustomEvent extends Event implements Cancellable { /** * Method setCancelled. * @param cancelled boolean - * @see org.bukkit.event.Cancellable#setCancelled(boolean) - */ + + * @see org.bukkit.event.Cancellable#setCancelled(boolean) */ public void setCancelled(boolean cancelled) { this.isCancelled = cancelled; } diff --git a/src/main/java/fr/xephi/authme/events/FirstSpawnTeleportEvent.java b/src/main/java/fr/xephi/authme/events/FirstSpawnTeleportEvent.java index f89ca9d9..29e24e34 100644 --- a/src/main/java/fr/xephi/authme/events/FirstSpawnTeleportEvent.java +++ b/src/main/java/fr/xephi/authme/events/FirstSpawnTeleportEvent.java @@ -31,8 +31,8 @@ public class FirstSpawnTeleportEvent extends CustomEvent { /** * Method getPlayer. - * @return Player - */ + + * @return Player */ public Player getPlayer() { return player; } @@ -47,16 +47,16 @@ public class FirstSpawnTeleportEvent extends CustomEvent { /** * Method getTo. - * @return Location - */ + + * @return Location */ public Location getTo() { return to; } /** * Method getFrom. - * @return Location - */ + + * @return Location */ public Location getFrom() { return from; } diff --git a/src/main/java/fr/xephi/authme/events/LoginEvent.java b/src/main/java/fr/xephi/authme/events/LoginEvent.java index 89126b91..9c661d20 100644 --- a/src/main/java/fr/xephi/authme/events/LoginEvent.java +++ b/src/main/java/fr/xephi/authme/events/LoginEvent.java @@ -30,8 +30,8 @@ public class LoginEvent extends Event { /** * Method getPlayer. - * @return Player - */ + + * @return Player */ public Player getPlayer() { return this.player; } @@ -55,16 +55,16 @@ public class LoginEvent extends Event { /** * Method isLogin. - * @return boolean - */ + + * @return boolean */ public boolean isLogin() { return isLogin; } /** * Method getHandlers. - * @return HandlerList - */ + + * @return HandlerList */ @Override public HandlerList getHandlers() { return handlers; @@ -72,8 +72,8 @@ public class LoginEvent extends Event { /** * Method getHandlerList. - * @return HandlerList - */ + + * @return HandlerList */ public static HandlerList getHandlerList() { return handlers; } diff --git a/src/main/java/fr/xephi/authme/events/LogoutEvent.java b/src/main/java/fr/xephi/authme/events/LogoutEvent.java index d5762006..4c4254dd 100644 --- a/src/main/java/fr/xephi/authme/events/LogoutEvent.java +++ b/src/main/java/fr/xephi/authme/events/LogoutEvent.java @@ -26,8 +26,8 @@ public class LogoutEvent extends Event { /** * Method getPlayer. - * @return Player - */ + + * @return Player */ public Player getPlayer() { return this.player; } @@ -42,8 +42,8 @@ public class LogoutEvent extends Event { /** * Method getHandlers. - * @return HandlerList - */ + + * @return HandlerList */ @Override public HandlerList getHandlers() { return handlers; @@ -51,8 +51,8 @@ public class LogoutEvent extends Event { /** * Method getHandlerList. - * @return HandlerList - */ + + * @return HandlerList */ public static HandlerList getHandlerList() { return handlers; } diff --git a/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java b/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java index d4838cb2..a2c2a306 100644 --- a/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java +++ b/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java @@ -35,8 +35,8 @@ public class PasswordEncryptionEvent extends Event { /** * Method getHandlers. - * @return HandlerList - */ + + * @return HandlerList */ @Override public HandlerList getHandlers() { return handlers; @@ -52,24 +52,24 @@ public class PasswordEncryptionEvent extends Event { /** * Method getMethod. - * @return EncryptionMethod - */ + + * @return EncryptionMethod */ public EncryptionMethod getMethod() { return method; } /** * Method getPlayerName. - * @return String - */ + + * @return String */ public String getPlayerName() { return playerName; } /** * Method getHandlerList. - * @return HandlerList - */ + + * @return HandlerList */ public static HandlerList getHandlerList() { return handlers; } diff --git a/src/main/java/fr/xephi/authme/events/ProtectInventoryEvent.java b/src/main/java/fr/xephi/authme/events/ProtectInventoryEvent.java index 461155a7..937c3f75 100644 --- a/src/main/java/fr/xephi/authme/events/ProtectInventoryEvent.java +++ b/src/main/java/fr/xephi/authme/events/ProtectInventoryEvent.java @@ -34,24 +34,24 @@ public class ProtectInventoryEvent extends CustomEvent { /** * Method getStoredInventory. - * @return ItemStack[] - */ + + * @return ItemStack[] */ public ItemStack[] getStoredInventory() { return this.storedinventory; } /** * Method getStoredArmor. - * @return ItemStack[] - */ + + * @return ItemStack[] */ public ItemStack[] getStoredArmor() { return this.storedarmor; } /** * Method getPlayer. - * @return Player - */ + + * @return Player */ public Player getPlayer() { return this.player; } @@ -66,8 +66,8 @@ public class ProtectInventoryEvent extends CustomEvent { /** * Method getEmptyInventory. - * @return ItemStack[] - */ + + * @return ItemStack[] */ public ItemStack[] getEmptyInventory() { return this.emptyInventory; } @@ -82,8 +82,8 @@ public class ProtectInventoryEvent extends CustomEvent { /** * Method getEmptyArmor. - * @return ItemStack[] - */ + + * @return ItemStack[] */ public ItemStack[] getEmptyArmor() { return this.emptyArmor; } diff --git a/src/main/java/fr/xephi/authme/events/RegisterTeleportEvent.java b/src/main/java/fr/xephi/authme/events/RegisterTeleportEvent.java index d5dea8ff..c7678e40 100644 --- a/src/main/java/fr/xephi/authme/events/RegisterTeleportEvent.java +++ b/src/main/java/fr/xephi/authme/events/RegisterTeleportEvent.java @@ -30,8 +30,8 @@ public class RegisterTeleportEvent extends CustomEvent { /** * Method getPlayer. - * @return Player - */ + + * @return Player */ public Player getPlayer() { return player; } @@ -46,16 +46,16 @@ public class RegisterTeleportEvent extends CustomEvent { /** * Method getTo. - * @return Location - */ + + * @return Location */ public Location getTo() { return to; } /** * Method getFrom. - * @return Location - */ + + * @return Location */ public Location getFrom() { return from; } diff --git a/src/main/java/fr/xephi/authme/events/ResetInventoryEvent.java b/src/main/java/fr/xephi/authme/events/ResetInventoryEvent.java index 736fbe66..af5c1ce0 100644 --- a/src/main/java/fr/xephi/authme/events/ResetInventoryEvent.java +++ b/src/main/java/fr/xephi/authme/events/ResetInventoryEvent.java @@ -24,8 +24,8 @@ public class ResetInventoryEvent extends CustomEvent { /** * Method getPlayer. - * @return Player - */ + + * @return Player */ public Player getPlayer() { return this.player; } diff --git a/src/main/java/fr/xephi/authme/events/RestoreInventoryEvent.java b/src/main/java/fr/xephi/authme/events/RestoreInventoryEvent.java index fb3f05a5..de2dcb17 100644 --- a/src/main/java/fr/xephi/authme/events/RestoreInventoryEvent.java +++ b/src/main/java/fr/xephi/authme/events/RestoreInventoryEvent.java @@ -32,8 +32,8 @@ public class RestoreInventoryEvent extends CustomEvent { /** * Method getPlayer. - * @return Player - */ + + * @return Player */ public Player getPlayer() { return this.player; } diff --git a/src/main/java/fr/xephi/authme/events/SpawnTeleportEvent.java b/src/main/java/fr/xephi/authme/events/SpawnTeleportEvent.java index 1aafdd65..d2379648 100644 --- a/src/main/java/fr/xephi/authme/events/SpawnTeleportEvent.java +++ b/src/main/java/fr/xephi/authme/events/SpawnTeleportEvent.java @@ -34,8 +34,8 @@ public class SpawnTeleportEvent extends CustomEvent { /** * Method getPlayer. - * @return Player - */ + + * @return Player */ public Player getPlayer() { return player; } @@ -50,24 +50,24 @@ public class SpawnTeleportEvent extends CustomEvent { /** * Method getTo. - * @return Location - */ + + * @return Location */ public Location getTo() { return to; } /** * Method getFrom. - * @return Location - */ + + * @return Location */ public Location getFrom() { return from; } /** * Method isAuthenticated. - * @return boolean - */ + + * @return boolean */ public boolean isAuthenticated() { return isAuthenticated; } diff --git a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java index 337e5b97..635f0277 100644 --- a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java +++ b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java @@ -27,8 +27,8 @@ public class BungeeCordMessage implements PluginMessageListener { * @param channel String * @param player Player * @param message byte[] - * @see org.bukkit.plugin.messaging.PluginMessageListener#onPluginMessageReceived(String, Player, byte[]) - */ + + * @see org.bukkit.plugin.messaging.PluginMessageListener#onPluginMessageReceived(String, Player, byte[]) */ @Override public void onPluginMessageReceived(String channel, Player player, byte[] message) { diff --git a/src/main/java/fr/xephi/authme/hooks/EssSpawn.java b/src/main/java/fr/xephi/authme/hooks/EssSpawn.java index 5e37b5eb..b13d8050 100644 --- a/src/main/java/fr/xephi/authme/hooks/EssSpawn.java +++ b/src/main/java/fr/xephi/authme/hooks/EssSpawn.java @@ -21,8 +21,8 @@ public class EssSpawn extends CustomConfiguration { /** * Method getInstance. - * @return EssSpawn - */ + + * @return EssSpawn */ public static EssSpawn getInstance() { if (spawn == null) { spawn = new EssSpawn(); @@ -32,8 +32,8 @@ public class EssSpawn extends CustomConfiguration { /** * Method getLocation. - * @return Location - */ + + * @return Location */ public Location getLocation() { try { if (!this.contains("spawns.default.world")) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java b/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java index b11501f3..affc050d 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java @@ -57,8 +57,8 @@ public class AuthMeInventoryPacketAdapter extends PacketAdapter { /** * Method onPacketSending. * @param packetEvent PacketEvent - * @see com.comphenix.protocol.events.PacketListener#onPacketSending(PacketEvent) - */ + + * @see com.comphenix.protocol.events.PacketListener#onPacketSending(PacketEvent) */ @Override public void onPacketSending(PacketEvent packetEvent) { Player player = packetEvent.getPlayer(); diff --git a/src/main/java/fr/xephi/authme/modules/Module.java b/src/main/java/fr/xephi/authme/modules/Module.java index 59a2bc4c..f6263ad2 100644 --- a/src/main/java/fr/xephi/authme/modules/Module.java +++ b/src/main/java/fr/xephi/authme/modules/Module.java @@ -18,14 +18,14 @@ public abstract class Module { /** * Method getName. - * @return String - */ + + * @return String */ public abstract String getName(); /** * Method getType. - * @return ModuleType - */ + + * @return ModuleType */ public abstract ModuleType getType(); public void load() { diff --git a/src/main/java/fr/xephi/authme/modules/ModuleManager.java b/src/main/java/fr/xephi/authme/modules/ModuleManager.java index 38e6c129..e83710f8 100644 --- a/src/main/java/fr/xephi/authme/modules/ModuleManager.java +++ b/src/main/java/fr/xephi/authme/modules/ModuleManager.java @@ -31,8 +31,8 @@ public class ModuleManager { /** * Method isModuleEnabled. * @param name String - * @return boolean - */ + + * @return boolean */ public boolean isModuleEnabled(String name) { for (Module m : modules) { if (m.getName().equalsIgnoreCase(name)) @@ -44,8 +44,8 @@ public class ModuleManager { /** * Method isModuleEnabled. * @param type Module.ModuleType - * @return boolean - */ + + * @return boolean */ public boolean isModuleEnabled(Module.ModuleType type) { for (Module m : modules) { if (m.getType() == type) @@ -57,8 +57,8 @@ public class ModuleManager { /** * Method getModule. * @param name String - * @return Module - */ + + * @return Module */ public Module getModule(String name) { for (Module m : modules) { if (m.getName().equalsIgnoreCase(name)) @@ -70,8 +70,8 @@ public class ModuleManager { /** * Method getModule. * @param type Module.ModuleType - * @return Module - */ + + * @return Module */ public Module getModule(Module.ModuleType type) { for (Module m : modules) { if (m.getType() == type) @@ -82,8 +82,8 @@ public class ModuleManager { /** * Method loadModules. - * @return int - */ + + * @return int */ public int loadModules() { File dir = Settings.MODULE_FOLDER; int count = 0; diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 908f4823..7f1b4f9c 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -91,7 +91,7 @@ public class PermissionsManager { * Check if the permissions manager is currently hooked into any of the supported permissions systems. * d. - * @return False if there isn't any permissions system used. */ + * @return False if there isn't any permissions system used. */ public boolean isEnabled() { return !permsType.equals(PermissionsSystemType.NONE); } @@ -100,7 +100,7 @@ public class PermissionsManager { * Return the permissions system where the permissions manager is currently hooked into. * e. - * @return Permissions system type. */ + * @return Permissions system type. */ public PermissionsSystemType getUsedPermissionsSystemType() { return this.permsType; } @@ -109,7 +109,7 @@ public class PermissionsManager { * Setup and hook into the permissions systems. * m. - * @return The detected permissions system. */ + * @return The detected permissions system. */ public PermissionsSystemType setup() { // Define the plugin manager final PluginManager pm = this.server.getPluginManager(); @@ -246,7 +246,7 @@ public class PermissionsManager { * Reload the permissions manager, and re-hook all permission plugins. * e. - * @return True on success, false on failure. */ + * @return True on success, false on failure. */ public boolean reload() { // Unhook all permission plugins unhook(); @@ -300,7 +300,7 @@ public class PermissionsManager { * Get the logger instance. * e. - * @return Logger instance. */ + * @return Logger instance. */ public Logger getLogger() { return this.log; } @@ -321,7 +321,7 @@ public class PermissionsManager { * @param permsNode Permissions node. * n. - * @return True if the player has permission. */ + * @return True if the player has permission. */ public boolean hasPermission(Player player, String permsNode) { return hasPermission(player, permsNode, player.isOp()); } @@ -334,7 +334,7 @@ public class PermissionsManager { * @param def Default returned if no permissions system is used. * n. - * @return True if the player has permission. */ + * @return True if the player has permission. */ public boolean hasPermission(Player player, String permsNode, boolean def) { if(!isEnabled()) // No permissions system is used, return default @@ -389,8 +389,8 @@ public class PermissionsManager { /** * Method getGroups. * @param player Player - * @return List - */ + g> + * @return List */ @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public List getGroups(Player player) { if(!isEnabled()) @@ -461,8 +461,8 @@ public class PermissionsManager { /** * Method getName. - * @return String - */ + ng + * @return String */ public String getName() { return this.name; } diff --git a/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java index aeae0f14..87faa3d2 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java @@ -255,8 +255,8 @@ public class AsyncronousJoin { /** * Method needFirstSpawn. - * @return boolean - */ + + * @return boolean */ private boolean needFirstSpawn() { if (player.hasPlayedBefore()) return false; diff --git a/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java index 2181a381..e6439d79 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java @@ -57,16 +57,16 @@ public class AsyncronousLogin { /** * Method getIP. - * @return String - */ + + * @return String */ protected String getIP() { return plugin.getIP(player); } /** * Method needsCaptcha. - * @return boolean - */ + + * @return boolean */ protected boolean needsCaptcha() { if (Settings.useCaptcha) { if (!plugin.captcha.containsKey(name)) { @@ -93,8 +93,8 @@ public class AsyncronousLogin { /** * Checks the precondition for authentication (like user known) and returns * the playerAuth-State - * @return PlayerAuth - */ + + * @return PlayerAuth */ protected PlayerAuth preAuth() { if (PlayerCache.getInstance().isAuthenticated(name)) { m.send(player, "logged_in"); diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java index 953459c2..2b63d4ff 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java @@ -54,8 +54,8 @@ public class ProcessSyncronousPlayerLogin implements Runnable { /** * Method getLimbo. - * @return LimboPlayer - */ + + * @return LimboPlayer */ public LimboPlayer getLimbo() { return limbo; } diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index 12b26327..15574f6c 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -46,17 +46,17 @@ public class AsyncRegister { /** * Method getIp. - * @return String - */ + + * @return String */ protected String getIp() { return plugin.getIP(player); } /** * Method preRegisterCheck. - * @return boolean - * @throws Exception - */ + + + * @return boolean * @throws Exception */ protected boolean preRegisterCheck() throws Exception { String lowpass = password.toLowerCase(); if (PlayerCache.getInstance().isAuthenticated(name)) { @@ -113,8 +113,8 @@ public class AsyncRegister { /** * Method emailRegister. - * @throws Exception - */ + + * @throws Exception */ protected void emailRegister() throws Exception { if (Settings.getmaxRegPerEmail > 0) { if (!plugin.getPermissionsManager().hasPermission(player, "authme.allow2accounts") && database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) { diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsyncronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsyncronousUnregister.java index 0b9f3a48..80c34835 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsyncronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsyncronousUnregister.java @@ -52,8 +52,8 @@ public class AsyncronousUnregister { /** * Method getIp. - * @return String - */ + + * @return String */ protected String getIp() { return plugin.getIP(player); } diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index 5ce86359..db3fb202 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -47,8 +47,8 @@ public enum HashAlgorithm { /** * Method getclasse. - * @return Class - */ + + * @return Class */ public Class getclasse() { return classe; } diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 1a0013d0..1bca5e18 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -25,9 +25,9 @@ public class PasswordSecurity { /** * Method createSalt. * @param length int - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ public static String createSalt(int length) throws NoSuchAlgorithmException { byte[] msg = new byte[40]; @@ -43,9 +43,9 @@ public class PasswordSecurity { * @param alg HashAlgorithm * @param password String * @param playerName String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ public static String getHash(HashAlgorithm alg, String password, String playerName) throws NoSuchAlgorithmException { EncryptionMethod method; @@ -146,9 +146,9 @@ public class PasswordSecurity { * @param password String * @param hash String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - */ + + + * @return boolean * @throws NoSuchAlgorithmException */ public static boolean comparePasswordWithHash(String password, String hash, String playerName) throws NoSuchAlgorithmException { HashAlgorithm algo = Settings.getPasswordHash; @@ -184,9 +184,9 @@ public class PasswordSecurity { * @param password String * @param hash String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - */ + + + * @return boolean * @throws NoSuchAlgorithmException */ private static boolean compareWithAllEncryptionMethod(String password, String hash, String playerName) throws NoSuchAlgorithmException { for (HashAlgorithm algo : HashAlgorithm.values()) { diff --git a/src/main/java/fr/xephi/authme/security/RandomString.java b/src/main/java/fr/xephi/authme/security/RandomString.java index 86b7683a..fec71b71 100644 --- a/src/main/java/fr/xephi/authme/security/RandomString.java +++ b/src/main/java/fr/xephi/authme/security/RandomString.java @@ -36,8 +36,8 @@ public class RandomString { /** * Method nextString. - * @return String - */ + + * @return String */ public String nextString() { for (int idx = 0; idx < buf.length; ++idx) buf[idx] = chars[random.nextInt(chars.length)]; diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java index 7e7a2f77..0eb79b33 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -94,7 +94,8 @@ public class BCRYPT implements EncryptionMethod { * @param len the number of bytes to encode - * @return base64-encoded string * @throws IllegalArgumentException if the length is invalid */ + * @return base64-encoded string * @throws IllegalArgumentException if the length is invalid * @throws IllegalArgumentException + */ private static String encode_base64(byte d[], int len) throws IllegalArgumentException { int off = 0; @@ -150,7 +151,8 @@ public class BCRYPT implements EncryptionMethod { * @param maxolen the maximum number of bytes to decode - * @return an array containing the decoded bytes * @throws IllegalArgumentException if maxolen is invalid */ + * @return an array containing the decoded bytes * @throws IllegalArgumentException if maxolen is invalid * @throws IllegalArgumentException + */ private static byte[] decode_base64(String s, int maxolen) throws IllegalArgumentException { StringBuffer rs = new StringBuffer(); @@ -474,8 +476,8 @@ public class BCRYPT implements EncryptionMethod { * @param hashed the previously-hashed password * @param rounds number of rounds to hash the password - * @return boolean - */ + + * @return boolean */ public static boolean checkpw(String text, String hashed, int rounds) { boolean matched = false; @@ -499,10 +501,10 @@ public class BCRYPT implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -514,10 +516,10 @@ public class BCRYPT implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -528,8 +530,8 @@ public class BCRYPT implements EncryptionMethod { * Method getDoubleHash. * @param text String * @param salt String - * @return String - */ + + * @return String */ public static String getDoubleHash(String text, String salt) { String hash = hashpw(text, salt); return hashpw(text, hash); diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java index 9cbe12fb..ddee2cd8 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java @@ -11,10 +11,10 @@ public class BCRYPT2Y implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -28,10 +28,10 @@ public class BCRYPT2Y implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java index 174531a8..86e640c6 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java @@ -16,10 +16,10 @@ public class CRAZYCRYPT1 implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -36,10 +36,7 @@ public class CRAZYCRYPT1 implements EncryptionMethod { * Method comparePassword. * @param hash String * @param password String - * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + * @param playerName Stringooleaneptiontring) * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override @@ -49,8 +46,7 @@ public class CRAZYCRYPT1 implements EncryptionMethod { } /** * Method byteArrayToHexString. - * @param args byte[] - * @return String + * @param args byte[]String * @return String */ public static String byteArrayToHexString(final byte... args) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java index c687dff3..9b1bc779 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java @@ -14,10 +14,10 @@ public class CryptPBKDF2 implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -33,10 +33,10 @@ public class CryptPBKDF2 implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java index 6d160c53..7c70909d 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java @@ -16,10 +16,10 @@ public class CryptPBKDF2Django implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -35,10 +35,10 @@ public class CryptPBKDF2Django implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java index 7baf131e..7f089be8 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java @@ -13,10 +13,10 @@ public class DOUBLEMD5 implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -28,10 +28,10 @@ public class DOUBLEMD5 implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -41,9 +41,9 @@ public class DOUBLEMD5 implements EncryptionMethod { /** * Method getMD5. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java index d3084562..829c786e 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java @@ -26,7 +26,8 @@ public interface EncryptionMethod { * @param name String - * @return Hashing password * @throws NoSuchAlgorithmException */ + * @return Hashing password * @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException + */ String getHash(String password, String salt, String name) throws NoSuchAlgorithmException; @@ -36,7 +37,8 @@ public interface EncryptionMethod { * @param playerName - * @return true if password match, false else * @throws NoSuchAlgorithmException */ + * @return true if password match, false else * @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException + */ boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException; diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java index 6d2029b7..7d8ca944 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java @@ -15,10 +15,10 @@ public class IPB3 implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -30,10 +30,10 @@ public class IPB3 implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -44,9 +44,9 @@ public class IPB3 implements EncryptionMethod { /** * Method getMD5. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java index 4720213e..f43d15f2 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java +++ b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java @@ -13,10 +13,10 @@ public class JOOMLA implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -28,10 +28,10 @@ public class JOOMLA implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -42,9 +42,9 @@ public class JOOMLA implements EncryptionMethod { /** * Method getMD5. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5.java b/src/main/java/fr/xephi/authme/security/crypts/MD5.java index 543e8a71..7caf5ba3 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5.java @@ -13,10 +13,10 @@ public class MD5 implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -28,10 +28,10 @@ public class MD5 implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -41,9 +41,9 @@ public class MD5 implements EncryptionMethod { /** * Method getMD5. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java index 4c5df204..ecb8a78c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java @@ -13,10 +13,10 @@ public class MD5VB implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -28,10 +28,10 @@ public class MD5VB implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -42,9 +42,9 @@ public class MD5VB implements EncryptionMethod { /** * Method getMD5. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java index 0988683d..02a362c3 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java @@ -15,10 +15,10 @@ public class MYBB implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -30,10 +30,10 @@ public class MYBB implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -44,9 +44,9 @@ public class MYBB implements EncryptionMethod { /** * Method getMD5. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java index c2860caf..ac06e32e 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java @@ -21,8 +21,8 @@ public class PHPBB implements EncryptionMethod { * Method phpbb_hash. * @param password String * @param salt String - * @return String - */ + + * @return String */ public String phpbb_hash(String password, String salt) { String random_state = salt; StringBuilder random = new StringBuilder(); @@ -42,8 +42,8 @@ public class PHPBB implements EncryptionMethod { * Method _hash_gensalt_private. * @param input String * @param itoa64 String - * @return String - */ + + * @return String */ private String _hash_gensalt_private(String input, String itoa64) { return _hash_gensalt_private(input, itoa64, 6); } @@ -53,8 +53,8 @@ public class PHPBB implements EncryptionMethod { * @param input String * @param itoa64 String * @param iteration_count_log2 int - * @return String - */ + + * @return String */ private String _hash_gensalt_private(String input, String itoa64, int iteration_count_log2) { if (iteration_count_log2 < 4 || iteration_count_log2 > 31) { @@ -70,8 +70,8 @@ public class PHPBB implements EncryptionMethod { * Encode hash * @param input String * @param count int - * @return String - */ + + * @return String */ private String _hash_encode64(String input, int count) { StringBuilder output = new StringBuilder(); int i = 0; @@ -97,8 +97,8 @@ public class PHPBB implements EncryptionMethod { * Method _hash_crypt_private. * @param password String * @param setting String - * @return String - */ + + * @return String */ String _hash_crypt_private(String password, String setting) { String output = "*"; if (!setting.substring(0, 3).equals("$H$")) @@ -124,8 +124,8 @@ public class PHPBB implements EncryptionMethod { * Method phpbb_check_hash. * @param password String * @param hash String - * @return boolean - */ + + * @return boolean */ public boolean phpbb_check_hash(String password, String hash) { if (hash.length() == 34) return _hash_crypt_private(password, hash).equals(hash); @@ -135,8 +135,8 @@ public class PHPBB implements EncryptionMethod { /** * Method md5. * @param data String - * @return String - */ + + * @return String */ public static String md5(String data) { try { byte[] bytes = data.getBytes("ISO-8859-1"); @@ -151,8 +151,8 @@ public class PHPBB implements EncryptionMethod { /** * Method hexToInt. * @param ch char - * @return int - */ + + * @return int */ static int hexToInt(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; @@ -165,8 +165,8 @@ public class PHPBB implements EncryptionMethod { /** * Method bytes2hex. * @param bytes byte[] - * @return String - */ + + * @return String */ private static String bytes2hex(byte[] bytes) { StringBuilder r = new StringBuilder(32); for (byte b : bytes) { @@ -181,8 +181,8 @@ public class PHPBB implements EncryptionMethod { /** * Method pack. * @param hex String - * @return String - */ + + * @return String */ static String pack(String hex) { StringBuilder buf = new StringBuilder(); for (int i = 0; i < hex.length(); i += 2) { @@ -199,10 +199,10 @@ public class PHPBB implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -214,10 +214,10 @@ public class PHPBB implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java index 4e9099b2..b9d938a8 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java @@ -20,10 +20,10 @@ public class PHPFUSION implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -56,10 +56,10 @@ public class PHPFUSION implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -70,9 +70,9 @@ public class PHPFUSION implements EncryptionMethod { /** * Method getSHA1. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getSHA1(String message) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java index e67fe9c6..94f4fe5f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java @@ -11,10 +11,10 @@ public class PLAINTEXT implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -26,10 +26,10 @@ public class PLAINTEXT implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java index 8388de74..e9b9e1e9 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java @@ -12,10 +12,10 @@ public class ROYALAUTH implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -28,9 +28,9 @@ public class ROYALAUTH implements EncryptionMethod { * Method hash. * @param password String * @param salt String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ public String hash(String password, String salt) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-512"); @@ -47,10 +47,10 @@ public class ROYALAUTH implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java index 0daecc04..f90f6907 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java @@ -15,10 +15,10 @@ public class SALTED2MD5 implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -30,10 +30,10 @@ public class SALTED2MD5 implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -44,9 +44,9 @@ public class SALTED2MD5 implements EncryptionMethod { /** * Method getMD5. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java index 61eb9cd5..cd2b4aac 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java @@ -15,10 +15,10 @@ public class SALTEDSHA512 implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -30,10 +30,10 @@ public class SALTEDSHA512 implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -44,9 +44,9 @@ public class SALTEDSHA512 implements EncryptionMethod { /** * Method getSHA512. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getSHA512(String message) throws NoSuchAlgorithmException { MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java index 2e7968cc..ae1fab0a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java @@ -13,10 +13,10 @@ public class SHA1 implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -28,10 +28,10 @@ public class SHA1 implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -41,9 +41,9 @@ public class SHA1 implements EncryptionMethod { /** * Method getSHA1. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getSHA1(String message) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java index d969427b..11d53e16 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java @@ -13,10 +13,10 @@ public class SHA256 implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -28,10 +28,10 @@ public class SHA256 implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -42,9 +42,9 @@ public class SHA256 implements EncryptionMethod { /** * Method getSHA256. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getSHA256(String message) throws NoSuchAlgorithmException { MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java index 29c80f17..a44d9367 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java @@ -13,10 +13,10 @@ public class SHA512 implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -28,10 +28,10 @@ public class SHA512 implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -41,9 +41,9 @@ public class SHA512 implements EncryptionMethod { /** * Method getSHA512. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getSHA512(String message) throws NoSuchAlgorithmException { MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/SMF.java index 2099a01d..8ee0cbe0 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SMF.java @@ -13,10 +13,10 @@ public class SMF implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -28,10 +28,10 @@ public class SMF implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -41,9 +41,9 @@ public class SMF implements EncryptionMethod { /** * Method getSHA1. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getSHA1(String message) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java index 462b1cce..4a5cdef2 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java @@ -15,10 +15,10 @@ public class WBB3 implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -30,10 +30,10 @@ public class WBB3 implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -44,9 +44,9 @@ public class WBB3 implements EncryptionMethod { /** * Method getSHA1. * @param message String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ private static String getSHA1(String message) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java index 3072f129..ab43c65a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java @@ -11,10 +11,10 @@ public class WBB4 implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -26,10 +26,10 @@ public class WBB4 implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java index dac8d8ab..f8cf09ee 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java @@ -379,8 +379,8 @@ public class WHIRLPOOL implements EncryptionMethod { /** * Method display. * @param array byte[] - * @return String - */ + + * @return String */ protected static String display(byte[] array) { char[] val = new char[2 * array.length]; String hex = "0123456789ABCDEF"; @@ -397,10 +397,10 @@ public class WHIRLPOOL implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -416,10 +416,10 @@ public class WHIRLPOOL implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java index 110abb57..6c74b0b9 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java @@ -17,8 +17,8 @@ public class WORDPRESS implements EncryptionMethod { * Method encode64. * @param src byte[] * @param count int - * @return String - */ + + * @return String */ private String encode64(byte[] src, int count) { int i, value; StringBuilder output = new StringBuilder(); @@ -57,8 +57,8 @@ public class WORDPRESS implements EncryptionMethod { * Method crypt. * @param password String * @param setting String - * @return String - */ + + * @return String */ private String crypt(String password, String setting) { String output = "*0"; if (((setting.length() < 2) ? setting : setting.substring(0, 2)).equalsIgnoreCase(output)) { @@ -100,8 +100,8 @@ public class WORDPRESS implements EncryptionMethod { /** * Method gensaltPrivate. * @param input byte[] - * @return String - */ + + * @return String */ private String gensaltPrivate(byte[] input) { String output = "$P$"; int iterationCountLog2 = 8; @@ -113,8 +113,8 @@ public class WORDPRESS implements EncryptionMethod { /** * Method stringToUtf8. * @param string String - * @return byte[] - */ + + * @return byte[] */ private byte[] stringToUtf8(String string) { try { return string.getBytes("UTF-8"); @@ -128,10 +128,10 @@ public class WORDPRESS implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -145,10 +145,10 @@ public class WORDPRESS implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java index 650c1644..2254700f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java @@ -11,10 +11,10 @@ public class XAUTH implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -28,10 +28,10 @@ public class XAUTH implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -43,8 +43,8 @@ public class XAUTH implements EncryptionMethod { /** * Method getWhirlpool. * @param message String - * @return String - */ + + * @return String */ public static String getWhirlpool(String message) { WHIRLPOOL w = new WHIRLPOOL(); byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES]; diff --git a/src/main/java/fr/xephi/authme/security/crypts/XF.java b/src/main/java/fr/xephi/authme/security/crypts/XF.java index dcd88686..c8f778db 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XF.java @@ -18,10 +18,10 @@ public class XF implements EncryptionMethod { * @param password String * @param salt String * @param name String - * @return String - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ + + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -33,10 +33,10 @@ public class XF implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - * @return boolean - * @throws NoSuchAlgorithmException - * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ + + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { @@ -47,9 +47,9 @@ public class XF implements EncryptionMethod { /** * Method getSHA256. * @param password String - * @return String - * @throws NoSuchAlgorithmException - */ + + + * @return String * @throws NoSuchAlgorithmException */ public String getSHA256(String password) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(password.getBytes()); @@ -73,8 +73,8 @@ public class XF implements EncryptionMethod { * Method regmatch. * @param pattern String * @param line String - * @return String - */ + + * @return String */ public String regmatch(String pattern, String line) { List allMatches = new ArrayList<>(); Matcher m = Pattern.compile(pattern).matcher(line); diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/MacBasedPRF.java b/src/main/java/fr/xephi/authme/security/pbkdf2/MacBasedPRF.java index e244b748..9a7cc0bc 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/MacBasedPRF.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/MacBasedPRF.java @@ -83,9 +83,9 @@ public class MacBasedPRF implements PRF { /** * Method doFinal. * @param M byte[] - * @return byte[] - * @see fr.xephi.authme.security.pbkdf2.PRF#doFinal(byte[]) - */ + + + * @return byte[] * @see fr.xephi.authme.security.pbkdf2.PRF#doFinal(byte[]) */ public byte[] doFinal(byte[] M) { byte[] r = mac.doFinal(M); return r; @@ -93,9 +93,9 @@ public class MacBasedPRF implements PRF { /** * Method getHLen. - * @return int - * @see fr.xephi.authme.security.pbkdf2.PRF#getHLen() - */ + + + * @return int * @see fr.xephi.authme.security.pbkdf2.PRF#getHLen() */ public int getHLen() { return hLen; } @@ -103,8 +103,8 @@ public class MacBasedPRF implements PRF { /** * Method init. * @param P byte[] - * @see fr.xephi.authme.security.pbkdf2.PRF#init(byte[]) - */ + + * @see fr.xephi.authme.security.pbkdf2.PRF#init(byte[]) */ public void init(byte[] P) { try { mac.init(new SecretKeySpec(P, macAlgorithm)); diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java index ae077dbf..3e124da1 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java @@ -115,9 +115,9 @@ public class PBKDF2Engine implements PBKDF2 { /** * Method deriveKey. * @param inputPassword String - * @return byte[] - * @see fr.xephi.authme.security.pbkdf2.PBKDF2#deriveKey(String) - */ + + + * @return byte[] * @see fr.xephi.authme.security.pbkdf2.PBKDF2#deriveKey(String) */ public byte[] deriveKey(String inputPassword) { return deriveKey(inputPassword, 0); } @@ -126,9 +126,9 @@ public class PBKDF2Engine implements PBKDF2 { * Method deriveKey. * @param inputPassword String * @param dkLen int - * @return byte[] - * @see fr.xephi.authme.security.pbkdf2.PBKDF2#deriveKey(String, int) - */ + + + * @return byte[] * @see fr.xephi.authme.security.pbkdf2.PBKDF2#deriveKey(String, int) */ public byte[] deriveKey(String inputPassword, int dkLen) { byte[] r = null; byte P[] = null; @@ -156,9 +156,9 @@ public class PBKDF2Engine implements PBKDF2 { /** * Method verifyKey. * @param inputPassword String - * @return boolean - * @see fr.xephi.authme.security.pbkdf2.PBKDF2#verifyKey(String) - */ + + + * @return boolean * @see fr.xephi.authme.security.pbkdf2.PBKDF2#verifyKey(String) */ public boolean verifyKey(String inputPassword) { byte[] referenceKey = getParameters().getDerivedKey(); if (referenceKey == null || referenceKey.length == 0) { @@ -193,9 +193,9 @@ public class PBKDF2Engine implements PBKDF2 { /** * Method getPseudoRandomFunction. - * @return PRF - * @see fr.xephi.authme.security.pbkdf2.PBKDF2#getPseudoRandomFunction() - */ + + + * @return PRF * @see fr.xephi.authme.security.pbkdf2.PBKDF2#getPseudoRandomFunction() */ public PRF getPseudoRandomFunction() { return prf; } @@ -318,9 +318,9 @@ public class PBKDF2Engine implements PBKDF2 { /** * Method getParameters. - * @return PBKDF2Parameters - * @see fr.xephi.authme.security.pbkdf2.PBKDF2#getParameters() - */ + + + * @return PBKDF2Parameters * @see fr.xephi.authme.security.pbkdf2.PBKDF2#getParameters() */ public PBKDF2Parameters getParameters() { return parameters; } @@ -328,8 +328,8 @@ public class PBKDF2Engine implements PBKDF2 { /** * Method setParameters. * @param parameters PBKDF2Parameters - * @see fr.xephi.authme.security.pbkdf2.PBKDF2#setParameters(PBKDF2Parameters) - */ + + * @see fr.xephi.authme.security.pbkdf2.PBKDF2#setParameters(PBKDF2Parameters) */ public void setParameters(PBKDF2Parameters parameters) { this.parameters = parameters; } @@ -337,8 +337,8 @@ public class PBKDF2Engine implements PBKDF2 { /** * Method setPseudoRandomFunction. * @param prf PRF - * @see fr.xephi.authme.security.pbkdf2.PBKDF2#setPseudoRandomFunction(PRF) - */ + + * @see fr.xephi.authme.security.pbkdf2.PBKDF2#setPseudoRandomFunction(PRF) */ public void setPseudoRandomFunction(PRF prf) { this.prf = prf; } @@ -357,7 +357,8 @@ public class PBKDF2Engine implements PBKDF2 { * Supply the password as argument. - * @throws IOException * @throws NoSuchAlgorithmException */ + * @throws IOException * @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException + */ public static void main(String[] args) throws IOException, NoSuchAlgorithmException { String password = "password"; diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2HexFormatter.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2HexFormatter.java index 8130c7e9..8ae057fd 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2HexFormatter.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2HexFormatter.java @@ -37,9 +37,9 @@ public class PBKDF2HexFormatter implements PBKDF2Formatter { * Method fromString. * @param p PBKDF2Parameters * @param s String - * @return boolean - * @see fr.xephi.authme.security.pbkdf2.PBKDF2Formatter#fromString(PBKDF2Parameters, String) - */ + + + * @return boolean * @see fr.xephi.authme.security.pbkdf2.PBKDF2Formatter#fromString(PBKDF2Parameters, String) */ public boolean fromString(PBKDF2Parameters p, String s) { if (p == null || s == null) { return true; @@ -63,9 +63,9 @@ public class PBKDF2HexFormatter implements PBKDF2Formatter { /** * Method toString. * @param p PBKDF2Parameters - * @return String - * @see fr.xephi.authme.security.pbkdf2.PBKDF2Formatter#toString(PBKDF2Parameters) - */ + + + * @return String * @see fr.xephi.authme.security.pbkdf2.PBKDF2Formatter#toString(PBKDF2Parameters) */ public String toString(PBKDF2Parameters p) { String s = BinTools.bin2hex(p.getSalt()) + ":" + String.valueOf(p.getIterationCount()) + ":" + BinTools.bin2hex(p.getDerivedKey()); return s; diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Parameters.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Parameters.java index 5f11cf1b..5dde7a99 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Parameters.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Parameters.java @@ -111,8 +111,8 @@ public class PBKDF2Parameters { /** * Method getIterationCount. - * @return int - */ + + * @return int */ public int getIterationCount() { return iterationCount; } @@ -127,8 +127,8 @@ public class PBKDF2Parameters { /** * Method getSalt. - * @return byte[] - */ + + * @return byte[] */ public byte[] getSalt() { return salt; } @@ -143,8 +143,8 @@ public class PBKDF2Parameters { /** * Method getDerivedKey. - * @return byte[] - */ + + * @return byte[] */ public byte[] getDerivedKey() { return derivedKey; } @@ -159,8 +159,8 @@ public class PBKDF2Parameters { /** * Method getHashAlgorithm. - * @return String - */ + + * @return String */ public String getHashAlgorithm() { return hashAlgorithm; } @@ -175,8 +175,8 @@ public class PBKDF2Parameters { /** * Method getHashCharset. - * @return String - */ + + * @return String */ public String getHashCharset() { return hashCharset; } diff --git a/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java b/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java index 0381e7e7..bb470359 100644 --- a/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java +++ b/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java @@ -43,8 +43,8 @@ public class CustomConfiguration extends YamlConfiguration { /** * Method reLoad. - * @return boolean - */ + + * @return boolean */ public boolean reLoad() { boolean out = true; if (!configFile.exists()) { @@ -65,8 +65,8 @@ public class CustomConfiguration extends YamlConfiguration { /** * Method getConfigFile. - * @return File - */ + + * @return File */ public File getConfigFile() { return configFile; } @@ -74,8 +74,8 @@ public class CustomConfiguration extends YamlConfiguration { /** * Method loadResource. * @param file File - * @return boolean - */ + + * @return boolean */ public boolean loadResource(File file) { if (!file.exists()) { try { diff --git a/src/main/java/fr/xephi/authme/settings/Messages.java b/src/main/java/fr/xephi/authme/settings/Messages.java index d76633cb..46957da6 100644 --- a/src/main/java/fr/xephi/authme/settings/Messages.java +++ b/src/main/java/fr/xephi/authme/settings/Messages.java @@ -46,8 +46,8 @@ public class Messages extends CustomConfiguration { /** * Method send. * @param msg String - * @return String[] - */ + + * @return String[] */ public String[] send(String msg) { if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.lang)) { singleton.reloadMessages(); @@ -73,8 +73,8 @@ public class Messages extends CustomConfiguration { /** * Method getInstance. - * @return Messages - */ + + * @return Messages */ public static Messages getInstance() { if (singleton == null) { singleton = new Messages(Settings.messageFile, Settings.messagesLanguage); diff --git a/src/main/java/fr/xephi/authme/settings/OtherAccounts.java b/src/main/java/fr/xephi/authme/settings/OtherAccounts.java index 29ae1e9c..dcaf3c00 100644 --- a/src/main/java/fr/xephi/authme/settings/OtherAccounts.java +++ b/src/main/java/fr/xephi/authme/settings/OtherAccounts.java @@ -35,8 +35,8 @@ public class OtherAccounts extends CustomConfiguration { /** * Method getInstance. - * @return OtherAccounts - */ + + * @return OtherAccounts */ public static OtherAccounts getInstance() { if (others == null) { others = new OtherAccounts(); @@ -83,8 +83,8 @@ public class OtherAccounts extends CustomConfiguration { /** * Method getAllPlayersByUUID. * @param uuid UUID - * @return List - */ + + * @return List */ public List getAllPlayersByUUID(UUID uuid) { return this.getStringList(uuid.toString()); } diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index 0e384df3..a8da9f5e 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -118,8 +118,8 @@ public final class Settings extends YamlConfiguration { /** * Method reload. - * @throws Exception - */ + + * @throws Exception */ public static void reload() throws Exception { plugin.getLogger().info("Loading Configuration File..."); boolean exist = SETTINGS_FILE.exists(); @@ -524,8 +524,8 @@ public final class Settings extends YamlConfiguration { /** * Method getPasswordHash. - * @return HashAlgorithm - */ + + * @return HashAlgorithm */ private static HashAlgorithm getPasswordHash() { String key = "settings.security.passwordHash"; try { @@ -538,8 +538,8 @@ public final class Settings extends YamlConfiguration { /** * Method getDataSource. - * @return DataSourceType - */ + + * @return DataSourceType */ private static DataSourceType getDataSource() { String key = "DataSource.backend"; try { @@ -556,8 +556,8 @@ public final class Settings extends YamlConfiguration { * server, so player has a restricted access * @param name String * @param ip String - * @return boolean - */ + + * @return boolean */ public static boolean getRestrictedIp(String name, String ip) { Iterator iter = getRestrictedIp.iterator(); @@ -610,8 +610,8 @@ public final class Settings extends YamlConfiguration { /** * Method checkLang. * @param lang String - * @return String - */ + + * @return String */ public static String checkLang(String lang) { if (new File(PLUGIN_FOLDER, "messages" + File.separator + "messages_" + lang + ".yml").exists()) { ConsoleLogger.info("Set Language to: " + lang); @@ -673,8 +673,8 @@ public final class Settings extends YamlConfiguration { /** * Method isEmailCorrect. * @param email String - * @return boolean - */ + + * @return boolean */ public static boolean isEmailCorrect(String email) { if (!email.contains("@")) return false; diff --git a/src/main/java/fr/xephi/authme/settings/Spawn.java b/src/main/java/fr/xephi/authme/settings/Spawn.java index fe53bc2d..0671ceb4 100644 --- a/src/main/java/fr/xephi/authme/settings/Spawn.java +++ b/src/main/java/fr/xephi/authme/settings/Spawn.java @@ -45,8 +45,8 @@ public class Spawn extends CustomConfiguration { /** * Method getInstance. - * @return Spawn - */ + + * @return Spawn */ public static Spawn getInstance() { if (spawn == null) { spawn = new Spawn(); @@ -57,8 +57,8 @@ public class Spawn extends CustomConfiguration { /** * Method setSpawn. * @param location Location - * @return boolean - */ + + * @return boolean */ public boolean setSpawn(Location location) { try { set("spawn.world", location.getWorld().getName()); @@ -77,8 +77,8 @@ public class Spawn extends CustomConfiguration { /** * Method setFirstSpawn. * @param location Location - * @return boolean - */ + + * @return boolean */ public boolean setFirstSpawn(Location location) { try { set("firstspawn.world", location.getWorld().getName()); @@ -96,8 +96,8 @@ public class Spawn extends CustomConfiguration { /** * Method getLocation. - * @return Location - */ + + * @return Location */ @Deprecated public Location getLocation() { return getSpawn(); @@ -105,8 +105,8 @@ public class Spawn extends CustomConfiguration { /** * Method getSpawn. - * @return Location - */ + + * @return Location */ public Location getSpawn() { try { if (this.getString("spawn.world").isEmpty() || this.getString("spawn.world").equals("")) @@ -120,8 +120,8 @@ public class Spawn extends CustomConfiguration { /** * Method getFirstSpawn. - * @return Location - */ + + * @return Location */ public Location getFirstSpawn() { try { if (this.getString("firstspawn.world").isEmpty() || this.getString("firstspawn.world").equals("")) diff --git a/src/main/java/fr/xephi/authme/task/TimeoutTask.java b/src/main/java/fr/xephi/authme/task/TimeoutTask.java index c055c0d4..4fed3583 100644 --- a/src/main/java/fr/xephi/authme/task/TimeoutTask.java +++ b/src/main/java/fr/xephi/authme/task/TimeoutTask.java @@ -30,8 +30,8 @@ public class TimeoutTask implements Runnable { /** * Method getName. - * @return String - */ + + * @return String */ public String getName() { return name; } diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java index 202b32ce..c84fc74a 100644 --- a/src/main/java/fr/xephi/authme/util/StringUtils.java +++ b/src/main/java/fr/xephi/authme/util/StringUtils.java @@ -32,8 +32,8 @@ public class StringUtils { * Method containsAny. * @param str String * @param pieces String[] - * @return boolean - */ + + * @return boolean */ public static boolean containsAny(String str, String... pieces) { if (str == null) { return false; diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index f18a4047..330e8d8e 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -47,8 +47,8 @@ public class Utils { // Check and Download GeoIP data if not exist /** * Method checkGeoIP. - * @return boolean - */ + + * @return boolean */ public static boolean checkGeoIP() { if (lookupService != null) { return true; @@ -98,8 +98,8 @@ public class Utils { /** * Method getCountryCode. * @param ip String - * @return String - */ + + * @return String */ public static String getCountryCode(String ip) { if (checkGeoIP()) { return lookupService.getCountry(ip).getCode(); @@ -110,8 +110,8 @@ public class Utils { /** * Method getCountryName. * @param ip String - * @return String - */ + + * @return String */ public static String getCountryName(String ip) { if (checkGeoIP()) { return lookupService.getCountry(ip).getName(); @@ -173,8 +173,8 @@ public class Utils { * Method addNormal. * @param player Player * @param group String - * @return boolean - */ + + * @return boolean */ public static boolean addNormal(Player player, String group) { if (!useGroupSystem()) { return false; @@ -197,8 +197,8 @@ public class Utils { /** * Method checkAuth. * @param player Player - * @return boolean - */ + + * @return boolean */ public static boolean checkAuth(Player player) { if (player == null || Utils.isUnrestricted(player)) { return true; @@ -221,8 +221,8 @@ public class Utils { /** * Method isUnrestricted. * @param player Player - * @return boolean - */ + + * @return boolean */ public static boolean isUnrestricted(Player player) { return Settings.isAllowRestrictedIp && !Settings.getUnrestrictedName.isEmpty() && (Settings.getUnrestrictedName.contains(player.getName())); @@ -230,8 +230,8 @@ public class Utils { /** * Method useGroupSystem. - * @return boolean - */ + + * @return boolean */ private static boolean useGroupSystem() { return Settings.isPermissionCheckEnabled && !Settings.getUnloggedinGroup.isEmpty(); } @@ -315,8 +315,8 @@ public class Utils { /** * Method getOnlinePlayers. - * @return Collection - */ + + * @return Collection */ @SuppressWarnings("unchecked") public static Collection getOnlinePlayers() { if (getOnlinePlayersIsCollection) { @@ -339,8 +339,8 @@ public class Utils { /** * Method getPlayer. * @param name String - * @return Player - */ + + * @return Player */ @SuppressWarnings("deprecation") public static Player getPlayer(String name) { name = name.toLowerCase(); @@ -350,8 +350,8 @@ public class Utils { /** * Method isNPC. * @param player Entity - * @return boolean - */ + + * @return boolean */ public static boolean isNPC(final Entity player) { try { if (player.hasMetadata("NPC")) { From 5ff9b7550e861de06c44d8682d785b2cd20be6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 02:33:59 +0100 Subject: [PATCH 037/199] Combined Plugin and Server listener, removed plugin listener --- src/main/java/fr/xephi/authme/AuthMe.java | 1 - .../authme/listener/AuthMePluginListener.java | 57 ------------------- .../authme/listener/AuthMeServerListener.java | 20 ++++++- 3 files changed, 19 insertions(+), 59 deletions(-) delete mode 100644 src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index c4cd327e..908dacc2 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -364,7 +364,6 @@ public class AuthMe extends JavaPlugin { pm.registerEvents(new AuthMePlayerListener18(this), this); } catch (ClassNotFoundException ignore) { } - pm.registerEvents(new AuthMePluginListener(this), this); pm.registerEvents(new AuthMeBlockListener(this), this); pm.registerEvents(new AuthMeEntityListener(this), this); pm.registerEvents(new AuthMeServerListener(this), this); diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java deleted file mode 100644 index 0b20a3d5..00000000 --- a/src/main/java/fr/xephi/authme/listener/AuthMePluginListener.java +++ /dev/null @@ -1,57 +0,0 @@ -package fr.xephi.authme.listener; - -import fr.xephi.authme.AuthMe; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.PluginEnableEvent; -import org.bukkit.plugin.Plugin; -/** - */ -public class AuthMePluginListener implements Listener { - - /** Plugin instance. */ - public AuthMe instance; - - /** - * Constructor. - * - * @param instance Main plugin instance. - */ - public AuthMePluginListener(AuthMe instance) { - this.instance = instance; - } - - /** - * Called when a plugin is enabled. - * - * @param event Event reference. - */ - @EventHandler - public void onPluginEnable(PluginEnableEvent event) { - // Call the onPluginEnable method in the permissions manager - this.instance.getPermissionsManager().onPluginEnable(event); - } - - /** - * Called when a plugin is disabled. - * - * @param event Event reference. - */ - @EventHandler - public void onPluginDisable(PluginDisableEvent event) { - // Get the plugin instance - Plugin plugin = event.getPlugin(); - - // Make sure the plugin instance isn't null - if(plugin == null) - return; - - // Make sure it's not this plugin itself - if(plugin.equals(this.instance)) - return; - - // Call the onPluginDisable method in the permissions manager - this.instance.getPermissionsManager().onPluginDisable(event); - } -} diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java index 3baa7ef6..862ac929 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java @@ -12,6 +12,7 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; +import org.bukkit.plugin.Plugin; /** */ @@ -55,7 +56,21 @@ public class AuthMeServerListener implements Listener { */ @EventHandler(priority = EventPriority.HIGHEST) public void onPluginDisable(PluginDisableEvent event) { - String pluginName = event.getPlugin().getName(); + // Get the plugin instance + Plugin pluginInstance = event.getPlugin(); + + // Make sure the plugin instance isn't null + if(pluginInstance == null) + return; + + // Make sure it's not this plugin itself + if(pluginInstance.equals(this.plugin)) + return; + + // Call the onPluginDisable method in the permissions manager + this.plugin.getPermissionsManager().onPluginDisable(event); + + String pluginName = pluginInstance.getName(); if (pluginName.equalsIgnoreCase("Essentials")) { plugin.ess = null; ConsoleLogger.info("Essentials has been disabled, unhook!"); @@ -91,6 +106,9 @@ public class AuthMeServerListener implements Listener { */ @EventHandler(priority = EventPriority.HIGHEST) public void onPluginEnable(PluginEnableEvent event) { + // Call the onPluginEnable method in the permissions manager + this.plugin.getPermissionsManager().onPluginEnable(event); + String pluginName = event.getPlugin().getName(); if (pluginName.equalsIgnoreCase("Essentials") || pluginName.equalsIgnoreCase("EssentialsSpawn")) plugin.checkEssentials(); From 06d6bd4a7e062d9fec885d6aefe8fd26a88c4056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 02:43:32 +0100 Subject: [PATCH 038/199] Fixed formatting in PermissionsManager class --- .../authme/permission/PermissionsManager.java | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 7f1b4f9c..76da48f1 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -61,14 +61,17 @@ public class PermissionsManager { * Essentials group manager instance. */ private GroupManager groupManagerPerms; + /** * Permissions manager instance for the legacy permissions system. */ private PermissionHandler defaultPerms; + /** * zPermissions service instance. */ private ZPermissionsService zPermissionsService; + /** * Vault instance. */ @@ -79,7 +82,7 @@ public class PermissionsManager { * * @param server Server instance * @param plugin Plugin instance - * @param log Logger + * @param log Logger */ public PermissionsManager(Server server, Plugin plugin, Logger log) { this.server = server; @@ -90,8 +93,8 @@ public class PermissionsManager { /** * Check if the permissions manager is currently hooked into any of the supported permissions systems. * - d. - * @return False if there isn't any permissions system used. */ + * @return False if there isn't any permissions system used. + */ public boolean isEnabled() { return !permsType.equals(PermissionsSystemType.NONE); } @@ -99,8 +102,8 @@ public class PermissionsManager { /** * Return the permissions system where the permissions manager is currently hooked into. * - e. - * @return Permissions system type. */ + * @return Permissions system type. + */ public PermissionsSystemType getUsedPermissionsSystemType() { return this.permsType; } @@ -108,8 +111,8 @@ public class PermissionsManager { /** * Setup and hook into the permissions systems. * - m. - * @return The detected permissions system. */ + * @return The detected permissions system. + */ public PermissionsSystemType setup() { // Define the plugin manager final PluginManager pm = this.server.getPluginManager(); @@ -245,8 +248,8 @@ public class PermissionsManager { /** * Reload the permissions manager, and re-hook all permission plugins. * - e. - * @return True on success, false on failure. */ + * @return True on success, false on failure. + */ public boolean reload() { // Unhook all permission plugins unhook(); @@ -299,8 +302,8 @@ public class PermissionsManager { /** * Get the logger instance. * - e. - * @return Logger instance. */ + * @return Logger instance. + */ public Logger getLogger() { return this.log; } @@ -317,11 +320,11 @@ public class PermissionsManager { /** * Check if the player has permission. If no permissions system is used, the player has to be OP. * - * @param player The player. + * @param player The player. * @param permsNode Permissions node. * - n. - * @return True if the player has permission. */ + * @return True if the player has permission. + */ public boolean hasPermission(Player player, String permsNode) { return hasPermission(player, permsNode, player.isOp()); } @@ -329,12 +332,12 @@ public class PermissionsManager { /** * Check if a player has permission. * - * @param player The player. + * @param player The player. * @param permsNode The permission node. - * @param def Default returned if no permissions system is used. + * @param def Default returned if no permissions system is used. * - n. - * @return True if the player has permission. */ + * @return True if the player has permission. + */ public boolean hasPermission(Player player, String permsNode, boolean def) { if(!isEnabled()) // No permissions system is used, return default @@ -386,12 +389,14 @@ public class PermissionsManager { } } - /** + /** * Method getGroups. - * @param player Player - g> - * @return List */ - @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) + * + * @param player Player. + * + * @return Groups. + */ + @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public List getGroups(Player player) { if(!isEnabled()) // No permissions system is used, return an empty list @@ -437,9 +442,7 @@ public class PermissionsManager { } } - /** - */ - public enum PermissionsSystemType { + public enum PermissionsSystemType { NONE("None"), PERMISSIONS_EX("PermissionsEx"), PERMISSIONS_BUKKIT("Permissions Bukkit"), @@ -451,19 +454,21 @@ public class PermissionsManager { public String name; - /** + /** * Constructor for PermissionsSystemType. + * * @param name String */ - PermissionsSystemType(String name) { + PermissionsSystemType(String name) { this.name = name; } - /** + /** * Method getName. - ng - * @return String */ - public String getName() { + * + * @return String + */ + public String getName() { return this.name; } } From 504106f83505696f555bfce3dec3962857e4a47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 03:01:45 +0100 Subject: [PATCH 039/199] Added base of setGroup method in permissions manager --- .../authme/permission/PermissionsManager.java | 71 ++++++++++++++++++- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 76da48f1..104a986c 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -390,11 +390,11 @@ public class PermissionsManager { } /** - * Method getGroups. + * Get the permission groups of a player, if available. * - * @param player Player. + * @param player The player. * - * @return Groups. + * @return Permission groups. */ @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public List getGroups(Player player) { @@ -442,6 +442,71 @@ public class PermissionsManager { } } + /** + * Set the permission group of a player, if supported. + * + * @param player The player + * @param groupName The name of the group. + * + * @return True if succeed, false otherwise. + */ + @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) + public boolean setGroup(Player player, String groupName) { + if(!isEnabled()) + // No permissions system is used, return false + return false; + + // Create a list of group names + List groupNames = new ArrayList<>(); + groupNames.add(groupName); + + // Set the group the proper way + switch(this.permsType) { + case PERMISSIONS_EX: + // Permissions Ex + PermissionUser user = PermissionsEx.getUser(player); + user.setParentsIdentifier(groupNames); + return true; + + case PERMISSIONS_BUKKIT: + // Permissions Bukkit + // Permissions Bukkit doesn't support groups, return false + return false; + + case B_PERMISSIONS: + // bPermissions + ApiLayer.setGroup(player.getWorld().getName(), CalculableType.USER, player.getName(), groupName); + return true; + + case ESSENTIALS_GROUP_MANAGER: + // Essentials Group Manager + final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); + if(handler == null) + return false; + // TODO: Write proper code here! + //return Arrays.asList(handler.getGroups(player.getName())); + + case Z_PERMISSIONS: + //zPermissions + // TODO: Write proper code here! + //return new ArrayList(zPermissionsService.getPlayerGroups(player.getName())); + + case VAULT: + // Vault + // TODO: Clear the current list of groups? + vaultPerms.playerAddGroup(player, groupName); + return true; + + case NONE: + // Not hooked into any permissions system, return false + return false; + + default: + // Something went wrong, return false + return false; + } + } + public enum PermissionsSystemType { NONE("None"), PERMISSIONS_EX("PermissionsEx"), From 38cc217cff374e3a33bf3d61b7c794d936c0f148 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 07:41:56 +0100 Subject: [PATCH 040/199] Revert certain JavaDoc changes Ideally JavaDoc should provide additional information to the developer as to the method's purpose and usage. Typically you do not add the return type of the method and the parameter's types since this can be seen in the code. A short description of what the parameter really is (e.g. a String can hold many types of information) is a lot more beneficial. A JavaDoc statement simply restating the parameter types and the method name is, put bluntly, simply noise, since all of these things are already contained in the code itself. Similarly, @see references are great for pointing to other, related methods but aren't very helpful to point to a superclass method (the implemented or overriden method) since it is implied by @Override. A developer can navigate easily to the superclass method with any reasonable IDE. --- pom.xml | 2 +- src/main/java/fr/xephi/authme/AuthMe.java | 118 ++++-------------- .../java/fr/xephi/authme/Log4JFilter.java | 56 +-------- 3 files changed, 28 insertions(+), 148 deletions(-) diff --git a/pom.xml b/pom.xml index 93fdb5f2..1a9a6439 100644 --- a/pom.xml +++ b/pom.xml @@ -606,7 +606,7 @@ true - + junit junit diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 908dacc2..29d7091c 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -60,6 +60,7 @@ import net.milkbowl.vault.permission.Permission; import net.minelink.ctplus.CombatTagPlus; /** + * The AuthMe main class. */ public class AuthMe extends JavaPlugin { @@ -506,8 +507,8 @@ public class AuthMe extends JavaPlugin { /** * Get the permissions manager instance. * - - * @return Permissions Manager instance. */ + * @return Permissions Manager instance. + */ public PermissionsManager getPermissionsManager() { return this.permsMan; } @@ -649,10 +650,6 @@ public class AuthMe extends JavaPlugin { } // Save Player Data - /** - * Method savePlayer. - * @param player Player - */ public void savePlayer(Player player) { if ((Utils.isNPC(player)) || (Utils.isUnrestricted(player))) { return; @@ -681,11 +678,6 @@ public class AuthMe extends JavaPlugin { } // Select the player to kick when a vip player join the server when full - /** - * Method generateKickPlayer. - * @param collection Collection - - * @return Player */ public Player generateKickPlayer(Collection collection) { Player player = null; for (Player p : collection) { @@ -730,11 +722,6 @@ public class AuthMe extends JavaPlugin { } // Return the spawn location of a player - /** - * Method getSpawnLocation. - * @param player Player - - * @return Location */ public Location getSpawnLocation(Player player) { World world = player.getWorld(); String[] spawnPriority = Settings.spawnPriority.split(","); @@ -757,21 +744,11 @@ public class AuthMe extends JavaPlugin { } // Return the default spawnpoint of a world - /** - * Method getDefaultSpawn. - * @param world World - - * @return Location */ private Location getDefaultSpawn(World world) { return world.getSpawnLocation(); } // Return the multiverse spawnpoint of a world - /** - * Method getMultiverseSpawn. - * @param world World - - * @return Location */ private Location getMultiverseSpawn(World world) { if (multiverse != null && Settings.multiverse) { try { @@ -784,10 +761,6 @@ public class AuthMe extends JavaPlugin { } // Return the essentials spawnpoint - /** - * Method getEssentialsSpawn. - - * @return Location */ private Location getEssentialsSpawn() { if (essentialsSpawn != null) { return essentialsSpawn; @@ -796,11 +769,6 @@ public class AuthMe extends JavaPlugin { } // Return the authme soawnpoint - /** - * Method getAuthMeSpawn. - * @param player Player - - * @return Location */ private Location getAuthMeSpawn(Player player) { if ((!database.isAuthAvailable(player.getName().toLowerCase()) || !player.hasPlayedBefore()) && (Spawn.getInstance().getFirstSpawn() != null)) { return Spawn.getInstance().getFirstSpawn(); @@ -811,19 +779,11 @@ public class AuthMe extends JavaPlugin { return player.getWorld().getSpawnLocation(); } - /** - * Method switchAntiBotMod. - * @param mode boolean - */ public void switchAntiBotMod(boolean mode) { this.antibotMod = mode; Settings.switchAntiBotMod(mode); } - /** - * Method getAntiBotModMode. - - * @return boolean */ public boolean getAntiBotModMode() { return this.antibotMod; } @@ -850,12 +810,7 @@ public class AuthMe extends JavaPlugin { }, 1, 1200 * Settings.delayRecall); } - /** - * Method replaceAllInfos. - * @param message String - * @param player Player - - * @return String */ + public String replaceAllInfos(String message, Player player) { int playersOnline = Utils.getOnlinePlayers().size(); message = message.replace("&", "\u00a7"); @@ -871,11 +826,7 @@ public class AuthMe extends JavaPlugin { return message; } - /** - * Method getIP. - * @param player Player - - * @return String */ + public String getIP(Player player) { String name = player.getName().toLowerCase(); String ip = player.getAddress().getAddress().getHostAddress(); @@ -889,12 +840,7 @@ public class AuthMe extends JavaPlugin { return ip; } - /** - * Method isLoggedIp. - * @param name String - * @param ip String - - * @return boolean */ + public boolean isLoggedIp(String name, String ip) { int count = 0; for (Player player : Utils.getOnlinePlayers()) { @@ -904,12 +850,7 @@ public class AuthMe extends JavaPlugin { return count >= Settings.getMaxLoginPerIp; } - /** - * Method hasJoinedIp. - * @param name String - * @param ip String - - * @return boolean */ + public boolean hasJoinedIp(String name, String ip) { int count = 0; for (Player player : Utils.getOnlinePlayers()) { @@ -919,21 +860,18 @@ public class AuthMe extends JavaPlugin { return count >= Settings.getMaxJoinPerIp; } - /** - * Method getModuleManager. - - * @return ModuleManager */ + public ModuleManager getModuleManager() { return moduleManager; } /** - * Get Player real IP through VeryGames method + * Gets a player's real IP through VeryGames method. * - * @param player - * player - - * @return String */ + * @param player the player to process + * + * @return the real IP of the player + */ @Deprecated public String getVeryGamesIP(Player player) { String realIP = player.getAddress().getAddress().getHostAddress(); @@ -952,31 +890,19 @@ public class AuthMe extends JavaPlugin { return realIP; } - /** - * Method getCountryCode. - * @param ip String - - * @return String */ + @Deprecated public String getCountryCode(String ip) { return Utils.getCountryCode(ip); } - /** - * Method getCountryName. - * @param ip String - - * @return String */ + @Deprecated public String getCountryName(String ip) { return Utils.getCountryName(ip); } - /** - * Get the command handler instance. - * - - * @return Command handler. */ + public CommandHandler getCommandHandler() { return this.commandHandler; } @@ -993,9 +919,7 @@ public class AuthMe extends JavaPlugin { * @param args * The command arguments (Bukkit). * - - - * @return True if the command was executed, false otherwise. * @see org.bukkit.command.CommandExecutor#onCommand(CommandSender, Command, String, String[]) * @see org.bukkit.command.CommandExecutor#onCommand(CommandSender, Command, String, String[]) + * @return True if the command was executed, false otherwise. */ @Override public boolean onCommand(CommandSender sender, Command cmd, @@ -1012,9 +936,9 @@ public class AuthMe extends JavaPlugin { /** * Get the current installed AuthMeReloaded version name. * - * @return The version name of the currently installed AuthMeReloaded - * instance. */ + * instance. + */ public static String getVersionName() { return PLUGIN_VERSION_NAME; } @@ -1022,9 +946,9 @@ public class AuthMe extends JavaPlugin { /** * Get the current installed AuthMeReloaded version code. * - * @return The version code of the currently installed AuthMeReloaded - * instance. */ + * instance. + */ public static int getVersionCode() { return PLUGIN_VERSION_CODE; } diff --git a/src/main/java/fr/xephi/authme/Log4JFilter.java b/src/main/java/fr/xephi/authme/Log4JFilter.java index 5eebc7cb..917a7536 100644 --- a/src/main/java/fr/xephi/authme/Log4JFilter.java +++ b/src/main/java/fr/xephi/authme/Log4JFilter.java @@ -24,12 +24,6 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { public Log4JFilter() { } - /** - * Method filter. - * @param record LogEvent - - - * @return Result * @see org.apache.logging.log4j.core.Filter#filter(LogEvent) */ @Override public Result filter(LogEvent record) { if (record == null) { @@ -38,32 +32,12 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { return validateMessage(record.getMessage()); } - /** - * Method filter. - * @param arg0 Logger - * @param arg1 Level - * @param arg2 Marker - * @param message String - * @param arg4 Object[] - - - * @return Result * @see org.apache.logging.log4j.core.Filter#filter(Logger, Level, Marker, String, Object[]) */ @Override public Result filter(Logger arg0, Level arg1, Marker arg2, String message, Object... arg4) { return validateMessage(message); } - /** - * Method filter. - * @param arg0 Logger - * @param arg1 Level - * @param arg2 Marker - * @param message Object - * @param arg4 Throwable - - - * @return Result * @see org.apache.logging.log4j.core.Filter#filter(Logger, Level, Marker, Object, Throwable) */ @Override public Result filter(Logger arg0, Level arg1, Marker arg2, Object message, Throwable arg4) { @@ -73,37 +47,17 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { return validateMessage(message.toString()); } - /** - * Method filter. - * @param arg0 Logger - * @param arg1 Level - * @param arg2 Marker - * @param message Message - * @param arg4 Throwable - - - * @return Result * @see org.apache.logging.log4j.core.Filter#filter(Logger, Level, Marker, Message, Throwable) */ @Override public Result filter(Logger arg0, Level arg1, Marker arg2, Message message, Throwable arg4) { return validateMessage(message); } - /** - * Method getOnMatch. - - - * @return Result * @see org.apache.logging.log4j.core.Filter#getOnMatch() */ @Override public Result getOnMatch() { return Result.NEUTRAL; } - /** - * Method getOnMismatch. - - - * @return Result * @see org.apache.logging.log4j.core.Filter#getOnMismatch() */ @Override public Result getOnMismatch() { return Result.NEUTRAL; @@ -115,8 +69,9 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { * data. * * @param message the Message object to verify - - * @return the Result value */ + * + * @return the Result value + */ private static Result validateMessage(Message message) { if (message == null) { return Result.NEUTRAL; @@ -129,8 +84,9 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { * depending on whether the message contains sensitive AuthMe data. * * @param message the message to verify - - * @return the Result value */ + * + * @return the Result value + */ private static Result validateMessage(String message) { if (message == null) { return Result.NEUTRAL; From 9a68aa5517afa61efdaec465808e219c1ccaf24d Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 08:28:53 +0100 Subject: [PATCH 041/199] Proper Javadoc example / add test for StringUtils - Proper example for the purpose of javadoc and how it could look like - Fix containsAny to be null safe - Add tests --- .../fr/xephi/authme/util/StringUtils.java | 26 ++++++----- .../fr/xephi/authme/util/StringUtilsTest.java | 46 +++++++++++++++++++ 2 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/util/StringUtilsTest.java diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java index c84fc74a..e9b495bc 100644 --- a/src/main/java/fr/xephi/authme/util/StringUtils.java +++ b/src/main/java/fr/xephi/authme/util/StringUtils.java @@ -5,17 +5,18 @@ import net.ricecode.similarity.StringSimilarityService; import net.ricecode.similarity.StringSimilarityServiceImpl; /** + * Utility class for String operations. */ public class StringUtils { /** * Get the difference of two strings. * - * @param first First string. - * @param second Second string. + * @param first First string + * @param second Second string * - - * @return The difference value. */ + * @return The difference value + */ public static double getDifference(String first, String second) { // Make sure the strings are valid. if(first == null || second == null) @@ -27,22 +28,25 @@ public class StringUtils { // Determine the difference value, return the result return Math.abs(service.score(first, second) - 1.0); } - + /** - * Method containsAny. - * @param str String - * @param pieces String[] - - * @return boolean */ + * Returns whether the given string contains any of the provided elements. + * + * @param str the string to analyze + * @param pieces the items to check the string for + * + * @return true if the string contains at least one of the items + */ public static boolean containsAny(String str, String... pieces) { if (str == null) { return false; } for (String piece : pieces) { - if (str.contains(piece)) { + if (piece != null && str.contains(piece)) { return true; } } return false; } + } diff --git a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java new file mode 100644 index 00000000..668f2059 --- /dev/null +++ b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java @@ -0,0 +1,46 @@ +package fr.xephi.authme.util; + +import org.junit.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link StringUtils}. + */ +public class StringUtilsTest { + + @Test + public void shouldFindContainedItem() { + // given + String text = "This is a test of containsAny()"; + String piece = "test"; + + // when + boolean result = StringUtils.containsAny(text, "some", "words", "that", "do not", "exist", piece); + + // then + assertThat(result, equalTo(true)); + } + + @Test + public void shouldReturnFalseIfNoneFound() { + // given + String text = "This is a test string"; + + // when + boolean result = StringUtils.containsAny(text, "some", "other", "words", null); + + // then + assertThat(result, equalTo(false)); + } + + @Test + public void shouldReturnFalseForNullString() { + // given/when + boolean result = StringUtils.containsAny(null, "some", "words", "to", "check"); + + // then + assertThat(result, equalTo(false)); + } +} From 3934d673305020515fbf72c9bee9affe32c749d7 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 09:07:12 +0100 Subject: [PATCH 042/199] Command refactor - remove unused fields, reduce variable "scope" Minor refactorings in the command section for familiarization. 1. Removed suppressWarning("Deprecated") - the method is deprecated for a reason and we should be made aware of that. 2. Removed same javadoc on ExecutableCommand implementation that just had the same as the interface (this is just clutter; @Override signals that it's an implementing class and a developer can view the superclass javadoc) 3. In places where the AuthMe instance was retrieved at the top but used at the very bottom, moved it to the bottom to reduce its "scope" (cherry picked from commit 45a50f3) --- .../command/CommandArgumentDescription.java | 12 +++++----- .../authme/command/ExecutableCommand.java | 5 ++-- .../command/executable/HelpCommand.java | 20 ++++------------ .../executable/authme/AccountsCommand.java | 9 -------- .../executable/authme/AuthMeCommand.java | 9 -------- .../authme/ChangePasswordCommand.java | 13 +---------- .../executable/authme/FirstSpawnCommand.java | 10 +------- .../executable/authme/ForceLoginCommand.java | 10 -------- .../executable/authme/GetEmailCommand.java | 8 ++----- .../executable/authme/GetIpCommand.java | 10 -------- .../executable/authme/LastLoginCommand.java | 18 +++------------ .../changepassword/ChangePasswordCommand.java | 14 +---------- .../executable/email/AddEmailCommand.java | 20 ++-------------- .../executable/email/ChangeEmailCommand.java | 21 ++--------------- .../executable/email/RecoverEmailCommand.java | 18 +++------------ .../executable/login/LoginCommand.java | 23 ++++--------------- .../executable/logout/LogoutCommand.java | 15 ++---------- .../executable/register/RegisterCommand.java | 14 +---------- 18 files changed, 36 insertions(+), 213 deletions(-) diff --git a/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java b/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java index e47311bf..75d6e4ae 100644 --- a/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java @@ -39,8 +39,8 @@ public class CommandArgumentDescription { /** * Get the argument label. * - - * @return Argument label. */ + * @return Argument label. + */ public String getLabel() { return this.label; } @@ -57,8 +57,8 @@ public class CommandArgumentDescription { /** * Get the argument description. * - - * @return Argument description. */ + * @return Argument description. + */ public String getDescription() { return description; } @@ -75,8 +75,8 @@ public class CommandArgumentDescription { /** * Check whether the argument is optional. * - - * @return True if the argument is optional, false otherwise. */ + * @return True if the argument is optional, false otherwise. + */ public boolean isOptional() { return optional; } diff --git a/src/main/java/fr/xephi/authme/command/ExecutableCommand.java b/src/main/java/fr/xephi/authme/command/ExecutableCommand.java index 6c3ffe45..e81c705a 100644 --- a/src/main/java/fr/xephi/authme/command/ExecutableCommand.java +++ b/src/main/java/fr/xephi/authme/command/ExecutableCommand.java @@ -3,6 +3,7 @@ package fr.xephi.authme.command; import org.bukkit.command.CommandSender; /** + * Base class for AuthMe commands that can be executed. */ public abstract class ExecutableCommand { @@ -13,7 +14,7 @@ public abstract class ExecutableCommand { * @param commandReference The command reference. * @param commandArguments The command arguments. * - - * @return True if the command was executed successfully, false otherwise. */ + * @return True if the command was executed successfully, false otherwise. + */ public abstract boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments); } diff --git a/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java b/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java index 1d625d9c..9fbcd1d9 100644 --- a/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java @@ -10,31 +10,19 @@ import fr.xephi.authme.command.help.HelpProvider; */ public class HelpCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // Check whether quick help should be shown boolean quickHelp = commandArguments.getCount() == 0; - // Set the proper command arguments for the quick help - if(quickHelp) + // Set the proper command arguments for the quick help and show it + if (quickHelp) { commandArguments = new CommandParts(commandReference.get(0)); - - // Show the new help - if(quickHelp) HelpProvider.showHelp(sender, commandReference, commandArguments, false, false, false, false, false, true); - else + } else { HelpProvider.showHelp(sender, commandReference, commandArguments); + } - // Return the result return true; } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java index a627c934..0e036b0d 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java @@ -16,15 +16,6 @@ import fr.xephi.authme.settings.Messages; */ public class AccountsCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(final CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // AuthMe plugin instance diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java index 541154e7..da67fa82 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java @@ -11,15 +11,6 @@ import fr.xephi.authme.command.ExecutableCommand; */ public class AuthMeCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // Show some version info diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordCommand.java index 170685c0..04b4a019 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordCommand.java @@ -19,20 +19,8 @@ import fr.xephi.authme.settings.Settings; */ public class ChangePasswordCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(final CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - // Messages instance final Messages m = Messages.getInstance(); @@ -62,6 +50,7 @@ public class ChangePasswordCommand extends ExecutableCommand { } // Set the password + final AuthMe plugin = AuthMe.getInstance(); final String playerNameLowerCase = playerName.toLowerCase(); Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() { diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java index 13555552..a6401b0a 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java @@ -12,15 +12,6 @@ import fr.xephi.authme.settings.Spawn; */ public class FirstSpawnCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // Make sure the command executor is a player @@ -33,6 +24,7 @@ public class FirstSpawnCommand extends ExecutableCommand { sender.sendMessage("[AuthMe] Please use that command in game"); } } catch (NullPointerException ex) { + // TODO ljacqu 20151119: Catching NullPointerException is never a good idea. Find what can cause one instead ConsoleLogger.showError(ex.getMessage()); } return true; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java index 19e1e67c..0a9fd944 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java @@ -12,15 +12,6 @@ import fr.xephi.authme.command.ExecutableCommand; */ public class ForceLoginCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // AuthMe plugin instance @@ -33,7 +24,6 @@ public class ForceLoginCommand extends ExecutableCommand { // Command logic try { - @SuppressWarnings("deprecation") Player player = Bukkit.getPlayer(playerName); if (player == null || !player.isOnline()) { sender.sendMessage("Player needs to be online!"); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/GetEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/GetEmailCommand.java index 1ca2a851..4ee1fe06 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/GetEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/GetEmailCommand.java @@ -23,18 +23,14 @@ public class GetEmailCommand extends ExecutableCommand { * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - AuthMe plugin = AuthMe.getInstance(); - - // Messages instance - Messages m = Messages.getInstance(); - // Get the player name String playerName = sender.getName(); if(commandArguments.getCount() >= 1) playerName = commandArguments.get(0); // Get the authenticated user + AuthMe plugin = AuthMe.getInstance(); + Messages m = Messages.getInstance(); PlayerAuth auth = plugin.database.getAuth(playerName.toLowerCase()); if (auth == null) { m.send(sender, "unknown_user"); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java index f3b2700a..343642c5 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java @@ -12,15 +12,6 @@ import fr.xephi.authme.command.ExecutableCommand; */ public class GetIpCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // AuthMe plugin instance @@ -31,7 +22,6 @@ public class GetIpCommand extends ExecutableCommand { if(commandArguments.getCount() >= 1) playerName = commandArguments.get(0); - @SuppressWarnings("deprecation") Player player = Bukkit.getPlayer(playerName); if (player == null) { sender.sendMessage("This player is not actually online"); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/LastLoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/LastLoginCommand.java index 58237ef8..b2c4c70f 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/LastLoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/LastLoginCommand.java @@ -14,29 +14,17 @@ import fr.xephi.authme.settings.Messages; */ public class LastLoginCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - AuthMe plugin = AuthMe.getInstance(); - - // Messages instance - Messages m = Messages.getInstance(); - // Get the player String playerName = sender.getName(); if(commandArguments.getCount() >= 1) playerName = commandArguments.get(0); // Validate the player + AuthMe plugin = AuthMe.getInstance(); + Messages m = Messages.getInstance(); + PlayerAuth auth; try { auth = plugin.database.getAuth(playerName.toLowerCase()); diff --git a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java index dc258362..9b6956b2 100644 --- a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java @@ -15,21 +15,8 @@ import fr.xephi.authme.task.ChangePasswordTask; */ public class ChangePasswordCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - - // Messages instance final Messages m = Messages.getInstance(); // Get the passwords @@ -71,6 +58,7 @@ public class ChangePasswordCommand extends ExecutableCommand { } // Set the password + final AuthMe plugin = AuthMe.getInstance(); plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new ChangePasswordTask(plugin, player, playerPass, playerPassVerify)); return true; } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java index 34d6bba4..68f5e54b 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java @@ -12,22 +12,8 @@ import fr.xephi.authme.settings.Messages; */ public class AddEmailCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - - // Messages instance - final Messages m = Messages.getInstance(); - // Get the parameter values String playerMail = commandArguments.get(0); String playerMailVerify = commandArguments.get(1); @@ -37,11 +23,9 @@ public class AddEmailCommand extends ExecutableCommand { return true; } - // Get the player instance and name + // Get the player and perform email addition + final AuthMe plugin = AuthMe.getInstance(); final Player player = (Player) sender; - final String playerName = player.getName().toLowerCase(); - - // Command logic plugin.management.performAddEmail(player, playerMail, playerMailVerify); return true; } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java index 03a3c156..d295512e 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java @@ -6,28 +6,13 @@ import org.bukkit.entity.Player; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.CommandParts; import fr.xephi.authme.command.ExecutableCommand; -import fr.xephi.authme.settings.Messages; /** */ public class ChangeEmailCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - - // Messages instance - final Messages m = Messages.getInstance(); - // Get the parameter values String playerMailOld = commandArguments.get(0); String playerMailNew = commandArguments.get(1); @@ -37,11 +22,9 @@ public class ChangeEmailCommand extends ExecutableCommand { return true; } - // Get the player instance and name + // Get the player instance and execute action + final AuthMe plugin = AuthMe.getInstance(); final Player player = (Player) sender; - final String playerName = player.getName(); - - // Command logic plugin.management.performChangeEmail(player, playerMailOld, playerMailNew); return true; } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index 748fed46..44f007a9 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -20,23 +20,8 @@ import fr.xephi.authme.settings.Settings; */ public class RecoverEmailCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - - // Messages instance - final Messages m = Messages.getInstance(); - // Get the parameter values String playerMail = commandArguments.get(0); @@ -50,6 +35,9 @@ public class RecoverEmailCommand extends ExecutableCommand { final String playerName = player.getName(); // Command logic + final AuthMe plugin = AuthMe.getInstance(); + final Messages m = Messages.getInstance(); + if (plugin.mail == null) { m.send(player, "error"); return true; diff --git a/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java index d6b44164..e9b80cdf 100644 --- a/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java @@ -11,32 +11,19 @@ import fr.xephi.authme.command.ExecutableCommand; */ public class LoginCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - // Make sure the current command executor is a player - if(!(sender instanceof Player)) { + if (!(sender instanceof Player)) { return true; } - // Get the player instance + // Get the necessary objects + final AuthMe plugin = AuthMe.getInstance(); final Player player = (Player) sender; + final String playerPass = commandArguments.get(0); - // Get the password - String playerPass = commandArguments.get(0); - - // Login the player + // Log the player in plugin.management.performLogin(player, playerPass, false); return true; } diff --git a/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java b/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java index fcc86ce2..e2e97806 100644 --- a/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java @@ -11,26 +11,15 @@ import fr.xephi.authme.command.ExecutableCommand; */ public class LogoutCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - // Make sure the current command executor is a player - if(!(sender instanceof Player)) { + if (!(sender instanceof Player)) { return true; } // Get the player instance + final AuthMe plugin = AuthMe.getInstance(); final Player player = (Player) sender; // Logout the player diff --git a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java index 7484793d..3c7f07ee 100644 --- a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java @@ -14,21 +14,8 @@ import fr.xephi.authme.settings.Settings; */ public class RegisterCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - - // Messages instance final Messages m = Messages.getInstance(); // Make sure the sender is a player @@ -44,6 +31,7 @@ public class RegisterCommand extends ExecutableCommand { return true; } + final AuthMe plugin = AuthMe.getInstance(); if (Settings.emailRegistration && !Settings.getmailAccount.isEmpty()) { if (Settings.doubleEmailCheck) { if (commandArguments.getCount() < 2 || !commandArguments.get(0).equals(commandArguments.get(1))) { From 987e38c5dfab200d88381883f51d8fcc5dcaf624 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 09:49:39 +0100 Subject: [PATCH 043/199] Add test for LoginCommand; create AuthMe mock test util Had to create a getter for the Management instance in the AuthMe class for mocking, but fields should generally not be accessed globally. Hopefully soon we will be able to make the field private. (cherry picked from commit f1a0022) --- src/main/java/fr/xephi/authme/AuthMe.java | 5 ++ .../executable/login/LoginCommand.java | 2 +- .../java/fr/xephi/authme/AuthMeMockUtil.java | 30 ++++++++ .../executable/login/LoginCommandTest.java | 77 +++++++++++++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/test/java/fr/xephi/authme/AuthMeMockUtil.java create mode 100644 src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 908dacc2..3b8d9f14 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -1028,4 +1028,9 @@ public class AuthMe extends JavaPlugin { public static int getVersionCode() { return PLUGIN_VERSION_CODE; } + + /** Returns the management instance. */ + public Management getManagement() { + return management; + } } diff --git a/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java index e9b80cdf..2479a105 100644 --- a/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java @@ -24,7 +24,7 @@ public class LoginCommand extends ExecutableCommand { final String playerPass = commandArguments.get(0); // Log the player in - plugin.management.performLogin(player, playerPass, false); + plugin.getManagement().performLogin(player, playerPass, false); return true; } } diff --git a/src/test/java/fr/xephi/authme/AuthMeMockUtil.java b/src/test/java/fr/xephi/authme/AuthMeMockUtil.java new file mode 100644 index 00000000..6f4ca7a4 --- /dev/null +++ b/src/test/java/fr/xephi/authme/AuthMeMockUtil.java @@ -0,0 +1,30 @@ +package fr.xephi.authme; + +import org.mockito.Mockito; + +import java.lang.reflect.Field; + +/** + * Creates a mock implementation of AuthMe for testing purposes. + */ +public final class AuthMeMockUtil { + + private AuthMeMockUtil() { + // Util class + } + + /** + * Set the AuthMe plugin instance to a mock object. Use {@link AuthMe#getInstance()} to retrieve the mock. + */ + public static void initialize() { + AuthMe mock = Mockito.mock(AuthMe.class); + + try { + Field instance = AuthMe.class.getDeclaredField("plugin"); + instance.setAccessible(true); + instance.set(null, mock); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Could not initialize AuthMe mock", e); + } + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java new file mode 100644 index 00000000..b8c3c2f5 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java @@ -0,0 +1,77 @@ +package fr.xephi.authme.command.executable.login; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.AuthMeMockUtil; +import fr.xephi.authme.command.CommandParts; +import fr.xephi.authme.process.Management; +import fr.xephi.authme.settings.Settings; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; + +/** + * Test for {@link LoginCommand}. + */ +public class LoginCommandTest { + + private static Management managementMock; + + @Before + public void initializeAuthMeMock() { + AuthMeMockUtil.initialize(); + AuthMe pluginMock = AuthMe.getInstance(); + + Settings.captchaLength = 10; + managementMock = mock(Management.class); + Mockito.when(pluginMock.getManagement()).thenReturn(managementMock); + } + + @Test + public void shouldStopIfSenderIsNotAPlayer() { + // given + CommandSender sender = mock(BlockCommandSender.class); + LoginCommand command = new LoginCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + Mockito.verify(managementMock, never()).performLogin(any(Player.class), anyString(), anyBoolean()); + } + + @Test + public void shouldCallManagementForPlayerCaller() { + // given + Player sender = mock(Player.class); + LoginCommand command = new LoginCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts("password")); + + // then + Mockito.verify(managementMock).performLogin(eq(sender), eq("password"), eq(false)); + } + + @Test + public void shouldHandleMissingPassword() { + // given + Player sender = mock(Player.class); + LoginCommand command = new LoginCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + // TODO ljacqu 20151121: May make sense to handle null password in LoginCommand instead of forwarding the call + String password = null; + Mockito.verify(managementMock).performLogin(eq(sender), eq(password), eq(false)); + } +} From e65319d42c086dcace1914d7cbe498542f328804 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 10:29:40 +0100 Subject: [PATCH 044/199] Add tests for LogoutCommand and RegisterCommand. Add more generic mockUtil (cherry picked from commit 06cfd13) --- .../executable/logout/LogoutCommand.java | 2 +- .../executable/register/RegisterCommand.java | 7 +- .../java/fr/xephi/authme/AuthMeMockUtil.java | 27 +++++- .../executable/login/LoginCommandTest.java | 8 +- .../executable/logout/LogoutCommandTest.java | 62 +++++++++++++ .../register/RegisterCommandTest.java | 88 +++++++++++++++++++ 6 files changed, 183 insertions(+), 11 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/command/executable/logout/LogoutCommandTest.java create mode 100644 src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java diff --git a/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java b/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java index e2e97806..60c14a26 100644 --- a/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java @@ -23,7 +23,7 @@ public class LogoutCommand extends ExecutableCommand { final Player player = (Player) sender; // Logout the player - plugin.management.performLogout(player); + plugin.getManagement().performLogout(player); return true; } } diff --git a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java index 3c7f07ee..6e89fc62 100644 --- a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java @@ -1,5 +1,6 @@ package fr.xephi.authme.command.executable.register; +import fr.xephi.authme.process.Management; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -31,7 +32,7 @@ public class RegisterCommand extends ExecutableCommand { return true; } - final AuthMe plugin = AuthMe.getInstance(); + final Management management = AuthMe.getInstance().getManagement(); if (Settings.emailRegistration && !Settings.getmailAccount.isEmpty()) { if (Settings.doubleEmailCheck) { if (commandArguments.getCount() < 2 || !commandArguments.get(0).equals(commandArguments.get(1))) { @@ -46,7 +47,7 @@ public class RegisterCommand extends ExecutableCommand { } RandomString rand = new RandomString(Settings.getRecoveryPassLength); final String thePass = rand.nextString(); - plugin.management.performRegister(player, thePass, email); + management.performRegister(player, thePass, email); return true; } if (commandArguments.getCount() > 1 && Settings.getEnablePasswordVerifier) @@ -54,7 +55,7 @@ public class RegisterCommand extends ExecutableCommand { m.send(player, "password_error"); return true; } - plugin.management.performRegister(player, commandArguments.get(0), ""); + management.performRegister(player, commandArguments.get(0), ""); return true; } } diff --git a/src/test/java/fr/xephi/authme/AuthMeMockUtil.java b/src/test/java/fr/xephi/authme/AuthMeMockUtil.java index 6f4ca7a4..905c4630 100644 --- a/src/test/java/fr/xephi/authme/AuthMeMockUtil.java +++ b/src/test/java/fr/xephi/authme/AuthMeMockUtil.java @@ -1,5 +1,6 @@ package fr.xephi.authme; +import fr.xephi.authme.settings.Messages; import org.mockito.Mockito; import java.lang.reflect.Field; @@ -14,17 +15,35 @@ public final class AuthMeMockUtil { } /** - * Set the AuthMe plugin instance to a mock object. Use {@link AuthMe#getInstance()} to retrieve the mock. + * Sets the AuthMe plugin instance to a mock object. Use {@link AuthMe#getInstance()} to retrieve the mock. */ - public static void initialize() { + public static void mockAuthMeInstance() { AuthMe mock = Mockito.mock(AuthMe.class); + mockSingletonForClass(AuthMe.class, "plugin", mock); + } + /** + * Creates a mock Messages object for the instance returned from {@link Messages#getInstance()}. + */ + public static void mockMessagesInstance() { + Messages mock = Mockito.mock(Messages.class); + mockSingletonForClass(Messages.class, "singleton", mock); + } + + /** + * Sets a field of a class to the given mock. + * + * @param clazz the class to modify + * @param fieldName the field name + * @param mock the mock to set for the given field + */ + private static void mockSingletonForClass(Class clazz, String fieldName, Object mock) { try { - Field instance = AuthMe.class.getDeclaredField("plugin"); + Field instance = clazz.getDeclaredField(fieldName); instance.setAccessible(true); instance.set(null, mock); } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException("Could not initialize AuthMe mock", e); + throw new RuntimeException("Could not set mock instance for class " + clazz.getName(), e); } } } diff --git a/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java index b8c3c2f5..5ed42876 100644 --- a/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java @@ -12,8 +12,10 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; - -import static org.mockito.Matchers.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -26,7 +28,7 @@ public class LoginCommandTest { @Before public void initializeAuthMeMock() { - AuthMeMockUtil.initialize(); + AuthMeMockUtil.mockAuthMeInstance(); AuthMe pluginMock = AuthMe.getInstance(); Settings.captchaLength = 10; diff --git a/src/test/java/fr/xephi/authme/command/executable/logout/LogoutCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/logout/LogoutCommandTest.java new file mode 100644 index 00000000..8416b5fd --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/logout/LogoutCommandTest.java @@ -0,0 +1,62 @@ +package fr.xephi.authme.command.executable.logout; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.AuthMeMockUtil; +import fr.xephi.authme.command.CommandParts; +import fr.xephi.authme.process.Management; +import fr.xephi.authme.settings.Settings; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; + +/** + * Test for {@link LogoutCommand}. + */ +public class LogoutCommandTest { + + private static Management managementMock; + + @Before + public void initializeAuthMeMock() { + AuthMeMockUtil.mockAuthMeInstance(); + AuthMe pluginMock = AuthMe.getInstance(); + + Settings.captchaLength = 10; + managementMock = mock(Management.class); + Mockito.when(pluginMock.getManagement()).thenReturn(managementMock); + } + + @Test + public void shouldStopIfSenderIsNotAPlayer() { + // given + CommandSender sender = mock(BlockCommandSender.class); + LogoutCommand command = new LogoutCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + Mockito.verify(managementMock, never()).performLogout(any(Player.class)); + } + + @Test + public void shouldCallManagementForPlayerCaller() { + // given + Player sender = mock(Player.class); + LogoutCommand command = new LogoutCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts("password")); + + // then + Mockito.verify(managementMock).performLogout(sender); + } + +} diff --git a/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java new file mode 100644 index 00000000..0b2ba2cd --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java @@ -0,0 +1,88 @@ +package fr.xephi.authme.command.executable.register; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.AuthMeMockUtil; +import fr.xephi.authme.command.CommandParts; +import fr.xephi.authme.process.Management; +import fr.xephi.authme.settings.Messages; +import fr.xephi.authme.settings.Settings; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link RegisterCommand}. + */ +public class RegisterCommandTest { + + private static Management managementMock; + private static Messages messagesMock; + + @Before + public void initializeAuthMeMock() { + AuthMeMockUtil.mockMessagesInstance(); + messagesMock = Messages.getInstance(); + + AuthMeMockUtil.mockAuthMeInstance(); + AuthMe pluginMock = AuthMe.getInstance(); + + Settings.captchaLength = 10; + managementMock = mock(Management.class); + Mockito.when(pluginMock.getManagement()).thenReturn(managementMock); + } + + @Test + public void shouldNotRunForNonPlayerSender() { + // given + CommandSender sender = mock(BlockCommandSender.class); + RegisterCommand command = new RegisterCommand(); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + verify(sender).sendMessage(messageCaptor.capture()); + assertThat(messageCaptor.getValue().contains("Player Only!"), equalTo(true)); + verify(managementMock, never()).performRegister(any(Player.class), anyString(), anyString()); + } + + @Test + public void shouldFailForEmptyArguments() { + // given + CommandSender sender = mock(Player.class); + RegisterCommand command = new RegisterCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + verify(messagesMock).send(sender, "usage_reg"); + verify(managementMock, never()).performRegister(any(Player.class), anyString(), anyString()); + } + + @Test + public void shouldForwardRegister() { + // given + Player sender = mock(Player.class); + RegisterCommand command = new RegisterCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts("password")); + + // then + verify(managementMock).performRegister(sender, "password", ""); + } +} From 115680a363e81424158ac9c8c59c24223d4c4144 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 11:16:31 +0100 Subject: [PATCH 045/199] Create test for HelpSyntaxHelperTest (cherry picked from commit 9a6e96d) --- .../authme/command/CommandDescription.java | 1 + .../xephi/authme/command/CommandManager.java | 1 + .../authme/command/help/HelpSyntaxHelper.java | 16 +- .../java/fr/xephi/authme/util/ListUtils.java | 19 --- .../command/help/HelpSyntaxHelperTest.java | 152 ++++++++++++++++++ 5 files changed, 164 insertions(+), 25 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/command/help/HelpSyntaxHelperTest.java diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java index 51ce67b0..4feec54d 100644 --- a/src/main/java/fr/xephi/authme/command/CommandDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandDescription.java @@ -681,6 +681,7 @@ public class CommandDescription { * * @param maximumArguments True if there is an argument maximum, based on the number of registered arguments. */ + // TODO ljacqu 20151121: Rename the setter public void setMaximumArguments(boolean maximumArguments) { this.noArgumentMaximum = !maximumArguments; } diff --git a/src/main/java/fr/xephi/authme/command/CommandManager.java b/src/main/java/fr/xephi/authme/command/CommandManager.java index 162d61d4..be5b28bf 100644 --- a/src/main/java/fr/xephi/authme/command/CommandManager.java +++ b/src/main/java/fr/xephi/authme/command/CommandManager.java @@ -54,6 +54,7 @@ public class CommandManager { /** * Register all commands. */ + // TODO ljacqu 20151121: Create a builder class for CommandDescription @SuppressWarnings({ "serial" }) public void registerCommands() { // Register the base AuthMe Reloaded command diff --git a/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java b/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java index 55f2581a..5f564749 100644 --- a/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java +++ b/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java @@ -9,7 +9,11 @@ import fr.xephi.authme.util.ListUtils; /** */ -public class HelpSyntaxHelper { +public final class HelpSyntaxHelper { + + private HelpSyntaxHelper() { + // Helper class + } /** * Get the proper syntax for a command. @@ -19,8 +23,8 @@ public class HelpSyntaxHelper { * @param alternativeLabel The alternative label to use for this command syntax. * @param highlight True to highlight the important parts of this command. * - - * @return The command with proper syntax. */ + * @return The command with proper syntax. + */ @SuppressWarnings("StringConcatenationInsideStringBufferAppend") public static String getCommandSyntax(CommandDescription commandDescription, CommandParts commandReference, String alternativeLabel, boolean highlight) { // Create a string builder to build the command @@ -35,9 +39,9 @@ public class HelpSyntaxHelper { String commandLabel = helpCommandReference.get(helpCommandReference.getCount() - 1); // Check whether the alternative label should be used - if(alternativeLabel != null) - if(alternativeLabel.trim().length() > 0) - commandLabel = alternativeLabel; + if (alternativeLabel != null && alternativeLabel.trim().length() > 0) { + commandLabel = alternativeLabel; + } // Show the important bit of the command, highlight this part if required sb.append(ListUtils.implode(parentCommand, (highlight ? ChatColor.YELLOW + "" + ChatColor.BOLD : "") + commandLabel, " ")); diff --git a/src/main/java/fr/xephi/authme/util/ListUtils.java b/src/main/java/fr/xephi/authme/util/ListUtils.java index 05ede1ba..69ed092e 100644 --- a/src/main/java/fr/xephi/authme/util/ListUtils.java +++ b/src/main/java/fr/xephi/authme/util/ListUtils.java @@ -37,25 +37,6 @@ public class ListUtils { return sb.toString(); } - /** - * Implode two lists of elements into a single string, with a specified separator. - * - * @param elements The first list of elements to implode. - * @param otherElements The second list of elements to implode. - * @param separator The separator to use. - * - - * @return The result string. */ - public static String implode(List elements, List otherElements, String separator) { - // Combine the lists - List combined = new ArrayList<>(); - combined.addAll(elements); - combined.addAll(otherElements); - - // Implode and return the result - return implode(combined, separator); - } - /** * Implode two elements into a single string, with a specified separator. * diff --git a/src/test/java/fr/xephi/authme/command/help/HelpSyntaxHelperTest.java b/src/test/java/fr/xephi/authme/command/help/HelpSyntaxHelperTest.java new file mode 100644 index 00000000..3fba7025 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/help/HelpSyntaxHelperTest.java @@ -0,0 +1,152 @@ +package fr.xephi.authme.command.help; + +import fr.xephi.authme.command.CommandArgumentDescription; +import fr.xephi.authme.command.CommandDescription; +import fr.xephi.authme.command.CommandParts; +import fr.xephi.authme.command.executable.authme.AuthMeCommand; +import fr.xephi.authme.command.executable.authme.RegisterCommand; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; + +import static java.util.Collections.singletonList; +import static org.bukkit.ChatColor.BOLD; +import static org.bukkit.ChatColor.ITALIC; +import static org.bukkit.ChatColor.WHITE; +import static org.bukkit.ChatColor.YELLOW; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link HelpSyntaxHelper}. + */ +public class HelpSyntaxHelperTest { + + @Test + public void shouldFormatSimpleCommand() { + // given + CommandDescription description = getDescription(); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), "", false); + + // then + assertThat(result, equalTo(WHITE + "/authme register" + ITALIC + " [name]")); + } + + @Test + public void shouldFormatSimpleCommandWithOptionalParam() { + // given + CommandDescription description = getDescription(); + description.setArguments(singletonList( + new CommandArgumentDescription("test", "", false))); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), null, false); + + // then + assertThat(result, equalTo(WHITE + "/authme register" + ITALIC + " ")); + } + + @Test + public void shouldFormatCommandWithMultipleParams() { + // given + CommandDescription description = getDescription(); + description.setArguments(Arrays.asList( + new CommandArgumentDescription("name", "", true), + new CommandArgumentDescription("test", "", false))); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), "", false); + + // then + assertThat(result, equalTo(WHITE + "/authme register" + ITALIC + " [name]" + ITALIC + " ")); + } + + @Test + public void shouldHighlightCommandWithMultipleParams() { + // given + CommandDescription description = getDescription(); + description.setArguments(Arrays.asList( + new CommandArgumentDescription("name", "", true), + new CommandArgumentDescription("test", "", false))); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), "", true); + + // then + assertThat(result, equalTo(WHITE + "/authme " + + YELLOW + BOLD + "register" + + YELLOW + ITALIC + " [name]" + ITALIC + " ")); + } + + @Test + public void shouldHighlightCommandWithNoParams() { + // given + CommandDescription description = getDescription(); + description.setArguments(new ArrayList()); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), null, true); + + // then + assertThat(result, equalTo(WHITE + "/authme " + YELLOW + BOLD + "register" + YELLOW)); + } + + @Test + public void shouldFormatSimpleCommandWithAlternativeLabel() { + // given + CommandDescription description = getDescription(); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), "alt", false); + + // then + assertThat(result, equalTo(WHITE + "/authme alt" + ITALIC + " [name]")); + } + + @Test + public void shouldHighlightCommandWithAltLabelAndUnlimitedArguments() { + // given + CommandDescription description = getDescription(); + description.setArguments(Arrays.asList( + new CommandArgumentDescription("name", "", true), + new CommandArgumentDescription("test", "", false))); + description.setMaximumArguments(false); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), "test", true); + + // then + assertThat(result, equalTo(WHITE + "/authme " + + YELLOW + BOLD + "test" + + YELLOW + ITALIC + " [name]" + ITALIC + " " + ITALIC + " ...")); + } + + + private static CommandDescription getDescription() { + CommandDescription base = new CommandDescription(new AuthMeCommand(), + singletonList("authme"), + "Base command", + "AuthMe base command", + null); + CommandArgumentDescription userArg = new CommandArgumentDescription( + "name", "", true); + + return new CommandDescription( + new RegisterCommand(), + Arrays.asList("register", "r"), + "Register a player", + "Register the specified player with the specified password.", + base, + singletonList(userArg)); + } +} From 4702a1b82d4774c50261f7791e658eb5b1cc88b2 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 11:57:04 +0100 Subject: [PATCH 046/199] Merge ListUtil into StringUtil; refactor HelpSyntaxHelper + create test The HelpSyntaxHelper had suppressed warnings for string concatenation within StringBuilder - the point of the StringBuilder is that it is faster when you use it to concatenate many elements. If you still use string concatenation with + within these calls it beats the purpose. (cherry picked from commit bb00be2) --- .../fr/xephi/authme/command/CommandParts.java | 15 ++-- .../authme/command/help/HelpSyntaxHelper.java | 63 ++++++++------ .../java/fr/xephi/authme/util/ListUtils.java | 58 ------------- .../fr/xephi/authme/util/StringUtils.java | 61 ++++++++++--- .../authme/command/CommandPartsTest.java | 38 ++++++++ .../fr/xephi/authme/util/StringUtilsTest.java | 87 +++++++++++++++++++ 6 files changed, 220 insertions(+), 102 deletions(-) delete mode 100644 src/main/java/fr/xephi/authme/util/ListUtils.java create mode 100644 src/test/java/fr/xephi/authme/command/CommandPartsTest.java create mode 100644 src/test/java/fr/xephi/authme/util/StringUtilsTest.java diff --git a/src/main/java/fr/xephi/authme/command/CommandParts.java b/src/main/java/fr/xephi/authme/command/CommandParts.java index 2c67f7b1..9e50cc16 100644 --- a/src/main/java/fr/xephi/authme/command/CommandParts.java +++ b/src/main/java/fr/xephi/authme/command/CommandParts.java @@ -3,7 +3,6 @@ package fr.xephi.authme.command; import java.util.ArrayList; import java.util.List; -import fr.xephi.authme.util.ListUtils; import fr.xephi.authme.util.StringUtils; /** @@ -165,8 +164,8 @@ public class CommandParts { * * @param other The other reference. * - - * @return The result from zero to above. A negative number will be returned on error. */ + * @return The result from zero to above. A negative number will be returned on error. + */ public double getDifference(CommandParts other) { return getDifference(other, false); } @@ -177,8 +176,8 @@ public class CommandParts { * @param other The other reference. * @param fullCompare True to compare the full references as far as the range reaches. * - - * @return The result from zero to above. A negative number will be returned on error. */ + * @return The result from zero to above. A negative number will be returned on error. + */ public double getDifference(CommandParts other, boolean fullCompare) { // Make sure the other reference is correct if(other == null) @@ -196,10 +195,10 @@ public class CommandParts { /** * Convert the parts to a string. * - - * @return The part as a string. */ + * @return The part as a string. + */ @Override public String toString() { - return ListUtils.implode(this.parts, " "); + return StringUtils.join(" ", this.parts); } } diff --git a/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java b/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java index 5f564749..0ff99a5c 100644 --- a/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java +++ b/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java @@ -1,13 +1,15 @@ package fr.xephi.authme.command.help; +import fr.xephi.authme.util.StringUtils; import org.bukkit.ChatColor; import fr.xephi.authme.command.CommandArgumentDescription; import fr.xephi.authme.command.CommandDescription; import fr.xephi.authme.command.CommandParts; -import fr.xephi.authme.util.ListUtils; /** + * Helper class for formatting a command's structure (name and arguments) + * for a Minecraft user. */ public final class HelpSyntaxHelper { @@ -16,52 +18,63 @@ public final class HelpSyntaxHelper { } /** - * Get the proper syntax for a command. + * Get the formatted syntax for a command. * - * @param commandDescription The command to get the syntax for. + * @param commandDescription The command to build the syntax for. * @param commandReference The reference of the command. * @param alternativeLabel The alternative label to use for this command syntax. * @param highlight True to highlight the important parts of this command. * * @return The command with proper syntax. */ - @SuppressWarnings("StringConcatenationInsideStringBufferAppend") - public static String getCommandSyntax(CommandDescription commandDescription, CommandParts commandReference, String alternativeLabel, boolean highlight) { - // Create a string builder to build the command - StringBuilder sb = new StringBuilder(); - - // Set the color and prefix a slash - sb.append(ChatColor.WHITE + "/"); + public static String getCommandSyntax(CommandDescription commandDescription, CommandParts commandReference, + String alternativeLabel, boolean highlight) { + // Create a string builder with white color and prefixed slash + StringBuilder sb = new StringBuilder() + .append(ChatColor.WHITE) + .append("/"); // Get the help command reference, and the command label CommandParts helpCommandReference = commandDescription.getCommandReference(commandReference); - final String parentCommand = (new CommandParts(helpCommandReference.getRange(0, helpCommandReference.getCount() - 1))).toString(); - String commandLabel = helpCommandReference.get(helpCommandReference.getCount() - 1); + final String parentCommand = new CommandParts( + helpCommandReference.getRange(0, helpCommandReference.getCount() - 1)).toString(); // Check whether the alternative label should be used - if (alternativeLabel != null && alternativeLabel.trim().length() > 0) { + String commandLabel; + if (StringUtils.isEmpty(alternativeLabel)) { + commandLabel = helpCommandReference.get(helpCommandReference.getCount() - 1); + } else { commandLabel = alternativeLabel; } // Show the important bit of the command, highlight this part if required - sb.append(ListUtils.implode(parentCommand, (highlight ? ChatColor.YELLOW + "" + ChatColor.BOLD : "") + commandLabel, " ")); - if(highlight) - sb.append(ChatColor.YELLOW); + sb.append(parentCommand) + .append(" ") + .append(highlight ? ChatColor.YELLOW.toString() + ChatColor.BOLD : "") + .append(commandLabel); - // Add each command arguments - for(CommandArgumentDescription arg : commandDescription.getArguments()) { - // Add the argument as optional or non-optional argument - if(!arg.isOptional()) - sb.append(ChatColor.ITALIC + " <" + arg.getLabel() + ">"); - else - sb.append(ChatColor.ITALIC + " [" + arg.getLabel() + "]"); + if (highlight) { + sb.append(ChatColor.YELLOW); + } + + // Add each command argument + for (CommandArgumentDescription arg : commandDescription.getArguments()) { + sb.append(ChatColor.ITALIC).append(formatArgument(arg)); } // Add some dots if the command allows unlimited arguments - if(commandDescription.getMaximumArguments() < 0) - sb.append(ChatColor.ITALIC + " ..."); + if (commandDescription.getMaximumArguments() < 0) { + sb.append(ChatColor.ITALIC).append(" ..."); + } // Return the build command syntax return sb.toString(); } + + private static String formatArgument(CommandArgumentDescription argument) { + if (argument.isOptional()) { + return " [" + argument.getLabel() + "]"; + } + return " <" + argument.getLabel() + ">"; + } } diff --git a/src/main/java/fr/xephi/authme/util/ListUtils.java b/src/main/java/fr/xephi/authme/util/ListUtils.java deleted file mode 100644 index 69ed092e..00000000 --- a/src/main/java/fr/xephi/authme/util/ListUtils.java +++ /dev/null @@ -1,58 +0,0 @@ -package fr.xephi.authme.util; - -import java.util.ArrayList; -import java.util.List; - -/** - */ -public class ListUtils { - - /** - * Implode a list of elements into a single string, with a specified separator. - * - * @param elements The elements to implode. - * @param separator The separator to use. - * - - * @return The result string. */ - public static String implode(List elements, String separator) { - // Create a string builder - StringBuilder sb = new StringBuilder(); - - // Append each element - for(String element : elements) { - // Make sure the element isn't empty - if(element.trim().length() == 0) - continue; - - // Prefix the separator if it isn't the first element - if(sb.length() > 0) - sb.append(separator); - - // Append the element - sb.append(element); - } - - // Return the result - return sb.toString(); - } - - /** - * Implode two elements into a single string, with a specified separator. - * - * @param element The first element to implode. - * @param otherElement The second element to implode. - * @param separator The separator to use. - * - - * @return The result string. */ - public static String implode(String element, String otherElement, String separator) { - // Combine the lists - List combined = new ArrayList<>(); - combined.add(element); - combined.add(otherElement); - - // Implode and return the result - return implode(combined, separator); - } -} diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java index c84fc74a..f0dd3d9c 100644 --- a/src/main/java/fr/xephi/authme/util/StringUtils.java +++ b/src/main/java/fr/xephi/authme/util/StringUtils.java @@ -5,17 +5,18 @@ import net.ricecode.similarity.StringSimilarityService; import net.ricecode.similarity.StringSimilarityServiceImpl; /** + * Utility class for String operations. */ public class StringUtils { /** * Get the difference of two strings. * - * @param first First string. - * @param second Second string. + * @param first First string + * @param second Second string * - - * @return The difference value. */ + * @return The difference value + */ public static double getDifference(String first, String second) { // Make sure the strings are valid. if(first == null || second == null) @@ -27,22 +28,60 @@ public class StringUtils { // Determine the difference value, return the result return Math.abs(service.score(first, second) - 1.0); } - + /** - * Method containsAny. - * @param str String - * @param pieces String[] - - * @return boolean */ + * Returns whether the given string contains any of the provided elements. + * + * @param str the string to analyze + * @param pieces the items to check the string for + * + * @return true if the string contains at least one of the items + */ public static boolean containsAny(String str, String... pieces) { if (str == null) { return false; } for (String piece : pieces) { - if (str.contains(piece)) { + if (piece != null && str.contains(piece)) { return true; } } return false; } + + /** + * Null-safe method for checking whether a string is empty. Note that the string + * is trimmed, so this method also considers a string with whitespace as empty. + * + * @param str the string to verify + * + * @return true if the string is empty, false otherwise + */ + public static boolean isEmpty(String str) { + return str == null || str.trim().isEmpty(); + } + + /** + * Joins a list of elements into a single string with the specified delimiter. + * + * @param delimiter the delimiter to use + * @param elements the elements to join + * + * @return a new String that is composed of the elements separated by the delimiter + */ + public static String join(String delimiter, Iterable elements) { + StringBuilder sb = new StringBuilder(); + for (String element : elements) { + if (!isEmpty(element)) { + // Add the separator if it isn't the first element + if (sb.length() > 0) { + sb.append(delimiter); + } + sb.append(element); + } + } + + return sb.toString(); + } + } diff --git a/src/test/java/fr/xephi/authme/command/CommandPartsTest.java b/src/test/java/fr/xephi/authme/command/CommandPartsTest.java new file mode 100644 index 00000000..62d22b5f --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/CommandPartsTest.java @@ -0,0 +1,38 @@ +package fr.xephi.authme.command; + +import org.junit.Test; + +import java.util.Arrays; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link CommandParts}. + */ +public class CommandPartsTest { + + @Test + public void shouldPrintPartsForStringRepresentation() { + // given + CommandParts parts = new CommandParts(Arrays.asList("some", "parts", "for", "test")); + + // when + String str = parts.toString(); + + // then + assertThat(str, equalTo("some parts for test")); + } + + @Test + public void shouldPrintEmptyStringForNoArguments() { + // given + CommandParts parts = new CommandParts(); + + // when + String str = parts.toString(); + + // then + assertThat(str, equalTo("")); + } +} diff --git a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java new file mode 100644 index 00000000..51cd9677 --- /dev/null +++ b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java @@ -0,0 +1,87 @@ +package fr.xephi.authme.util; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Test for {@link StringUtils}. + */ +public class StringUtilsTest { + + @Test + public void shouldFindContainedItem() { + // given + String text = "This is a test of containsAny()"; + String piece = "test"; + + // when + boolean result = StringUtils.containsAny(text, "some", "words", "that", "do not", "exist", piece); + + // then + assertThat(result, equalTo(true)); + } + + @Test + public void shouldReturnFalseIfNoneFound() { + // given + String text = "This is a test string"; + + // when + boolean result = StringUtils.containsAny(text, "some", "other", "words", null); + + // then + assertThat(result, equalTo(false)); + } + + @Test + public void shouldReturnFalseForNullString() { + // given/when + boolean result = StringUtils.containsAny(null, "some", "words", "to", "check"); + + // then + assertThat(result, equalTo(false)); + } + + @Test + public void shouldCheckIsEmptyUtil() { + // Should be true for null/empty/whitespace + assertTrue(StringUtils.isEmpty(null)); + assertTrue(StringUtils.isEmpty("")); + assertTrue(StringUtils.isEmpty(" \t")); + + // Should be false if string has content + assertFalse(StringUtils.isEmpty("P")); + assertFalse(StringUtils.isEmpty(" test")); + } + + @Test + public void shouldJoinString() { + // given + List elements = Arrays.asList("test", "for", null, "join", "StringUtils"); + + // when + String result = StringUtils.join(", ", elements); + + // then + assertThat(result, equalTo("test, for, join, StringUtils")); + } + + @Test + public void shouldNotHaveDelimiter() { + // given + List elements = Arrays.asList(" ", null, "\t", "hello", null); + + // when + String result = StringUtils.join("-", elements); + + // then + assertThat(result, equalTo("hello")); + } +} From b633b9a005bb5523ced605b60e3e7d01a140d444 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 13:23:18 +0100 Subject: [PATCH 047/199] Create test for change password command --- .../changepassword/ChangePasswordCommand.java | 19 +- .../java/fr/xephi/authme/AuthMeMockUtil.java | 9 + .../ChangePasswordCommandTest.java | 166 ++++++++++++++++++ 3 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java diff --git a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java index 9b6956b2..e3ba71b4 100644 --- a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java @@ -17,17 +17,17 @@ public class ChangePasswordCommand extends ExecutableCommand { @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { + // Make sure the current command executor is a player + if (!(sender instanceof Player)) { + return true; + } + final Messages m = Messages.getInstance(); // Get the passwords String playerPass = commandArguments.get(0); String playerPassVerify = commandArguments.get(1); - // Make sure the current command executor is a player - if(!(sender instanceof Player)) { - return true; - } - // Get the player instance and make sure it's authenticated Player player = (Player) sender; String name = player.getName().toLowerCase(); @@ -37,6 +37,7 @@ public class ChangePasswordCommand extends ExecutableCommand { } // Make sure the password is allowed + // TODO ljacqu 20151121: The password confirmation appears to be never verified String playerPassLowerCase = playerPass.toLowerCase(); if (playerPassLowerCase.contains("delete") || playerPassLowerCase.contains("where") || playerPassLowerCase.contains("insert") || playerPassLowerCase.contains("modify") || playerPassLowerCase.contains("from") || playerPassLowerCase.contains("select") || playerPassLowerCase.contains(";") || playerPassLowerCase.contains("null") || !playerPassLowerCase.matches(Settings.getPassRegex)) { m.send(player, "password_error"); @@ -50,11 +51,9 @@ public class ChangePasswordCommand extends ExecutableCommand { m.send(player, "pass_len"); return true; } - if (!Settings.unsafePasswords.isEmpty()) { - if (Settings.unsafePasswords.contains(playerPassLowerCase)) { - m.send(player, "password_error_unsafe"); - return true; - } + if (!Settings.unsafePasswords.isEmpty() && Settings.unsafePasswords.contains(playerPassLowerCase)) { + m.send(player, "password_error_unsafe"); + return true; } // Set the password diff --git a/src/test/java/fr/xephi/authme/AuthMeMockUtil.java b/src/test/java/fr/xephi/authme/AuthMeMockUtil.java index 905c4630..f3ab8971 100644 --- a/src/test/java/fr/xephi/authme/AuthMeMockUtil.java +++ b/src/test/java/fr/xephi/authme/AuthMeMockUtil.java @@ -1,5 +1,6 @@ package fr.xephi.authme; +import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.settings.Messages; import org.mockito.Mockito; @@ -30,6 +31,14 @@ public final class AuthMeMockUtil { mockSingletonForClass(Messages.class, "singleton", mock); } + /** + * Creates a mock singleton for the player cache, retrievable from {@link PlayerCache#getInstance()}. + */ + public static void mockPlayerCacheInstance() { + PlayerCache mock = Mockito.mock(PlayerCache.class); + mockSingletonForClass(PlayerCache.class, "singleton", mock); + } + /** * Sets a field of a class to the given mock. * diff --git a/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java new file mode 100644 index 00000000..d991797e --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java @@ -0,0 +1,166 @@ +package fr.xephi.authme.command.executable.changepassword; + +import fr.xephi.authme.AuthMeMockUtil; +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.command.CommandParts; +import fr.xephi.authme.settings.Messages; +import fr.xephi.authme.settings.Settings; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import org.junit.Before; +import org.junit.Test; + + +import java.util.Arrays; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test for {@link ChangePasswordCommand}. + */ +public class ChangePasswordCommandTest { + + private Messages messagesMock; + private PlayerCache cacheMock; + + @Before + public void setUpMocks() { + AuthMeMockUtil.mockAuthMeInstance(); + AuthMeMockUtil.mockPlayerCacheInstance(); + cacheMock = PlayerCache.getInstance(); + + AuthMeMockUtil.mockMessagesInstance(); + messagesMock = Messages.getInstance(); + + // Only allow passwords with alphanumerical characters for the test + Settings.getPassRegex = "[a-zA-Z0-9]+"; + Settings.getPasswordMinLen = 2; + Settings.passwordMaxLength = 50; + + // TODO ljacqu 20151121: Cannot mock getServer() as it's final + // Probably the Command class should delegate as the others do + /* + Server server = Mockito.mock(Server.class); + schedulerMock = Mockito.mock(BukkitScheduler.class); + Mockito.when(server.getScheduler()).thenReturn(schedulerMock); + Mockito.when(pluginMock.getServer()).thenReturn(server); + */ + } + + @Test + public void shouldRejectNonPlayerSender() { + // given + CommandSender sender = mock(BlockCommandSender.class); + ChangePasswordCommand command = new ChangePasswordCommand(); + CommandParts arguments = mock(CommandParts.class); + + // when + command.executeCommand(sender, new CommandParts(), arguments); + + // then + verify(arguments, never()).get(anyInt()); + //verify(pluginMock, never()).getServer(); + } + + @Test + public void shouldRejectNotLoggedInPlayer() { + // given + CommandSender sender = initPlayerWithName("name", false); + ChangePasswordCommand command = new ChangePasswordCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts("pass")); + + // then + verify(messagesMock).send(sender, "not_logged_in"); + //verify(pluginMock, never()).getServer(); + } + + @Test + public void shouldDenyInvalidPassword() { + // given + CommandSender sender = initPlayerWithName("name", true); + ChangePasswordCommand command = new ChangePasswordCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts("!pass")); + + // then + verify(messagesMock).send(sender, "password_error"); + //verify(pluginMock, never()).getServer(); + } + + + @Test + public void shouldRejectPasswordEqualToNick() { + // given + CommandSender sender = initPlayerWithName("tester", true); + ChangePasswordCommand command = new ChangePasswordCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts("Tester")); + + // then + verify(messagesMock).send(sender, "password_error_nick"); + //verify(pluginMock, never()).getServer(); + } + + @Test + public void shouldRejectTooLongPassword() { + // given + CommandSender sender = initPlayerWithName("abc12", true); + ChangePasswordCommand command = new ChangePasswordCommand(); + Settings.passwordMaxLength = 3; + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts("test")); + + // then + verify(messagesMock).send(sender, "pass_len"); + //verify(pluginMock, never()).getServer(); + } + + @Test + public void shouldRejectTooShortPassword() { + // given + CommandSender sender = initPlayerWithName("abc12", true); + ChangePasswordCommand command = new ChangePasswordCommand(); + Settings.getPasswordMinLen = 7; + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts("tester")); + + // then + verify(messagesMock).send(sender, "pass_len"); + //verify(pluginMock, never()).getServer(); + } + + @Test + public void shouldRejectUnsafeCustomPassword() { + // given + CommandSender sender = initPlayerWithName("player", true); + ChangePasswordCommand command = new ChangePasswordCommand(); + Settings.unsafePasswords = Arrays.asList("test", "abc123"); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts("abc123")); + + // then + verify(messagesMock).send(sender, "password_error_unsafe"); + //verify(pluginMock, never()).getServer(); + } + + private Player initPlayerWithName(String name, boolean loggedIn) { + Player player = mock(Player.class); + when(player.getName()).thenReturn(name); + when(cacheMock.isAuthenticated(name)).thenReturn(loggedIn); + return player; + } + +} From 400d014e7b9fe22fb54e718001904f7bf02295c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 14:41:14 +0100 Subject: [PATCH 048/199] Fixed setGroup in PermissionsManager for Essentials Group Manager and zPermissions --- .../xephi/authme/permission/PermissionsManager.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 104a986c..45f4e8cd 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -480,16 +480,17 @@ public class PermissionsManager { case ESSENTIALS_GROUP_MANAGER: // Essentials Group Manager - final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); + /*final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); if(handler == null) - return false; - // TODO: Write proper code here! - //return Arrays.asList(handler.getGroups(player.getName())); + return false;*/ + // Add the user to the group + // TODO: Clear the current list of groups? + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "manuadd " + player.getName() + " " + groupName); case Z_PERMISSIONS: //zPermissions - // TODO: Write proper code here! - //return new ArrayList(zPermissionsService.getPlayerGroups(player.getName())); + // Set the players group through the plugin commands + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " setgroup " + groupName); case VAULT: // Vault From 42dee2e10125ab560b16a85a07c10f49829518b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 14:44:43 +0100 Subject: [PATCH 049/199] Created addGroup method in permissions manager --- .../authme/permission/PermissionsManager.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 45f4e8cd..cacf74d2 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -442,6 +442,63 @@ public class PermissionsManager { } } + /** + * Add the permission group of a player, if supported. + * + * @param player The player + * @param groupName The name of the group. + * + * @return True if succeed, false otherwise. + */ + @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) + public boolean addGroup(Player player, String groupName) { + if(!isEnabled()) + // No permissions system is used, return false + return false; + + // Set the group the proper way + switch(this.permsType) { + case PERMISSIONS_EX: + // Permissions Ex + PermissionUser user = PermissionsEx.getUser(player); + user.addGroup(groupName); + return true; + + case PERMISSIONS_BUKKIT: + // Permissions Bukkit + // Permissions Bukkit doesn't support groups, return false + return false; + + case B_PERMISSIONS: + // bPermissions + ApiLayer.addGroup(player.getWorld().getName(), CalculableType.USER, player.getName(), groupName); + return true; + + case ESSENTIALS_GROUP_MANAGER: + // Essentials Group Manager + // Add the group to the user using a command + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "manuadd " + player.getName() + " " + groupName); + + case Z_PERMISSIONS: + // zPermissions + // Add the group to the user using a command + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " addgroup " + groupName); + + case VAULT: + // Vault + vaultPerms.playerAddGroup(player, groupName); + return true; + + case NONE: + // Not hooked into any permissions system, return false + return false; + + default: + // Something went wrong, return false + return false; + } + } + /** * Set the permission group of a player, if supported. * From f4da63fee6ca04244ef04417aa45485bb8dcee0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 14:57:34 +0100 Subject: [PATCH 050/199] Added note to getGroups method in permissions manager for PermissionsBukkit --- .../java/fr/xephi/authme/permission/PermissionsManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index cacf74d2..55d5dc58 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -410,7 +410,7 @@ public class PermissionsManager { case PERMISSIONS_BUKKIT: // Permissions Bukkit - // Permissions Bukkit doesn't support group, return an empty list + // FIXME: Add support for this! return new ArrayList<>(); case B_PERMISSIONS: From a6fe728d796ccfd674b458b1e8ead3c89a32b340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 15:00:14 +0100 Subject: [PATCH 051/199] Fixed addGroup method for PermissionsBukkit in permissions manager --- .../java/fr/xephi/authme/permission/PermissionsManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 55d5dc58..ef7bda49 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -466,8 +466,8 @@ public class PermissionsManager { case PERMISSIONS_BUKKIT: // Permissions Bukkit - // Permissions Bukkit doesn't support groups, return false - return false; + // Add the group to the user using a command + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player addgroup " + player.getName() + " " + groupName); case B_PERMISSIONS: // bPermissions From b07e4b62ccc04ff18030ad488a11d447a5e80c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 15:07:46 +0100 Subject: [PATCH 052/199] Created addGroups method in permissions manager --- .../authme/permission/PermissionsManager.java | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index ef7bda49..6830622a 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -339,8 +339,8 @@ public class PermissionsManager { * @return True if the player has permission. */ public boolean hasPermission(Player player, String permsNode, boolean def) { + // If no permissions system is used, return the default value if(!isEnabled()) - // No permissions system is used, return default return def; switch(this.permsType) { @@ -394,12 +394,12 @@ public class PermissionsManager { * * @param player The player. * - * @return Permission groups. + * @return Permission groups, or an empty list if this feature is not supported. */ @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public List getGroups(Player player) { + // If no permissions system is used, return an empty list if(!isEnabled()) - // No permissions system is used, return an empty list return new ArrayList<>(); switch(this.permsType) { @@ -449,11 +449,12 @@ public class PermissionsManager { * @param groupName The name of the group. * * @return True if succeed, false otherwise. + * False is also returned if this feature isn't supported for the current permissions system. */ @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public boolean addGroup(Player player, String groupName) { + // If no permissions system is used, return false if(!isEnabled()) - // No permissions system is used, return false return false; // Set the group the proper way @@ -499,6 +500,31 @@ public class PermissionsManager { } } + /** + * Add the permission group of a player, if supported. + * + * @param player The player + * @param groupNames The name of the groups to add. + * + * @return True if succeed, false otherwise. + * False is also returned if this feature isn't supported for the current permissions system. + */ + @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) + public boolean addGroups(Player player, List groupNames) { + // If no permissions system is used, return false + if(!isEnabled()) + return false; + + // Add each group to the user + boolean result = true; + for(String groupName : groupNames) + if(!addGroup(player, groupName)) + result = false; + + // Return the result + return result; + } + /** * Set the permission group of a player, if supported. * @@ -506,11 +532,12 @@ public class PermissionsManager { * @param groupName The name of the group. * * @return True if succeed, false otherwise. + * False is also returned if this feature isn't supported for the current permissions system. */ @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public boolean setGroup(Player player, String groupName) { + // If no permissions system is used, return false if(!isEnabled()) - // No permissions system is used, return false return false; // Create a list of group names From bcf4eeab00631133ba2c52d904e8d39973e985a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 15:15:04 +0100 Subject: [PATCH 053/199] Created removeGroup method in permissions manager --- .../authme/permission/PermissionsManager.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 6830622a..0dbfc52f 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -525,6 +525,64 @@ public class PermissionsManager { return result; } + /** + * Remove the permission group of a player, if supported. + * + * @param player The player + * @param groupName The name of the group. + * + * @return True if succeed, false otherwise. + * False is also returned if this feature isn't supported for the current permissions system. + */ + @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) + public boolean removeGroup(Player player, String groupName) { + // If no permissions system is used, return false + if(!isEnabled()) + return false; + + // Set the group the proper way + switch(this.permsType) { + case PERMISSIONS_EX: + // Permissions Ex + PermissionUser user = PermissionsEx.getUser(player); + user.removeGroup(groupName); + return true; + + case PERMISSIONS_BUKKIT: + // Permissions Bukkit + // Remove the group to the user using a command + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player removegroup " + player.getName() + " " + groupName); + + case B_PERMISSIONS: + // bPermissions + ApiLayer.removeGroup(player.getWorld().getName(), CalculableType.USER, player.getName(), groupName); + return true; + + case ESSENTIALS_GROUP_MANAGER: + // Essentials Group Manager + // Remove the group to the user using a command + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "manudelsub " + player.getName() + " " + groupName); + + case Z_PERMISSIONS: + // zPermissions + // Remove the group to the user using a command + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " removegroup " + groupName); + + case VAULT: + // Vault + vaultPerms.playerRemoveGroup(player, groupName); + return true; + + case NONE: + // Not hooked into any permissions system, return false + return false; + + default: + // Something went wrong, return false + return false; + } + } + /** * Set the permission group of a player, if supported. * From 462a2e9878d159d22af8ace31f8620f2753e5712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 15:16:19 +0100 Subject: [PATCH 054/199] Created removeGroups method in permissions manager --- .../authme/permission/PermissionsManager.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 0dbfc52f..89e01241 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -501,7 +501,7 @@ public class PermissionsManager { } /** - * Add the permission group of a player, if supported. + * Add the permission groups of a player, if supported. * * @param player The player * @param groupNames The name of the groups to add. @@ -583,6 +583,31 @@ public class PermissionsManager { } } + /** + * Remove the permission groups of a player, if supported. + * + * @param player The player + * @param groupNames The name of the groups to add. + * + * @return True if succeed, false otherwise. + * False is also returned if this feature isn't supported for the current permissions system. + */ + @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) + public boolean removeGroups(Player player, List groupNames) { + // If no permissions system is used, return false + if(!isEnabled()) + return false; + + // Add each group to the user + boolean result = true; + for(String groupName : groupNames) + if(!removeGroup(player, groupName)) + result = false; + + // Return the result + return result; + } + /** * Set the permission group of a player, if supported. * From a84e219899a886834d222f88028ebaed3ebeb02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 15:18:01 +0100 Subject: [PATCH 055/199] Fixed minor Essentials Group Manager issue in addGroup method of permissions manager --- .../java/fr/xephi/authme/permission/PermissionsManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 89e01241..15d86a66 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -478,7 +478,7 @@ public class PermissionsManager { case ESSENTIALS_GROUP_MANAGER: // Essentials Group Manager // Add the group to the user using a command - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "manuadd " + player.getName() + " " + groupName); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "manuaddsub " + player.getName() + " " + groupName); case Z_PERMISSIONS: // zPermissions From f7f455a56ab49dd7f2488fe2802dac27f5d2b651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 15:24:53 +0100 Subject: [PATCH 056/199] Created setGroups method in permissions manager --- .../authme/permission/PermissionsManager.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 15d86a66..1edda2ea 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -610,6 +610,7 @@ public class PermissionsManager { /** * Set the permission group of a player, if supported. + * This clears the current groups of the player. * * @param player The player * @param groupName The name of the group. @@ -675,6 +676,41 @@ public class PermissionsManager { } } + /** + * Set the permission groups of a player, if supported. + * This clears the current groups of the player. + * + * @param player The player + * @param groupNames The name of the groups to set. + * + * @return True if succeed, false otherwise. + * False is also returned if this feature isn't supported for the current permissions system. + */ + @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) + public boolean setGroups(Player player, List groupNames) { + // If no permissions system is used or if there's no group supplied, return false + if(!isEnabled() || groupNames.size() <= 0) + return false; + + // Set the main group + if(!setGroup(player, groupNames.get(0))) + return false; + + // Add the rest of the groups + boolean result = true; + for(int i = 1; i < groupNames.size(); i++) { + // Get the group name + String groupName = groupNames.get(0); + + // Add this group + if(!addGroup(player, groupName)) + result = false; + } + + // Return the result + return result; + } + public enum PermissionsSystemType { NONE("None"), PERMISSIONS_EX("PermissionsEx"), From 1091db0e15a4f10db2828a7d294e895110035a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 15:34:38 +0100 Subject: [PATCH 057/199] Created removeAllGroups method in permissions manager --- .../authme/permission/PermissionsManager.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 1edda2ea..11b18475 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -711,6 +711,28 @@ public class PermissionsManager { return result; } + /** + * Remove all groups of the specified player, if supported. + * Systems like Essentials GroupManager don't allow all groups to be removed from a player, thus the user will stay + * in it's primary group. All the subgroups are removed just fine. + * + * @param player The player to remove all groups from. + * + * @return True if succeed, false otherwise. + * False will also be returned if this feature isn't supported for the used permissions system. + */ + public boolean removeAllGroups(Player player) { + // If no permissions system is used, return false + if(!isEnabled()) + return false; + + // Get a list of current groups + List groupNames = getGroups(player); + + // Remove each group + return removeGroups(player, groupNames); + } + public enum PermissionsSystemType { NONE("None"), PERMISSIONS_EX("PermissionsEx"), From a05a97a0a69f2dea8e70dfd7451c0acbd690ab89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 15:38:20 +0100 Subject: [PATCH 058/199] Fixed setGroup method in permissions manager --- .../authme/permission/PermissionsManager.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 11b18475..240f0381 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -638,8 +638,8 @@ public class PermissionsManager { case PERMISSIONS_BUKKIT: // Permissions Bukkit - // Permissions Bukkit doesn't support groups, return false - return false; + // Set the user's group using a command + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player setgroup " + player.getName() + " " + groupName); case B_PERMISSIONS: // bPermissions @@ -648,11 +648,8 @@ public class PermissionsManager { case ESSENTIALS_GROUP_MANAGER: // Essentials Group Manager - /*final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); - if(handler == null) - return false;*/ - // Add the user to the group - // TODO: Clear the current list of groups? + // Clear the list of groups, add the player to the specified group afterwards using a command + removeAllGroups(player); return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "manuadd " + player.getName() + " " + groupName); case Z_PERMISSIONS: @@ -662,7 +659,8 @@ public class PermissionsManager { case VAULT: // Vault - // TODO: Clear the current list of groups? + // Remove all current groups, add the player to the specified group afterwards + removeAllGroups(player); vaultPerms.playerAddGroup(player, groupName); return true; From f8cf9e2e48bcf0d6776d7f9cbc5a0b4c48addb7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 15:44:57 +0100 Subject: [PATCH 059/199] Created inGroup method in permissions manager --- .../authme/permission/PermissionsManager.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 240f0381..098082d8 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -442,6 +442,62 @@ public class PermissionsManager { } } + /** + * Check whether the player is in the specified group. + * + * @param player The player. + * @param groupName The group name. + * + * @return True if the player is in the specified group, false otherwise. + * False is also returned if groups aren't supported by the used permissions system. + */ + public boolean inGroup(Player player, String groupName) { + // If no permissions system is used, return false + if(!isEnabled()) + return false; + + switch(this.permsType) { + case PERMISSIONS_EX: + // Permissions Ex + PermissionUser user = PermissionsEx.getUser(player); + return user.inGroup(groupName); + + case PERMISSIONS_BUKKIT: + case Z_PERMISSIONS: + // Get the current list of groups + List groupNames = getGroups(player); + + // Check whether the list contains the group name, return the result + for(String entry : groupNames) + if(entry.equals(groupName)) + return true; + return false; + + case B_PERMISSIONS: + // bPermissions + return ApiLayer.hasGroup(player.getWorld().getName(), CalculableType.USER, player.getName(), groupName); + + case ESSENTIALS_GROUP_MANAGER: + // Essentials Group Manager + final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); + if(handler == null) + return false; + return handler.inGroup(player.getName(), groupName); + + case VAULT: + // Vault + return vaultPerms.playerInGroup(player, groupName); + + case NONE: + // Not hooked into any permissions system, return an empty list + return false; + + default: + // Something went wrong, return an empty list to prevent problems + return false; + } + } + /** * Add the permission group of a player, if supported. * From cfaece3eae8737122b77d0ed6229b34fef2f38bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 15:46:10 +0100 Subject: [PATCH 060/199] Simplefied some code --- .../java/fr/xephi/authme/permission/PermissionsManager.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 098082d8..a66f6dca 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -480,9 +480,7 @@ public class PermissionsManager { case ESSENTIALS_GROUP_MANAGER: // Essentials Group Manager final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); - if(handler == null) - return false; - return handler.inGroup(player.getName(), groupName); + return handler != null && handler.inGroup(player.getName(), groupName); case VAULT: // Vault From 973c683c90fc943a4de13c23d9feacb83209b74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 15:47:59 +0100 Subject: [PATCH 061/199] Minor fix for PermissionsBukkit support in permissions manager --- .../java/fr/xephi/authme/permission/PermissionsManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index a66f6dca..890b85e8 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -415,7 +415,7 @@ public class PermissionsManager { case B_PERMISSIONS: // bPermissions - return Arrays.asList(ApiLayer.getGroups(player.getName(), CalculableType.USER, player.getName())); + return Arrays.asList(ApiLayer.getGroups(player.getWorld().getName(), CalculableType.USER, player.getName())); case ESSENTIALS_GROUP_MANAGER: // Essentials Group Manager From 69d6518b30a8950e0f490e16652c0f724ecf1d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 16:03:00 +0100 Subject: [PATCH 062/199] Improved setGroup method in Utils class, to replace legacy permissions code --- src/main/java/fr/xephi/authme/util/Utils.java | 69 ++++++++++--------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 330e8d8e..af15b8e5 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -7,6 +7,7 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.cache.limbo.LimboPlayer; import fr.xephi.authme.events.AuthMeTeleportEvent; +import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.settings.Settings; import org.bukkit.Bukkit; import org.bukkit.GameMode; @@ -125,47 +126,49 @@ public class Utils { * @param group GroupType */ public static void setGroup(Player player, GroupType group) { - if (!Settings.isPermissionCheckEnabled) + if(!Settings.isPermissionCheckEnabled) return; - if (plugin.vaultGroupManagement == null) - return; - String currentGroup; - try { - currentGroup = plugin.vaultGroupManagement.getPrimaryGroup(player); - } catch (UnsupportedOperationException e) { - ConsoleLogger.showError("Your permission plugin (" + plugin.vaultGroupManagement.getName() + ") doesn't support the Group system... unhook!"); - plugin.vaultGroupManagement = null; - return; - } - switch (group) { - case UNREGISTERED: { - plugin.vaultGroupManagement.playerRemoveGroup(player, currentGroup); - plugin.vaultGroupManagement.playerAddGroup(player, Settings.unRegisteredGroup); + + // TODO: Make sure a groups system is used! + + // Get the permissions manager, and make sure it's valid + PermissionsManager permsMan = plugin.getPermissionsManager(); + if(permsMan == null) + ConsoleLogger.showError("Failed to access permissions manager instance, shutting down."); + assert permsMan != null; + + switch(group) { + case UNREGISTERED: + // Remove the other group type groups, set the current group + permsMan.removeGroups(player, Arrays.asList(Settings.getRegisteredGroup, Settings.getUnloggedinGroup)); + permsMan.addGroup(player, Settings.unRegisteredGroup); break; - } - case REGISTERED: { - plugin.vaultGroupManagement.playerRemoveGroup(player, currentGroup); - plugin.vaultGroupManagement.playerAddGroup(player, Settings.getRegisteredGroup); + + case REGISTERED: + // Remove the other group type groups, set the current group + permsMan.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getUnloggedinGroup)); + permsMan.addGroup(player, Settings.getRegisteredGroup); break; - } - case NOTLOGGEDIN: { - if (!useGroupSystem()) - break; - plugin.vaultGroupManagement.playerRemoveGroup(player, currentGroup); - plugin.vaultGroupManagement.playerAddGroup(player, Settings.getUnloggedinGroup); + + case NOTLOGGEDIN: + // Remove the other group type groups, set the current group + permsMan.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getRegisteredGroup)); + permsMan.addGroup(player, Settings.getUnloggedinGroup); break; - } - case LOGGEDIN: { - if (!useGroupSystem()) - break; + + case LOGGEDIN: + // Get the limbo player data LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(player.getName().toLowerCase()); - if (limbo == null) + if(limbo == null) break; + + // Get the players group String realGroup = limbo.getGroup(); - plugin.vaultGroupManagement.playerRemoveGroup(player, currentGroup); - plugin.vaultGroupManagement.playerAddGroup(player, realGroup); + + // Remove the other group types groups, set the real group + permsMan.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getRegisteredGroup, Settings.getUnloggedinGroup)); + permsMan.addGroup(player, realGroup); break; - } } } From eaba2765fa2197221365d055388a9d79e4fce931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 16:07:02 +0100 Subject: [PATCH 063/199] Created hasGroupSupport method in permissions manager --- .../authme/permission/PermissionsManager.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 890b85e8..0b075d45 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -389,6 +389,40 @@ public class PermissionsManager { } } + /** + * Check whether the current permissions system has group support. + * If no permissions system is hooked, false will be returned. + * + * @return True if the current permissions system supports groups, false otherwise. + */ + public boolean hasGroupSupport() { + // If no permissions system is used, return false + if(!isEnabled()) + return false; + + switch(this.permsType) { + case PERMISSIONS_EX: + case PERMISSIONS_BUKKIT: + case B_PERMISSIONS: + case ESSENTIALS_GROUP_MANAGER: + case Z_PERMISSIONS: + case PERMISSIONS: + return true; + + case VAULT: + // Vault + return vaultPerms.hasGroupSupport(); + + case NONE: + // Not hooked into any permissions system, return false + return false; + + default: + // Something went wrong, return false to prevent problems + return false; + } + } + /** * Get the permission groups of a player, if available. * From 6dc406656312c511fa526936ce37ea47c6e88168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 16:08:41 +0100 Subject: [PATCH 064/199] Added group support check to setGroups method in Utils class --- src/main/java/fr/xephi/authme/util/Utils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index af15b8e5..278cf41a 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -129,14 +129,16 @@ public class Utils { if(!Settings.isPermissionCheckEnabled) return; - // TODO: Make sure a groups system is used! - // Get the permissions manager, and make sure it's valid PermissionsManager permsMan = plugin.getPermissionsManager(); if(permsMan == null) ConsoleLogger.showError("Failed to access permissions manager instance, shutting down."); assert permsMan != null; + // Make sure group support is available + if(!permsMan.hasGroupSupport()) + ConsoleLogger.showError("The current permissions system doesn't have group support, unable to set group!"); + switch(group) { case UNREGISTERED: // Remove the other group type groups, set the current group From bb22daab336384e520b9cf5c8e8893811797b380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 16:21:38 +0100 Subject: [PATCH 065/199] Added some missing support for Nijikokun's Permissions in permissions manager --- .../authme/permission/PermissionsManager.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 0b075d45..b0e8700d 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -1,5 +1,6 @@ package fr.xephi.authme.permission; +import com.nijiko.permissions.Group; import com.nijiko.permissions.PermissionHandler; import com.nijikokun.bukkit.Permissions.Permissions; import de.bananaco.bpermissions.api.ApiLayer; @@ -466,6 +467,18 @@ public class PermissionsManager { // Vault return Arrays.asList(vaultPerms.getPlayerGroups(player)); + case PERMISSIONS: + // Permissions + // Create a list to put the groups in + List groups = new ArrayList<>(); + + // Get the groups and add each to the list + for(Group group : this.defaultPerms.getGroups(player.getName())) + groups.add(group.getName()); + + // Return the groups + return groups; + case NONE: // Not hooked into any permissions system, return an empty list return new ArrayList<>(); @@ -520,6 +533,10 @@ public class PermissionsManager { // Vault return vaultPerms.playerInGroup(player, groupName); + case PERMISSIONS: + // Permissions + return this.defaultPerms.inGroup(player.getWorld().getName(), player.getName(), groupName); + case NONE: // Not hooked into any permissions system, return an empty list return false; @@ -578,6 +595,11 @@ public class PermissionsManager { vaultPerms.playerAddGroup(player, groupName); return true; + case PERMISSIONS: + // Permissions + // FIXME: Add this method! + //return this.defaultPerms.group + case NONE: // Not hooked into any permissions system, return false return false; @@ -661,6 +683,11 @@ public class PermissionsManager { vaultPerms.playerRemoveGroup(player, groupName); return true; + case PERMISSIONS: + // Permissions + // FIXME: Add this method! + //return this.defaultPerms.group + case NONE: // Not hooked into any permissions system, return false return false; @@ -752,6 +779,11 @@ public class PermissionsManager { vaultPerms.playerAddGroup(player, groupName); return true; + case PERMISSIONS: + // Permissions + // FIXME: Add this method! + //return this.defaultPerms.group + case NONE: // Not hooked into any permissions system, return false return false; From 8181bda762ca4685e3676115e6bb12ac8dc2d62c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 16:26:05 +0100 Subject: [PATCH 066/199] Minor update to hasGroupSupport method in permissions manager --- .../java/fr/xephi/authme/permission/PermissionsManager.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index b0e8700d..8640d3e1 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -407,13 +407,17 @@ public class PermissionsManager { case B_PERMISSIONS: case ESSENTIALS_GROUP_MANAGER: case Z_PERMISSIONS: - case PERMISSIONS: return true; case VAULT: // Vault return vaultPerms.hasGroupSupport(); + case PERMISSIONS: + // Legacy permissions + // FIXME: Supported by plugin, but addGroup and removeGroup haven't been implemented correctly yet! + return false; + case NONE: // Not hooked into any permissions system, return false return false; From d81ef3168e0152107a8340539d353d775c547b8b Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 09:07:12 +0100 Subject: [PATCH 067/199] Command refactor - remove unused fields, reduce variable "scope" Minor refactorings in the command section for familiarization. 1. Removed suppressWarning("Deprecated") - the method is deprecated for a reason and we should be made aware of that. 2. Removed same javadoc on ExecutableCommand implementation that just had the same as the interface (this is just clutter; @Override signals that it's an implementing class and a developer can view the superclass javadoc) 3. In places where the AuthMe instance was retrieved at the top but used at the very bottom, moved it to the bottom to reduce its "scope" --- .../command/CommandArgumentDescription.java | 12 +++++----- .../authme/command/ExecutableCommand.java | 5 ++-- .../command/executable/HelpCommand.java | 20 ++++------------ .../executable/authme/AccountsCommand.java | 9 -------- .../executable/authme/AuthMeCommand.java | 9 -------- .../authme/ChangePasswordCommand.java | 13 +---------- .../executable/authme/FirstSpawnCommand.java | 10 +------- .../executable/authme/ForceLoginCommand.java | 10 -------- .../executable/authme/GetEmailCommand.java | 8 ++----- .../executable/authme/GetIpCommand.java | 10 -------- .../executable/authme/LastLoginCommand.java | 18 +++------------ .../changepassword/ChangePasswordCommand.java | 14 +---------- .../executable/email/AddEmailCommand.java | 20 ++-------------- .../executable/email/ChangeEmailCommand.java | 21 ++--------------- .../executable/email/RecoverEmailCommand.java | 18 +++------------ .../executable/login/LoginCommand.java | 23 ++++--------------- .../executable/logout/LogoutCommand.java | 15 ++---------- .../executable/register/RegisterCommand.java | 14 +---------- 18 files changed, 36 insertions(+), 213 deletions(-) diff --git a/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java b/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java index e47311bf..75d6e4ae 100644 --- a/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java @@ -39,8 +39,8 @@ public class CommandArgumentDescription { /** * Get the argument label. * - - * @return Argument label. */ + * @return Argument label. + */ public String getLabel() { return this.label; } @@ -57,8 +57,8 @@ public class CommandArgumentDescription { /** * Get the argument description. * - - * @return Argument description. */ + * @return Argument description. + */ public String getDescription() { return description; } @@ -75,8 +75,8 @@ public class CommandArgumentDescription { /** * Check whether the argument is optional. * - - * @return True if the argument is optional, false otherwise. */ + * @return True if the argument is optional, false otherwise. + */ public boolean isOptional() { return optional; } diff --git a/src/main/java/fr/xephi/authme/command/ExecutableCommand.java b/src/main/java/fr/xephi/authme/command/ExecutableCommand.java index 6c3ffe45..e81c705a 100644 --- a/src/main/java/fr/xephi/authme/command/ExecutableCommand.java +++ b/src/main/java/fr/xephi/authme/command/ExecutableCommand.java @@ -3,6 +3,7 @@ package fr.xephi.authme.command; import org.bukkit.command.CommandSender; /** + * Base class for AuthMe commands that can be executed. */ public abstract class ExecutableCommand { @@ -13,7 +14,7 @@ public abstract class ExecutableCommand { * @param commandReference The command reference. * @param commandArguments The command arguments. * - - * @return True if the command was executed successfully, false otherwise. */ + * @return True if the command was executed successfully, false otherwise. + */ public abstract boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments); } diff --git a/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java b/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java index 1d625d9c..9fbcd1d9 100644 --- a/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java @@ -10,31 +10,19 @@ import fr.xephi.authme.command.help.HelpProvider; */ public class HelpCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // Check whether quick help should be shown boolean quickHelp = commandArguments.getCount() == 0; - // Set the proper command arguments for the quick help - if(quickHelp) + // Set the proper command arguments for the quick help and show it + if (quickHelp) { commandArguments = new CommandParts(commandReference.get(0)); - - // Show the new help - if(quickHelp) HelpProvider.showHelp(sender, commandReference, commandArguments, false, false, false, false, false, true); - else + } else { HelpProvider.showHelp(sender, commandReference, commandArguments); + } - // Return the result return true; } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java index a627c934..0e036b0d 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java @@ -16,15 +16,6 @@ import fr.xephi.authme.settings.Messages; */ public class AccountsCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(final CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // AuthMe plugin instance diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java index 541154e7..da67fa82 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java @@ -11,15 +11,6 @@ import fr.xephi.authme.command.ExecutableCommand; */ public class AuthMeCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // Show some version info diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordCommand.java index 170685c0..04b4a019 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordCommand.java @@ -19,20 +19,8 @@ import fr.xephi.authme.settings.Settings; */ public class ChangePasswordCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(final CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - // Messages instance final Messages m = Messages.getInstance(); @@ -62,6 +50,7 @@ public class ChangePasswordCommand extends ExecutableCommand { } // Set the password + final AuthMe plugin = AuthMe.getInstance(); final String playerNameLowerCase = playerName.toLowerCase(); Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() { diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java index 13555552..a6401b0a 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java @@ -12,15 +12,6 @@ import fr.xephi.authme.settings.Spawn; */ public class FirstSpawnCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // Make sure the command executor is a player @@ -33,6 +24,7 @@ public class FirstSpawnCommand extends ExecutableCommand { sender.sendMessage("[AuthMe] Please use that command in game"); } } catch (NullPointerException ex) { + // TODO ljacqu 20151119: Catching NullPointerException is never a good idea. Find what can cause one instead ConsoleLogger.showError(ex.getMessage()); } return true; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java index 19e1e67c..0a9fd944 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java @@ -12,15 +12,6 @@ import fr.xephi.authme.command.ExecutableCommand; */ public class ForceLoginCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // AuthMe plugin instance @@ -33,7 +24,6 @@ public class ForceLoginCommand extends ExecutableCommand { // Command logic try { - @SuppressWarnings("deprecation") Player player = Bukkit.getPlayer(playerName); if (player == null || !player.isOnline()) { sender.sendMessage("Player needs to be online!"); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/GetEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/GetEmailCommand.java index 1ca2a851..4ee1fe06 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/GetEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/GetEmailCommand.java @@ -23,18 +23,14 @@ public class GetEmailCommand extends ExecutableCommand { * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - AuthMe plugin = AuthMe.getInstance(); - - // Messages instance - Messages m = Messages.getInstance(); - // Get the player name String playerName = sender.getName(); if(commandArguments.getCount() >= 1) playerName = commandArguments.get(0); // Get the authenticated user + AuthMe plugin = AuthMe.getInstance(); + Messages m = Messages.getInstance(); PlayerAuth auth = plugin.database.getAuth(playerName.toLowerCase()); if (auth == null) { m.send(sender, "unknown_user"); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java index f3b2700a..343642c5 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java @@ -12,15 +12,6 @@ import fr.xephi.authme.command.ExecutableCommand; */ public class GetIpCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // AuthMe plugin instance @@ -31,7 +22,6 @@ public class GetIpCommand extends ExecutableCommand { if(commandArguments.getCount() >= 1) playerName = commandArguments.get(0); - @SuppressWarnings("deprecation") Player player = Bukkit.getPlayer(playerName); if (player == null) { sender.sendMessage("This player is not actually online"); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/LastLoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/LastLoginCommand.java index 58237ef8..b2c4c70f 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/LastLoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/LastLoginCommand.java @@ -14,29 +14,17 @@ import fr.xephi.authme.settings.Messages; */ public class LastLoginCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - AuthMe plugin = AuthMe.getInstance(); - - // Messages instance - Messages m = Messages.getInstance(); - // Get the player String playerName = sender.getName(); if(commandArguments.getCount() >= 1) playerName = commandArguments.get(0); // Validate the player + AuthMe plugin = AuthMe.getInstance(); + Messages m = Messages.getInstance(); + PlayerAuth auth; try { auth = plugin.database.getAuth(playerName.toLowerCase()); diff --git a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java index dc258362..9b6956b2 100644 --- a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java @@ -15,21 +15,8 @@ import fr.xephi.authme.task.ChangePasswordTask; */ public class ChangePasswordCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - - // Messages instance final Messages m = Messages.getInstance(); // Get the passwords @@ -71,6 +58,7 @@ public class ChangePasswordCommand extends ExecutableCommand { } // Set the password + final AuthMe plugin = AuthMe.getInstance(); plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new ChangePasswordTask(plugin, player, playerPass, playerPassVerify)); return true; } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java index 34d6bba4..68f5e54b 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java @@ -12,22 +12,8 @@ import fr.xephi.authme.settings.Messages; */ public class AddEmailCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - - // Messages instance - final Messages m = Messages.getInstance(); - // Get the parameter values String playerMail = commandArguments.get(0); String playerMailVerify = commandArguments.get(1); @@ -37,11 +23,9 @@ public class AddEmailCommand extends ExecutableCommand { return true; } - // Get the player instance and name + // Get the player and perform email addition + final AuthMe plugin = AuthMe.getInstance(); final Player player = (Player) sender; - final String playerName = player.getName().toLowerCase(); - - // Command logic plugin.management.performAddEmail(player, playerMail, playerMailVerify); return true; } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java index 03a3c156..d295512e 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java @@ -6,28 +6,13 @@ import org.bukkit.entity.Player; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.CommandParts; import fr.xephi.authme.command.ExecutableCommand; -import fr.xephi.authme.settings.Messages; /** */ public class ChangeEmailCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - - // Messages instance - final Messages m = Messages.getInstance(); - // Get the parameter values String playerMailOld = commandArguments.get(0); String playerMailNew = commandArguments.get(1); @@ -37,11 +22,9 @@ public class ChangeEmailCommand extends ExecutableCommand { return true; } - // Get the player instance and name + // Get the player instance and execute action + final AuthMe plugin = AuthMe.getInstance(); final Player player = (Player) sender; - final String playerName = player.getName(); - - // Command logic plugin.management.performChangeEmail(player, playerMailOld, playerMailNew); return true; } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index 748fed46..44f007a9 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -20,23 +20,8 @@ import fr.xephi.authme.settings.Settings; */ public class RecoverEmailCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - - // Messages instance - final Messages m = Messages.getInstance(); - // Get the parameter values String playerMail = commandArguments.get(0); @@ -50,6 +35,9 @@ public class RecoverEmailCommand extends ExecutableCommand { final String playerName = player.getName(); // Command logic + final AuthMe plugin = AuthMe.getInstance(); + final Messages m = Messages.getInstance(); + if (plugin.mail == null) { m.send(player, "error"); return true; diff --git a/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java index d6b44164..e9b80cdf 100644 --- a/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java @@ -11,32 +11,19 @@ import fr.xephi.authme.command.ExecutableCommand; */ public class LoginCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - // Make sure the current command executor is a player - if(!(sender instanceof Player)) { + if (!(sender instanceof Player)) { return true; } - // Get the player instance + // Get the necessary objects + final AuthMe plugin = AuthMe.getInstance(); final Player player = (Player) sender; + final String playerPass = commandArguments.get(0); - // Get the password - String playerPass = commandArguments.get(0); - - // Login the player + // Log the player in plugin.management.performLogin(player, playerPass, false); return true; } diff --git a/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java b/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java index fcc86ce2..e2e97806 100644 --- a/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java @@ -11,26 +11,15 @@ import fr.xephi.authme.command.ExecutableCommand; */ public class LogoutCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - // Make sure the current command executor is a player - if(!(sender instanceof Player)) { + if (!(sender instanceof Player)) { return true; } // Get the player instance + final AuthMe plugin = AuthMe.getInstance(); final Player player = (Player) sender; // Logout the player diff --git a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java index 7484793d..3c7f07ee 100644 --- a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java @@ -14,21 +14,8 @@ import fr.xephi.authme.settings.Settings; */ public class RegisterCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - - * @return True if the command was executed successfully, false otherwise. */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - - // Messages instance final Messages m = Messages.getInstance(); // Make sure the sender is a player @@ -44,6 +31,7 @@ public class RegisterCommand extends ExecutableCommand { return true; } + final AuthMe plugin = AuthMe.getInstance(); if (Settings.emailRegistration && !Settings.getmailAccount.isEmpty()) { if (Settings.doubleEmailCheck) { if (commandArguments.getCount() < 2 || !commandArguments.get(0).equals(commandArguments.get(1))) { From 4e8614fdf7cfa909179f48e1c5723f03cb627ee1 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 09:49:39 +0100 Subject: [PATCH 068/199] Add test for LoginCommand; create AuthMe mock test util Had to create a getter for the Management instance in the AuthMe class for mocking, but fields should generally not be accessed globally. Hopefully soon we will be able to make the field private. --- src/main/java/fr/xephi/authme/AuthMe.java | 5 ++ .../executable/login/LoginCommand.java | 2 +- .../java/fr/xephi/authme/AuthMeMockUtil.java | 30 ++++++++ .../executable/login/LoginCommandTest.java | 77 +++++++++++++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/test/java/fr/xephi/authme/AuthMeMockUtil.java create mode 100644 src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 29d7091c..69112ea2 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -952,4 +952,9 @@ public class AuthMe extends JavaPlugin { public static int getVersionCode() { return PLUGIN_VERSION_CODE; } + + /** Returns the management instance. */ + public Management getManagement() { + return management; + } } diff --git a/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java index e9b80cdf..2479a105 100644 --- a/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java @@ -24,7 +24,7 @@ public class LoginCommand extends ExecutableCommand { final String playerPass = commandArguments.get(0); // Log the player in - plugin.management.performLogin(player, playerPass, false); + plugin.getManagement().performLogin(player, playerPass, false); return true; } } diff --git a/src/test/java/fr/xephi/authme/AuthMeMockUtil.java b/src/test/java/fr/xephi/authme/AuthMeMockUtil.java new file mode 100644 index 00000000..6f4ca7a4 --- /dev/null +++ b/src/test/java/fr/xephi/authme/AuthMeMockUtil.java @@ -0,0 +1,30 @@ +package fr.xephi.authme; + +import org.mockito.Mockito; + +import java.lang.reflect.Field; + +/** + * Creates a mock implementation of AuthMe for testing purposes. + */ +public final class AuthMeMockUtil { + + private AuthMeMockUtil() { + // Util class + } + + /** + * Set the AuthMe plugin instance to a mock object. Use {@link AuthMe#getInstance()} to retrieve the mock. + */ + public static void initialize() { + AuthMe mock = Mockito.mock(AuthMe.class); + + try { + Field instance = AuthMe.class.getDeclaredField("plugin"); + instance.setAccessible(true); + instance.set(null, mock); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Could not initialize AuthMe mock", e); + } + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java new file mode 100644 index 00000000..b8c3c2f5 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java @@ -0,0 +1,77 @@ +package fr.xephi.authme.command.executable.login; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.AuthMeMockUtil; +import fr.xephi.authme.command.CommandParts; +import fr.xephi.authme.process.Management; +import fr.xephi.authme.settings.Settings; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; + +/** + * Test for {@link LoginCommand}. + */ +public class LoginCommandTest { + + private static Management managementMock; + + @Before + public void initializeAuthMeMock() { + AuthMeMockUtil.initialize(); + AuthMe pluginMock = AuthMe.getInstance(); + + Settings.captchaLength = 10; + managementMock = mock(Management.class); + Mockito.when(pluginMock.getManagement()).thenReturn(managementMock); + } + + @Test + public void shouldStopIfSenderIsNotAPlayer() { + // given + CommandSender sender = mock(BlockCommandSender.class); + LoginCommand command = new LoginCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + Mockito.verify(managementMock, never()).performLogin(any(Player.class), anyString(), anyBoolean()); + } + + @Test + public void shouldCallManagementForPlayerCaller() { + // given + Player sender = mock(Player.class); + LoginCommand command = new LoginCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts("password")); + + // then + Mockito.verify(managementMock).performLogin(eq(sender), eq("password"), eq(false)); + } + + @Test + public void shouldHandleMissingPassword() { + // given + Player sender = mock(Player.class); + LoginCommand command = new LoginCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + // TODO ljacqu 20151121: May make sense to handle null password in LoginCommand instead of forwarding the call + String password = null; + Mockito.verify(managementMock).performLogin(eq(sender), eq(password), eq(false)); + } +} From 58dc15123c4d4e19a48c4a4294c15487891c730d Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 10:29:40 +0100 Subject: [PATCH 069/199] Add tests for LogoutCommand and RegisterCommand. Add more generic mockUtil --- .../executable/logout/LogoutCommand.java | 2 +- .../executable/register/RegisterCommand.java | 7 +- .../java/fr/xephi/authme/AuthMeMockUtil.java | 27 +++++- .../executable/login/LoginCommandTest.java | 8 +- .../executable/logout/LogoutCommandTest.java | 62 +++++++++++++ .../register/RegisterCommandTest.java | 88 +++++++++++++++++++ 6 files changed, 183 insertions(+), 11 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/command/executable/logout/LogoutCommandTest.java create mode 100644 src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java diff --git a/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java b/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java index e2e97806..60c14a26 100644 --- a/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java @@ -23,7 +23,7 @@ public class LogoutCommand extends ExecutableCommand { final Player player = (Player) sender; // Logout the player - plugin.management.performLogout(player); + plugin.getManagement().performLogout(player); return true; } } diff --git a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java index 3c7f07ee..6e89fc62 100644 --- a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java @@ -1,5 +1,6 @@ package fr.xephi.authme.command.executable.register; +import fr.xephi.authme.process.Management; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -31,7 +32,7 @@ public class RegisterCommand extends ExecutableCommand { return true; } - final AuthMe plugin = AuthMe.getInstance(); + final Management management = AuthMe.getInstance().getManagement(); if (Settings.emailRegistration && !Settings.getmailAccount.isEmpty()) { if (Settings.doubleEmailCheck) { if (commandArguments.getCount() < 2 || !commandArguments.get(0).equals(commandArguments.get(1))) { @@ -46,7 +47,7 @@ public class RegisterCommand extends ExecutableCommand { } RandomString rand = new RandomString(Settings.getRecoveryPassLength); final String thePass = rand.nextString(); - plugin.management.performRegister(player, thePass, email); + management.performRegister(player, thePass, email); return true; } if (commandArguments.getCount() > 1 && Settings.getEnablePasswordVerifier) @@ -54,7 +55,7 @@ public class RegisterCommand extends ExecutableCommand { m.send(player, "password_error"); return true; } - plugin.management.performRegister(player, commandArguments.get(0), ""); + management.performRegister(player, commandArguments.get(0), ""); return true; } } diff --git a/src/test/java/fr/xephi/authme/AuthMeMockUtil.java b/src/test/java/fr/xephi/authme/AuthMeMockUtil.java index 6f4ca7a4..905c4630 100644 --- a/src/test/java/fr/xephi/authme/AuthMeMockUtil.java +++ b/src/test/java/fr/xephi/authme/AuthMeMockUtil.java @@ -1,5 +1,6 @@ package fr.xephi.authme; +import fr.xephi.authme.settings.Messages; import org.mockito.Mockito; import java.lang.reflect.Field; @@ -14,17 +15,35 @@ public final class AuthMeMockUtil { } /** - * Set the AuthMe plugin instance to a mock object. Use {@link AuthMe#getInstance()} to retrieve the mock. + * Sets the AuthMe plugin instance to a mock object. Use {@link AuthMe#getInstance()} to retrieve the mock. */ - public static void initialize() { + public static void mockAuthMeInstance() { AuthMe mock = Mockito.mock(AuthMe.class); + mockSingletonForClass(AuthMe.class, "plugin", mock); + } + /** + * Creates a mock Messages object for the instance returned from {@link Messages#getInstance()}. + */ + public static void mockMessagesInstance() { + Messages mock = Mockito.mock(Messages.class); + mockSingletonForClass(Messages.class, "singleton", mock); + } + + /** + * Sets a field of a class to the given mock. + * + * @param clazz the class to modify + * @param fieldName the field name + * @param mock the mock to set for the given field + */ + private static void mockSingletonForClass(Class clazz, String fieldName, Object mock) { try { - Field instance = AuthMe.class.getDeclaredField("plugin"); + Field instance = clazz.getDeclaredField(fieldName); instance.setAccessible(true); instance.set(null, mock); } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException("Could not initialize AuthMe mock", e); + throw new RuntimeException("Could not set mock instance for class " + clazz.getName(), e); } } } diff --git a/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java index b8c3c2f5..5ed42876 100644 --- a/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java @@ -12,8 +12,10 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; - -import static org.mockito.Matchers.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -26,7 +28,7 @@ public class LoginCommandTest { @Before public void initializeAuthMeMock() { - AuthMeMockUtil.initialize(); + AuthMeMockUtil.mockAuthMeInstance(); AuthMe pluginMock = AuthMe.getInstance(); Settings.captchaLength = 10; diff --git a/src/test/java/fr/xephi/authme/command/executable/logout/LogoutCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/logout/LogoutCommandTest.java new file mode 100644 index 00000000..8416b5fd --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/logout/LogoutCommandTest.java @@ -0,0 +1,62 @@ +package fr.xephi.authme.command.executable.logout; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.AuthMeMockUtil; +import fr.xephi.authme.command.CommandParts; +import fr.xephi.authme.process.Management; +import fr.xephi.authme.settings.Settings; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; + +/** + * Test for {@link LogoutCommand}. + */ +public class LogoutCommandTest { + + private static Management managementMock; + + @Before + public void initializeAuthMeMock() { + AuthMeMockUtil.mockAuthMeInstance(); + AuthMe pluginMock = AuthMe.getInstance(); + + Settings.captchaLength = 10; + managementMock = mock(Management.class); + Mockito.when(pluginMock.getManagement()).thenReturn(managementMock); + } + + @Test + public void shouldStopIfSenderIsNotAPlayer() { + // given + CommandSender sender = mock(BlockCommandSender.class); + LogoutCommand command = new LogoutCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + Mockito.verify(managementMock, never()).performLogout(any(Player.class)); + } + + @Test + public void shouldCallManagementForPlayerCaller() { + // given + Player sender = mock(Player.class); + LogoutCommand command = new LogoutCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts("password")); + + // then + Mockito.verify(managementMock).performLogout(sender); + } + +} diff --git a/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java new file mode 100644 index 00000000..0b2ba2cd --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java @@ -0,0 +1,88 @@ +package fr.xephi.authme.command.executable.register; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.AuthMeMockUtil; +import fr.xephi.authme.command.CommandParts; +import fr.xephi.authme.process.Management; +import fr.xephi.authme.settings.Messages; +import fr.xephi.authme.settings.Settings; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link RegisterCommand}. + */ +public class RegisterCommandTest { + + private static Management managementMock; + private static Messages messagesMock; + + @Before + public void initializeAuthMeMock() { + AuthMeMockUtil.mockMessagesInstance(); + messagesMock = Messages.getInstance(); + + AuthMeMockUtil.mockAuthMeInstance(); + AuthMe pluginMock = AuthMe.getInstance(); + + Settings.captchaLength = 10; + managementMock = mock(Management.class); + Mockito.when(pluginMock.getManagement()).thenReturn(managementMock); + } + + @Test + public void shouldNotRunForNonPlayerSender() { + // given + CommandSender sender = mock(BlockCommandSender.class); + RegisterCommand command = new RegisterCommand(); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + verify(sender).sendMessage(messageCaptor.capture()); + assertThat(messageCaptor.getValue().contains("Player Only!"), equalTo(true)); + verify(managementMock, never()).performRegister(any(Player.class), anyString(), anyString()); + } + + @Test + public void shouldFailForEmptyArguments() { + // given + CommandSender sender = mock(Player.class); + RegisterCommand command = new RegisterCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + verify(messagesMock).send(sender, "usage_reg"); + verify(managementMock, never()).performRegister(any(Player.class), anyString(), anyString()); + } + + @Test + public void shouldForwardRegister() { + // given + Player sender = mock(Player.class); + RegisterCommand command = new RegisterCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts("password")); + + // then + verify(managementMock).performRegister(sender, "password", ""); + } +} From a3f24bcb9a38473f04018d98f3635e794aec634a Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 11:16:31 +0100 Subject: [PATCH 070/199] Create test for HelpSyntaxHelperTest --- .../authme/command/CommandDescription.java | 1 + .../xephi/authme/command/CommandManager.java | 1 + .../authme/command/help/HelpSyntaxHelper.java | 16 +- .../java/fr/xephi/authme/util/ListUtils.java | 19 --- .../command/help/HelpSyntaxHelperTest.java | 152 ++++++++++++++++++ 5 files changed, 164 insertions(+), 25 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/command/help/HelpSyntaxHelperTest.java diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java index 51ce67b0..4feec54d 100644 --- a/src/main/java/fr/xephi/authme/command/CommandDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandDescription.java @@ -681,6 +681,7 @@ public class CommandDescription { * * @param maximumArguments True if there is an argument maximum, based on the number of registered arguments. */ + // TODO ljacqu 20151121: Rename the setter public void setMaximumArguments(boolean maximumArguments) { this.noArgumentMaximum = !maximumArguments; } diff --git a/src/main/java/fr/xephi/authme/command/CommandManager.java b/src/main/java/fr/xephi/authme/command/CommandManager.java index 162d61d4..be5b28bf 100644 --- a/src/main/java/fr/xephi/authme/command/CommandManager.java +++ b/src/main/java/fr/xephi/authme/command/CommandManager.java @@ -54,6 +54,7 @@ public class CommandManager { /** * Register all commands. */ + // TODO ljacqu 20151121: Create a builder class for CommandDescription @SuppressWarnings({ "serial" }) public void registerCommands() { // Register the base AuthMe Reloaded command diff --git a/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java b/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java index 55f2581a..5f564749 100644 --- a/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java +++ b/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java @@ -9,7 +9,11 @@ import fr.xephi.authme.util.ListUtils; /** */ -public class HelpSyntaxHelper { +public final class HelpSyntaxHelper { + + private HelpSyntaxHelper() { + // Helper class + } /** * Get the proper syntax for a command. @@ -19,8 +23,8 @@ public class HelpSyntaxHelper { * @param alternativeLabel The alternative label to use for this command syntax. * @param highlight True to highlight the important parts of this command. * - - * @return The command with proper syntax. */ + * @return The command with proper syntax. + */ @SuppressWarnings("StringConcatenationInsideStringBufferAppend") public static String getCommandSyntax(CommandDescription commandDescription, CommandParts commandReference, String alternativeLabel, boolean highlight) { // Create a string builder to build the command @@ -35,9 +39,9 @@ public class HelpSyntaxHelper { String commandLabel = helpCommandReference.get(helpCommandReference.getCount() - 1); // Check whether the alternative label should be used - if(alternativeLabel != null) - if(alternativeLabel.trim().length() > 0) - commandLabel = alternativeLabel; + if (alternativeLabel != null && alternativeLabel.trim().length() > 0) { + commandLabel = alternativeLabel; + } // Show the important bit of the command, highlight this part if required sb.append(ListUtils.implode(parentCommand, (highlight ? ChatColor.YELLOW + "" + ChatColor.BOLD : "") + commandLabel, " ")); diff --git a/src/main/java/fr/xephi/authme/util/ListUtils.java b/src/main/java/fr/xephi/authme/util/ListUtils.java index 05ede1ba..69ed092e 100644 --- a/src/main/java/fr/xephi/authme/util/ListUtils.java +++ b/src/main/java/fr/xephi/authme/util/ListUtils.java @@ -37,25 +37,6 @@ public class ListUtils { return sb.toString(); } - /** - * Implode two lists of elements into a single string, with a specified separator. - * - * @param elements The first list of elements to implode. - * @param otherElements The second list of elements to implode. - * @param separator The separator to use. - * - - * @return The result string. */ - public static String implode(List elements, List otherElements, String separator) { - // Combine the lists - List combined = new ArrayList<>(); - combined.addAll(elements); - combined.addAll(otherElements); - - // Implode and return the result - return implode(combined, separator); - } - /** * Implode two elements into a single string, with a specified separator. * diff --git a/src/test/java/fr/xephi/authme/command/help/HelpSyntaxHelperTest.java b/src/test/java/fr/xephi/authme/command/help/HelpSyntaxHelperTest.java new file mode 100644 index 00000000..3fba7025 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/help/HelpSyntaxHelperTest.java @@ -0,0 +1,152 @@ +package fr.xephi.authme.command.help; + +import fr.xephi.authme.command.CommandArgumentDescription; +import fr.xephi.authme.command.CommandDescription; +import fr.xephi.authme.command.CommandParts; +import fr.xephi.authme.command.executable.authme.AuthMeCommand; +import fr.xephi.authme.command.executable.authme.RegisterCommand; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; + +import static java.util.Collections.singletonList; +import static org.bukkit.ChatColor.BOLD; +import static org.bukkit.ChatColor.ITALIC; +import static org.bukkit.ChatColor.WHITE; +import static org.bukkit.ChatColor.YELLOW; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link HelpSyntaxHelper}. + */ +public class HelpSyntaxHelperTest { + + @Test + public void shouldFormatSimpleCommand() { + // given + CommandDescription description = getDescription(); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), "", false); + + // then + assertThat(result, equalTo(WHITE + "/authme register" + ITALIC + " [name]")); + } + + @Test + public void shouldFormatSimpleCommandWithOptionalParam() { + // given + CommandDescription description = getDescription(); + description.setArguments(singletonList( + new CommandArgumentDescription("test", "", false))); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), null, false); + + // then + assertThat(result, equalTo(WHITE + "/authme register" + ITALIC + " ")); + } + + @Test + public void shouldFormatCommandWithMultipleParams() { + // given + CommandDescription description = getDescription(); + description.setArguments(Arrays.asList( + new CommandArgumentDescription("name", "", true), + new CommandArgumentDescription("test", "", false))); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), "", false); + + // then + assertThat(result, equalTo(WHITE + "/authme register" + ITALIC + " [name]" + ITALIC + " ")); + } + + @Test + public void shouldHighlightCommandWithMultipleParams() { + // given + CommandDescription description = getDescription(); + description.setArguments(Arrays.asList( + new CommandArgumentDescription("name", "", true), + new CommandArgumentDescription("test", "", false))); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), "", true); + + // then + assertThat(result, equalTo(WHITE + "/authme " + + YELLOW + BOLD + "register" + + YELLOW + ITALIC + " [name]" + ITALIC + " ")); + } + + @Test + public void shouldHighlightCommandWithNoParams() { + // given + CommandDescription description = getDescription(); + description.setArguments(new ArrayList()); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), null, true); + + // then + assertThat(result, equalTo(WHITE + "/authme " + YELLOW + BOLD + "register" + YELLOW)); + } + + @Test + public void shouldFormatSimpleCommandWithAlternativeLabel() { + // given + CommandDescription description = getDescription(); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), "alt", false); + + // then + assertThat(result, equalTo(WHITE + "/authme alt" + ITALIC + " [name]")); + } + + @Test + public void shouldHighlightCommandWithAltLabelAndUnlimitedArguments() { + // given + CommandDescription description = getDescription(); + description.setArguments(Arrays.asList( + new CommandArgumentDescription("name", "", true), + new CommandArgumentDescription("test", "", false))); + description.setMaximumArguments(false); + + // when + String result = HelpSyntaxHelper.getCommandSyntax( + description, new CommandParts(), "test", true); + + // then + assertThat(result, equalTo(WHITE + "/authme " + + YELLOW + BOLD + "test" + + YELLOW + ITALIC + " [name]" + ITALIC + " " + ITALIC + " ...")); + } + + + private static CommandDescription getDescription() { + CommandDescription base = new CommandDescription(new AuthMeCommand(), + singletonList("authme"), + "Base command", + "AuthMe base command", + null); + CommandArgumentDescription userArg = new CommandArgumentDescription( + "name", "", true); + + return new CommandDescription( + new RegisterCommand(), + Arrays.asList("register", "r"), + "Register a player", + "Register the specified player with the specified password.", + base, + singletonList(userArg)); + } +} From b3d0a71dec4c1f3c3b00f815a05003fb5e8c33f6 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 11:57:04 +0100 Subject: [PATCH 071/199] Merge ListUtil into StringUtil; refactor HelpSyntaxHelper + create test The HelpSyntaxHelper had suppressed warnings for string concatenation within StringBuilder - the point of the StringBuilder is that it is faster when you use it to concatenate many elements. If you still use string concatenation with + within these calls it beats the purpose. --- .../fr/xephi/authme/command/CommandParts.java | 15 +++-- .../authme/command/help/HelpSyntaxHelper.java | 63 +++++++++++-------- .../java/fr/xephi/authme/util/ListUtils.java | 58 ----------------- .../fr/xephi/authme/util/StringUtils.java | 35 +++++++++++ .../authme/command/CommandPartsTest.java | 38 +++++++++++ .../fr/xephi/authme/util/StringUtilsTest.java | 41 ++++++++++++ 6 files changed, 159 insertions(+), 91 deletions(-) delete mode 100644 src/main/java/fr/xephi/authme/util/ListUtils.java create mode 100644 src/test/java/fr/xephi/authme/command/CommandPartsTest.java diff --git a/src/main/java/fr/xephi/authme/command/CommandParts.java b/src/main/java/fr/xephi/authme/command/CommandParts.java index 2c67f7b1..9e50cc16 100644 --- a/src/main/java/fr/xephi/authme/command/CommandParts.java +++ b/src/main/java/fr/xephi/authme/command/CommandParts.java @@ -3,7 +3,6 @@ package fr.xephi.authme.command; import java.util.ArrayList; import java.util.List; -import fr.xephi.authme.util.ListUtils; import fr.xephi.authme.util.StringUtils; /** @@ -165,8 +164,8 @@ public class CommandParts { * * @param other The other reference. * - - * @return The result from zero to above. A negative number will be returned on error. */ + * @return The result from zero to above. A negative number will be returned on error. + */ public double getDifference(CommandParts other) { return getDifference(other, false); } @@ -177,8 +176,8 @@ public class CommandParts { * @param other The other reference. * @param fullCompare True to compare the full references as far as the range reaches. * - - * @return The result from zero to above. A negative number will be returned on error. */ + * @return The result from zero to above. A negative number will be returned on error. + */ public double getDifference(CommandParts other, boolean fullCompare) { // Make sure the other reference is correct if(other == null) @@ -196,10 +195,10 @@ public class CommandParts { /** * Convert the parts to a string. * - - * @return The part as a string. */ + * @return The part as a string. + */ @Override public String toString() { - return ListUtils.implode(this.parts, " "); + return StringUtils.join(" ", this.parts); } } diff --git a/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java b/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java index 5f564749..0ff99a5c 100644 --- a/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java +++ b/src/main/java/fr/xephi/authme/command/help/HelpSyntaxHelper.java @@ -1,13 +1,15 @@ package fr.xephi.authme.command.help; +import fr.xephi.authme.util.StringUtils; import org.bukkit.ChatColor; import fr.xephi.authme.command.CommandArgumentDescription; import fr.xephi.authme.command.CommandDescription; import fr.xephi.authme.command.CommandParts; -import fr.xephi.authme.util.ListUtils; /** + * Helper class for formatting a command's structure (name and arguments) + * for a Minecraft user. */ public final class HelpSyntaxHelper { @@ -16,52 +18,63 @@ public final class HelpSyntaxHelper { } /** - * Get the proper syntax for a command. + * Get the formatted syntax for a command. * - * @param commandDescription The command to get the syntax for. + * @param commandDescription The command to build the syntax for. * @param commandReference The reference of the command. * @param alternativeLabel The alternative label to use for this command syntax. * @param highlight True to highlight the important parts of this command. * * @return The command with proper syntax. */ - @SuppressWarnings("StringConcatenationInsideStringBufferAppend") - public static String getCommandSyntax(CommandDescription commandDescription, CommandParts commandReference, String alternativeLabel, boolean highlight) { - // Create a string builder to build the command - StringBuilder sb = new StringBuilder(); - - // Set the color and prefix a slash - sb.append(ChatColor.WHITE + "/"); + public static String getCommandSyntax(CommandDescription commandDescription, CommandParts commandReference, + String alternativeLabel, boolean highlight) { + // Create a string builder with white color and prefixed slash + StringBuilder sb = new StringBuilder() + .append(ChatColor.WHITE) + .append("/"); // Get the help command reference, and the command label CommandParts helpCommandReference = commandDescription.getCommandReference(commandReference); - final String parentCommand = (new CommandParts(helpCommandReference.getRange(0, helpCommandReference.getCount() - 1))).toString(); - String commandLabel = helpCommandReference.get(helpCommandReference.getCount() - 1); + final String parentCommand = new CommandParts( + helpCommandReference.getRange(0, helpCommandReference.getCount() - 1)).toString(); // Check whether the alternative label should be used - if (alternativeLabel != null && alternativeLabel.trim().length() > 0) { + String commandLabel; + if (StringUtils.isEmpty(alternativeLabel)) { + commandLabel = helpCommandReference.get(helpCommandReference.getCount() - 1); + } else { commandLabel = alternativeLabel; } // Show the important bit of the command, highlight this part if required - sb.append(ListUtils.implode(parentCommand, (highlight ? ChatColor.YELLOW + "" + ChatColor.BOLD : "") + commandLabel, " ")); - if(highlight) - sb.append(ChatColor.YELLOW); + sb.append(parentCommand) + .append(" ") + .append(highlight ? ChatColor.YELLOW.toString() + ChatColor.BOLD : "") + .append(commandLabel); - // Add each command arguments - for(CommandArgumentDescription arg : commandDescription.getArguments()) { - // Add the argument as optional or non-optional argument - if(!arg.isOptional()) - sb.append(ChatColor.ITALIC + " <" + arg.getLabel() + ">"); - else - sb.append(ChatColor.ITALIC + " [" + arg.getLabel() + "]"); + if (highlight) { + sb.append(ChatColor.YELLOW); + } + + // Add each command argument + for (CommandArgumentDescription arg : commandDescription.getArguments()) { + sb.append(ChatColor.ITALIC).append(formatArgument(arg)); } // Add some dots if the command allows unlimited arguments - if(commandDescription.getMaximumArguments() < 0) - sb.append(ChatColor.ITALIC + " ..."); + if (commandDescription.getMaximumArguments() < 0) { + sb.append(ChatColor.ITALIC).append(" ..."); + } // Return the build command syntax return sb.toString(); } + + private static String formatArgument(CommandArgumentDescription argument) { + if (argument.isOptional()) { + return " [" + argument.getLabel() + "]"; + } + return " <" + argument.getLabel() + ">"; + } } diff --git a/src/main/java/fr/xephi/authme/util/ListUtils.java b/src/main/java/fr/xephi/authme/util/ListUtils.java deleted file mode 100644 index 69ed092e..00000000 --- a/src/main/java/fr/xephi/authme/util/ListUtils.java +++ /dev/null @@ -1,58 +0,0 @@ -package fr.xephi.authme.util; - -import java.util.ArrayList; -import java.util.List; - -/** - */ -public class ListUtils { - - /** - * Implode a list of elements into a single string, with a specified separator. - * - * @param elements The elements to implode. - * @param separator The separator to use. - * - - * @return The result string. */ - public static String implode(List elements, String separator) { - // Create a string builder - StringBuilder sb = new StringBuilder(); - - // Append each element - for(String element : elements) { - // Make sure the element isn't empty - if(element.trim().length() == 0) - continue; - - // Prefix the separator if it isn't the first element - if(sb.length() > 0) - sb.append(separator); - - // Append the element - sb.append(element); - } - - // Return the result - return sb.toString(); - } - - /** - * Implode two elements into a single string, with a specified separator. - * - * @param element The first element to implode. - * @param otherElement The second element to implode. - * @param separator The separator to use. - * - - * @return The result string. */ - public static String implode(String element, String otherElement, String separator) { - // Combine the lists - List combined = new ArrayList<>(); - combined.add(element); - combined.add(otherElement); - - // Implode and return the result - return implode(combined, separator); - } -} diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java index e9b495bc..f0dd3d9c 100644 --- a/src/main/java/fr/xephi/authme/util/StringUtils.java +++ b/src/main/java/fr/xephi/authme/util/StringUtils.java @@ -49,4 +49,39 @@ public class StringUtils { return false; } + /** + * Null-safe method for checking whether a string is empty. Note that the string + * is trimmed, so this method also considers a string with whitespace as empty. + * + * @param str the string to verify + * + * @return true if the string is empty, false otherwise + */ + public static boolean isEmpty(String str) { + return str == null || str.trim().isEmpty(); + } + + /** + * Joins a list of elements into a single string with the specified delimiter. + * + * @param delimiter the delimiter to use + * @param elements the elements to join + * + * @return a new String that is composed of the elements separated by the delimiter + */ + public static String join(String delimiter, Iterable elements) { + StringBuilder sb = new StringBuilder(); + for (String element : elements) { + if (!isEmpty(element)) { + // Add the separator if it isn't the first element + if (sb.length() > 0) { + sb.append(delimiter); + } + sb.append(element); + } + } + + return sb.toString(); + } + } diff --git a/src/test/java/fr/xephi/authme/command/CommandPartsTest.java b/src/test/java/fr/xephi/authme/command/CommandPartsTest.java new file mode 100644 index 00000000..62d22b5f --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/CommandPartsTest.java @@ -0,0 +1,38 @@ +package fr.xephi.authme.command; + +import org.junit.Test; + +import java.util.Arrays; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link CommandParts}. + */ +public class CommandPartsTest { + + @Test + public void shouldPrintPartsForStringRepresentation() { + // given + CommandParts parts = new CommandParts(Arrays.asList("some", "parts", "for", "test")); + + // when + String str = parts.toString(); + + // then + assertThat(str, equalTo("some parts for test")); + } + + @Test + public void shouldPrintEmptyStringForNoArguments() { + // given + CommandParts parts = new CommandParts(); + + // when + String str = parts.toString(); + + // then + assertThat(str, equalTo("")); + } +} diff --git a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java index 668f2059..51cd9677 100644 --- a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java @@ -2,8 +2,13 @@ package fr.xephi.authme.util; import org.junit.Test; +import java.util.Arrays; +import java.util.List; + import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; /** * Test for {@link StringUtils}. @@ -43,4 +48,40 @@ public class StringUtilsTest { // then assertThat(result, equalTo(false)); } + + @Test + public void shouldCheckIsEmptyUtil() { + // Should be true for null/empty/whitespace + assertTrue(StringUtils.isEmpty(null)); + assertTrue(StringUtils.isEmpty("")); + assertTrue(StringUtils.isEmpty(" \t")); + + // Should be false if string has content + assertFalse(StringUtils.isEmpty("P")); + assertFalse(StringUtils.isEmpty(" test")); + } + + @Test + public void shouldJoinString() { + // given + List elements = Arrays.asList("test", "for", null, "join", "StringUtils"); + + // when + String result = StringUtils.join(", ", elements); + + // then + assertThat(result, equalTo("test, for, join, StringUtils")); + } + + @Test + public void shouldNotHaveDelimiter() { + // given + List elements = Arrays.asList(" ", null, "\t", "hello", null); + + // when + String result = StringUtils.join("-", elements); + + // then + assertThat(result, equalTo("hello")); + } } From 30b4cfe44e77e33fdfbbc1a0222dc4837a9d0b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 19:18:56 +0100 Subject: [PATCH 072/199] Added notice to legacy permissions instance in main class --- src/main/java/fr/xephi/authme/AuthMe.java | 2 +- src/main/java/fr/xephi/authme/DataManager.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 69112ea2..c253ca5b 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -96,7 +96,7 @@ public class AuthMe extends JavaPlugin { public boolean delayedAntiBot = true; // Hooks TODO: move into modules - public Permission vaultGroupManagement; + public Permission vaultGroupManagement; // TODO: Remove this instance, and replace every usage with permissions manager! public Essentials ess; public MultiverseCore multiverse; public CombatTagPlus combatTagPlus; diff --git a/src/main/java/fr/xephi/authme/DataManager.java b/src/main/java/fr/xephi/authme/DataManager.java index c808ebc5..edc96eee 100644 --- a/src/main/java/fr/xephi/authme/DataManager.java +++ b/src/main/java/fr/xephi/authme/DataManager.java @@ -177,8 +177,7 @@ public class DataManager { * @param cleared List * @param permission Permission */ - public synchronized void purgePermissions(List cleared, - Permission permission) { + public synchronized void purgePermissions(List cleared, Permission permission) { int i = 0; for (String name : cleared) { try { From a176aba35049b0f073bd35e669a8257823642b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 19:22:23 +0100 Subject: [PATCH 073/199] Fixed horrible typo, implemented dynamic plugin name --- src/main/java/fr/xephi/authme/settings/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index a8da9f5e..e9b719bb 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -651,7 +651,7 @@ public final class Settings extends YamlConfiguration { BufferedWriter w = new BufferedWriter(fw); w.write("Welcome {PLAYER} on {SERVER} server"); w.newLine(); - w.write("This server use AuthMe protection!"); + w.write("This server uses " + AuthMe.PLUGIN_NAME + " protection!"); w.close(); } catch (IOException e) { e.printStackTrace(); From 579b7e7b974ce29eafcd0b2e512f5f33be2d9003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 19:24:25 +0100 Subject: [PATCH 074/199] Improved some grammar and variable names --- .../fr/xephi/authme/settings/Settings.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index e9b719bb..94f032d8 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -545,14 +545,14 @@ public final class Settings extends YamlConfiguration { try { return DataSource.DataSourceType.valueOf(configFile.getString(key, "sqlite").toUpperCase()); } catch (IllegalArgumentException ex) { - ConsoleLogger.showError("Unknown database backend; defaulting to sqlite database"); + ConsoleLogger.showError("Unknown database backend; defaulting to SQLite database"); return DataSource.DataSourceType.SQLITE; } } /** * Config option for setting and check restricted user by username;ip , - * return false if ip and name doesnt amtch with player that join the + * return false if ip and name doesn't match with player that join the * server, so player has a restricted access * @param name String * @param ip String @@ -560,21 +560,21 @@ public final class Settings extends YamlConfiguration { * @return boolean */ public static boolean getRestrictedIp(String name, String ip) { - Iterator iter = getRestrictedIp.iterator(); - boolean trueonce = false; - boolean namefound = false; - while (iter.hasNext()) { - String[] args = iter.next().split(";"); - String testname = args[0]; - String testip = args[1]; - if (testname.equalsIgnoreCase(name)) { - namefound = true; - if (testip.equalsIgnoreCase(ip)) { - trueonce = true; + Iterator iterator = getRestrictedIp.iterator(); + boolean trueOnce = false; + boolean nameFound = false; + while(iterator.hasNext()) { + String[] args = iterator.next().split(";"); + String testName = args[0]; + String testIp = args[1]; + if (testName.equalsIgnoreCase(name)) { + nameFound = true; + if (testIp.equalsIgnoreCase(ip)) { + trueOnce = true; } } } - return !namefound || trueonce; + return !nameFound || trueOnce; } /** From efb57989ed93d3766dc635d11cd250436ea0e656 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 19:51:49 +0100 Subject: [PATCH 075/199] Start tests for email commands - Removed randomStringGenerator from Management as it is unused --- .../executable/email/AddEmailCommand.java | 10 +-- .../executable/email/ChangeEmailCommand.java | 10 +-- .../executable/email/RecoverEmailCommand.java | 8 +-- .../fr/xephi/authme/process/Management.java | 58 +---------------- .../executable/email/AddEmailCommandTest.java | 63 +++++++++++++++++++ .../email/ChangeEmailCommandTest.java | 63 +++++++++++++++++++ .../email/RecoverEmailCommandTest.java | 36 +++++++++++ 7 files changed, 178 insertions(+), 70 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/command/executable/email/AddEmailCommandTest.java create mode 100644 src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java create mode 100644 src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java diff --git a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java index 68f5e54b..a185fed9 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java @@ -14,19 +14,19 @@ public class AddEmailCommand extends ExecutableCommand { @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // Get the parameter values - String playerMail = commandArguments.get(0); - String playerMailVerify = commandArguments.get(1); - // Make sure the current command executor is a player if (!(sender instanceof Player)) { return true; } + // Get the parameter values + String playerMail = commandArguments.get(0); + String playerMailVerify = commandArguments.get(1); + // Get the player and perform email addition final AuthMe plugin = AuthMe.getInstance(); final Player player = (Player) sender; - plugin.management.performAddEmail(player, playerMail, playerMailVerify); + plugin.getManagement().performAddEmail(player, playerMail, playerMailVerify); return true; } } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java index d295512e..2aa3972c 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java @@ -13,19 +13,19 @@ public class ChangeEmailCommand extends ExecutableCommand { @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // Get the parameter values - String playerMailOld = commandArguments.get(0); - String playerMailNew = commandArguments.get(1); - // Make sure the current command executor is a player if (!(sender instanceof Player)) { return true; } + // Get the parameter values + String playerMailOld = commandArguments.get(0); + String playerMailNew = commandArguments.get(1); + // Get the player instance and execute action final AuthMe plugin = AuthMe.getInstance(); final Player player = (Player) sender; - plugin.management.performChangeEmail(player, playerMailOld, playerMailNew); + plugin.getManagement().performChangeEmail(player, playerMailOld, playerMailNew); return true; } } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index 44f007a9..5429a005 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -22,14 +22,14 @@ public class RecoverEmailCommand extends ExecutableCommand { @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // Get the parameter values - String playerMail = commandArguments.get(0); - // Make sure the current command executor is a player - if(!(sender instanceof Player)) { + if (!(sender instanceof Player)) { return true; } + // Get the parameter values + String playerMail = commandArguments.get(0); + // Get the player instance and name final Player player = (Player) sender; final String playerName = player.getName(); diff --git a/src/main/java/fr/xephi/authme/process/Management.java b/src/main/java/fr/xephi/authme/process/Management.java index 1030eedd..e917e352 100644 --- a/src/main/java/fr/xephi/authme/process/Management.java +++ b/src/main/java/fr/xephi/authme/process/Management.java @@ -1,8 +1,5 @@ package fr.xephi.authme.process; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitScheduler; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.process.email.AsyncChangeEmail; import fr.xephi.authme.process.join.AsyncronousJoin; @@ -11,35 +8,21 @@ import fr.xephi.authme.process.logout.AsyncronousLogout; import fr.xephi.authme.process.quit.AsyncronousQuit; import fr.xephi.authme.process.register.AsyncRegister; import fr.xephi.authme.process.unregister.AsyncronousUnregister; -import fr.xephi.authme.security.RandomString; -import fr.xephi.authme.settings.Settings; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitScheduler; /** - - * @author Gabriele - * @version $Revision: 1.0 $ */ public class Management { private final AuthMe plugin; private final BukkitScheduler sched; - public static RandomString rdm = new RandomString(Settings.captchaLength); - /** - * Constructor for Management. - * @param plugin AuthMe - */ public Management(AuthMe plugin) { this.plugin = plugin; this.sched = this.plugin.getServer().getScheduler(); } - /** - * Method performLogin. - * @param player Player - * @param password String - * @param forceLogin boolean - */ public void performLogin(final Player player, final String password, final boolean forceLogin) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -50,10 +33,6 @@ public class Management { }); } - /** - * Method performLogout. - * @param player Player - */ public void performLogout(final Player player) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -64,12 +43,6 @@ public class Management { }); } - /** - * Method performRegister. - * @param player Player - * @param password String - * @param email String - */ public void performRegister(final Player player, final String password, final String email) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -80,12 +53,6 @@ public class Management { }); } - /** - * Method performUnregister. - * @param player Player - * @param password String - * @param force boolean - */ public void performUnregister(final Player player, final String password, final boolean force) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -96,10 +63,6 @@ public class Management { }); } - /** - * Method performJoin. - * @param player Player - */ public void performJoin(final Player player) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -111,11 +74,6 @@ public class Management { }); } - /** - * Method performQuit. - * @param player Player - * @param isKick boolean - */ public void performQuit(final Player player, final boolean isKick) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -127,12 +85,6 @@ public class Management { }); } - /** - * Method performAddEmail. - * @param player Player - * @param newEmail String - * @param newEmailVerify String - */ public void performAddEmail(final Player player, final String newEmail, final String newEmailVerify) { sched.runTaskAsynchronously(plugin, new Runnable() { @Override @@ -142,12 +94,6 @@ public class Management { }); } - /** - * Method performChangeEmail. - * @param player Player - * @param oldEmail String - * @param newEmail String - */ public void performChangeEmail(final Player player, final String oldEmail, final String newEmail) { sched.runTaskAsynchronously(plugin, new Runnable() { @Override diff --git a/src/test/java/fr/xephi/authme/command/executable/email/AddEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/AddEmailCommandTest.java new file mode 100644 index 00000000..795fe1be --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/email/AddEmailCommandTest.java @@ -0,0 +1,63 @@ +package fr.xephi.authme.command.executable.email; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.AuthMeMockUtil; +import fr.xephi.authme.command.CommandParts; +import fr.xephi.authme.process.Management; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.Arrays; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test for {@link AddEmailCommand}. + */ +public class AddEmailCommandTest { + + private AuthMe authMeMock; + private Management managementMock; + + @Before + public void setUpMocks() { + AuthMeMockUtil.mockAuthMeInstance(); + authMeMock = AuthMe.getInstance(); + managementMock = Mockito.mock(Management.class); + when(authMeMock.getManagement()).thenReturn(managementMock); + } + + @Test + public void shouldRejectNonPlayerSender() { + // given + CommandSender sender = Mockito.mock(BlockCommandSender.class); + AddEmailCommand command = new AddEmailCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + verify(authMeMock, never()).getManagement(); + } + + @Test + public void shouldForwardData() { + // given + Player sender = Mockito.mock(Player.class); + AddEmailCommand command = new AddEmailCommand(); + + // when + command.executeCommand(sender, new CommandParts(), + new CommandParts(Arrays.asList("mail@example", "other_example"))); + + // then + verify(authMeMock).getManagement(); + verify(managementMock).performAddEmail(sender, "mail@example", "other_example"); + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java new file mode 100644 index 00000000..db6e6b84 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java @@ -0,0 +1,63 @@ +package fr.xephi.authme.command.executable.email; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.AuthMeMockUtil; +import fr.xephi.authme.command.CommandParts; +import fr.xephi.authme.process.Management; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.Arrays; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test for {@link ChangeEmailCommand}. + */ +public class ChangeEmailCommandTest { + + private AuthMe authMeMock; + private Management managementMock; + + @Before + public void setUpMocks() { + AuthMeMockUtil.mockAuthMeInstance(); + authMeMock = AuthMe.getInstance(); + managementMock = Mockito.mock(Management.class); + when(authMeMock.getManagement()).thenReturn(managementMock); + } + + @Test + public void shouldRejectNonPlayerSender() { + // given + CommandSender sender = Mockito.mock(BlockCommandSender.class); + ChangeEmailCommand command = new ChangeEmailCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + verify(authMeMock, never()).getManagement(); + } + + @Test + public void shouldForwardData() { + // given + Player sender = Mockito.mock(Player.class); + ChangeEmailCommand command = new ChangeEmailCommand(); + + // when + command.executeCommand(sender, new CommandParts(), + new CommandParts(Arrays.asList("new.mail@example.org", "old_mail@example.org"))); + + // then + verify(authMeMock).getManagement(); + verify(managementMock).performChangeEmail(sender, "new.mail@example.org", "old_mail@example.org"); + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java new file mode 100644 index 00000000..bd1e2e44 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java @@ -0,0 +1,36 @@ +package fr.xephi.authme.command.executable.email; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.AuthMeMockUtil; +import fr.xephi.authme.command.CommandParts; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * Test for {@link RecoverEmailCommand}. + */ +public class RecoverEmailCommandTest { + + @Before + public void setUpMocks() { + AuthMeMockUtil.mockAuthMeInstance(); + } + + @Test + public void shouldRejectNonPlayerSender() { + // given + CommandSender sender = Mockito.mock(BlockCommandSender.class); + RecoverEmailCommand command = new RecoverEmailCommand(); + + // when + command.executeCommand(sender, new CommandParts(), new CommandParts()); + + // then + } + + // TODO ljacqu 20151121: Expand tests. This command doesn't use a scheduler and has all of its + // logic inside here. +} From 579c51f4a2f3335360f147ffe613ce5c3ea74666 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sat, 21 Nov 2015 20:16:07 +0100 Subject: [PATCH 076/199] Remove the old Vault group management system. (THIS COMMIT BREAKS THE COMPILATION) --- src/main/java/fr/xephi/authme/AuthMe.java | 34 +++---------------- .../executable/authme/ForceLoginCommand.java | 1 + .../executable/authme/GetIpCommand.java | 1 + .../executable/email/AddEmailCommand.java | 1 - .../authme/listener/AuthMeServerListener.java | 6 ---- .../authme/permission/PermissionsManager.java | 6 ---- 6 files changed, 7 insertions(+), 42 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index c253ca5b..de1a3d39 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -70,9 +70,8 @@ public class AuthMe extends JavaPlugin { /** Defines the current AuthMeReloaded version name. */ private static final String PLUGIN_VERSION_NAME = "5.1-SNAPSHOT"; /** Defines the current AuthMeReloaded version code. */ - private static final int PLUGIN_VERSION_CODE = 100; // Increase this number - // by one when an update - // is released + // Increase this number by one when an update is released + private static final int PLUGIN_VERSION_CODE = 100; private static AuthMe plugin; private static Server server; @@ -96,7 +95,6 @@ public class AuthMe extends JavaPlugin { public boolean delayedAntiBot = true; // Hooks TODO: move into modules - public Permission vaultGroupManagement; // TODO: Remove this instance, and replace every usage with permissions manager! public Essentials ess; public MultiverseCore multiverse; public CombatTagPlus combatTagPlus; @@ -278,9 +276,6 @@ public class AuthMe extends JavaPlugin { mail = new SendMailSSL(this); } - // Find Permissions - checkVault(); - // Check Combat Tag Plus Version checkCombatTagPlus(); @@ -525,21 +520,6 @@ public class AuthMe extends JavaPlugin { }); } - // Check the presence of the Vault plugin and a permissions provider - public void checkVault() { - if (server.getPluginManager().isPluginEnabled("Vault")) { - RegisteredServiceProvider permissionProvider = server.getServicesManager().getRegistration(net.milkbowl.vault.permission.Permission.class); - if (permissionProvider != null) { - vaultGroupManagement = permissionProvider.getProvider(); - ConsoleLogger.info("Vault detected, hooking with the " + vaultGroupManagement.getName() + " group management system..."); - } else { - ConsoleLogger.showError("Vault detected, but I can't find any group management plugin to hook with!"); - } - } else { - vaultGroupManagement = null; - } - } - // Get the Multiverse plugin public void checkMultiverse() { if (Settings.multiverse && server.getPluginManager().isPluginEnabled("Multiverse-Core")) { @@ -706,17 +686,13 @@ public class AuthMe extends JavaPlugin { } ConsoleLogger.info("AutoPurging the Database: " + cleared.size() + " accounts removed!"); if (Settings.purgeEssentialsFile && this.ess != null) - dataManager.purgeEssentials(cleared); // name to UUID convertion - // needed with latest versions + dataManager.purgeEssentials(cleared); if (Settings.purgePlayerDat) - dataManager.purgeDat(cleared); // name to UUID convertion needed - // with latest versions of MC + dataManager.purgeDat(cleared); if (Settings.purgeLimitedCreative) dataManager.purgeLimitedCreative(cleared); if (Settings.purgeAntiXray) - dataManager.purgeAntiXray(cleared); // IDK if it uses UUID or - // names... (Actually it purges - // only names!) + dataManager.purgeAntiXray(cleared); if (Settings.purgePermissions) dataManager.purgePermissions(cleared, vaultGroupManagement); } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java index 0a9fd944..8d2179b9 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java @@ -24,6 +24,7 @@ public class ForceLoginCommand extends ExecutableCommand { // Command logic try { + @SuppressWarnings("deprecation") Player player = Bukkit.getPlayer(playerName); if (player == null || !player.isOnline()) { sender.sendMessage("Player needs to be online!"); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java index 343642c5..610df993 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java @@ -22,6 +22,7 @@ public class GetIpCommand extends ExecutableCommand { if(commandArguments.getCount() >= 1) playerName = commandArguments.get(0); + @SuppressWarnings("deprecation") Player player = Bukkit.getPlayer(playerName); if (player == null) { sender.sendMessage("This player is not actually online"); diff --git a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java index 68f5e54b..d3126111 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java @@ -6,7 +6,6 @@ import org.bukkit.entity.Player; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.CommandParts; import fr.xephi.authme.command.ExecutableCommand; -import fr.xephi.authme.settings.Messages; /** */ diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java index 862ac929..ec17eaaf 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java @@ -90,10 +90,6 @@ public class AuthMeServerListener implements Listener { plugin.combatTagPlus = null; ConsoleLogger.info("CombatTagPlus has been disabled, unhook!"); } - if (pluginName.equalsIgnoreCase("Vault")) { - plugin.vaultGroupManagement = null; - ConsoleLogger.showError("Vault has been disabled, unhook permissions!"); - } if (pluginName.equalsIgnoreCase("ProtocolLib")) { plugin.inventoryProtector = null; ConsoleLogger.showError("ProtocolLib has been disabled, unhook packet inventory protection!"); @@ -116,8 +112,6 @@ public class AuthMeServerListener implements Listener { plugin.checkMultiverse(); if (pluginName.equalsIgnoreCase("CombatTagPlus")) plugin.checkCombatTagPlus(); - if (pluginName.equalsIgnoreCase("Vault")) - plugin.checkVault(); if (pluginName.equalsIgnoreCase("ProtocolLib")) { plugin.checkProtocolLib(); } diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 8640d3e1..9827b0d7 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -560,7 +560,6 @@ public class PermissionsManager { * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ - @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public boolean addGroup(Player player, String groupName) { // If no permissions system is used, return false if(!isEnabled()) @@ -623,7 +622,6 @@ public class PermissionsManager { * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ - @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public boolean addGroups(Player player, List groupNames) { // If no permissions system is used, return false if(!isEnabled()) @@ -648,7 +646,6 @@ public class PermissionsManager { * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ - @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public boolean removeGroup(Player player, String groupName) { // If no permissions system is used, return false if(!isEnabled()) @@ -711,7 +708,6 @@ public class PermissionsManager { * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ - @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public boolean removeGroups(Player player, List groupNames) { // If no permissions system is used, return false if(!isEnabled()) @@ -737,7 +733,6 @@ public class PermissionsManager { * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ - @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public boolean setGroup(Player player, String groupName) { // If no permissions system is used, return false if(!isEnabled()) @@ -808,7 +803,6 @@ public class PermissionsManager { * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ - @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public boolean setGroups(Player player, List groupNames) { // If no permissions system is used or if there's no group supplied, return false if(!isEnabled() || groupNames.size() <= 0) From 7902cd87bb6a5abe099755104716e7bfcdefb2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 20:47:56 +0100 Subject: [PATCH 077/199] Added return statement for setGroup method, updated it's JavaDocs --- src/main/java/fr/xephi/authme/util/Utils.java | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 278cf41a..457bac0e 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -121,11 +121,15 @@ public class Utils { } /** - * Method setGroup. - * @param player Player - * @param group GroupType + * Set the group of a player, by it's AuthMe group type. + * + * @param player The player. + * @param group The group type. + * + * @return True if succeed, false otherwise. + * False is also returned if groups aren't supported with the current permissions system. */ - public static void setGroup(Player player, GroupType group) { + public static boolean setGroup(Player player, GroupType group) { if(!Settings.isPermissionCheckEnabled) return; @@ -143,34 +147,33 @@ public class Utils { case UNREGISTERED: // Remove the other group type groups, set the current group permsMan.removeGroups(player, Arrays.asList(Settings.getRegisteredGroup, Settings.getUnloggedinGroup)); - permsMan.addGroup(player, Settings.unRegisteredGroup); - break; + return permsMan.addGroup(player, Settings.unRegisteredGroup); case REGISTERED: // Remove the other group type groups, set the current group permsMan.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getUnloggedinGroup)); - permsMan.addGroup(player, Settings.getRegisteredGroup); - break; + return permsMan.addGroup(player, Settings.getRegisteredGroup); case NOTLOGGEDIN: // Remove the other group type groups, set the current group permsMan.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getRegisteredGroup)); - permsMan.addGroup(player, Settings.getUnloggedinGroup); - break; + return permsMan.addGroup(player, Settings.getUnloggedinGroup); case LOGGEDIN: // Get the limbo player data LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(player.getName().toLowerCase()); if(limbo == null) - break; + return false; // Get the players group String realGroup = limbo.getGroup(); // Remove the other group types groups, set the real group permsMan.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getRegisteredGroup, Settings.getUnloggedinGroup)); - permsMan.addGroup(player, realGroup); - break; + return permsMan.addGroup(player, realGroup); + + default: + return false; } } From 89b5444d697f844c1b43224ddad3776cfbcc7e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 20:50:12 +0100 Subject: [PATCH 078/199] Fixed addNormal method, updated it's JavaDocs --- src/main/java/fr/xephi/authme/util/Utils.java | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 457bac0e..aa1ba5c5 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -178,27 +178,30 @@ public class Utils { } /** - * Method addNormal. - * @param player Player - * @param group String - - * @return boolean */ + * TODO: This method requires better explanation. + * + * Set the normal group of a player. + * + * @param player The player. + * @param group The normal group. + + * @return True on success, false on failure. + */ public static boolean addNormal(Player player, String group) { - if (!useGroupSystem()) { + if(!Settings.isPermissionCheckEnabled) return false; - } - if (plugin.vaultGroupManagement == null) - return false; - try { - if (plugin.vaultGroupManagement.playerRemoveGroup(player, Settings.getUnloggedinGroup) && plugin.vaultGroupManagement.playerAddGroup(player, group)) { - return true; - } - } catch (UnsupportedOperationException e) { - ConsoleLogger.showError("Your permission system (" + plugin.vaultGroupManagement.getName() + ") do not support Group system with that config... unhook!"); - plugin.vaultGroupManagement = null; - return false; - } - return false; + + // Get the permissions manager, and make sure it's valid + PermissionsManager permsMan = plugin.getPermissionsManager(); + if(permsMan == null) + ConsoleLogger.showError("Failed to access permissions manager instance, shutting down."); + assert permsMan != null; + + // Remove old groups + permsMan.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getRegisteredGroup, Settings.getUnloggedinGroup)); + + // Add the normal group, return the result + return permsMan.addGroup(player, group); } // TODO: Move to a Manager From 0a5c0817320af79a050f51ad72a55d0f5e852384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 20:59:23 +0100 Subject: [PATCH 079/199] Added method to permissions manager to get primary group of player --- .../authme/permission/PermissionsManager.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 9827b0d7..9ba1dcf1 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -493,6 +493,59 @@ public class PermissionsManager { } } + /** + * Get the primary group of a player, if available. + * + * @param player The player. + * + * @return The name of the primary permission group. Or null. + */ + @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) + public String getPrimaryGroup(Player player) { + // If no permissions system is used, return an empty list + if(!isEnabled()) + return null; + + switch(this.permsType) { + case PERMISSIONS_EX: + case PERMISSIONS_BUKKIT: + case B_PERMISSIONS: + case PERMISSIONS: // FIXME: Is this correct for PERMISSIONS? + // Get the groups of the player + List groups = getGroups(player); + + // Make sure there is any group available, or return null + if(groups.size() == 0) + return null; + + // Return the first group + return groups.get(0); + + case ESSENTIALS_GROUP_MANAGER: + // Essentials Group Manager + final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); + if(handler == null) + return null; + return handler.getGroup(player.getName()); + + case Z_PERMISSIONS: + //zPermissions + return zPermissionsService.getPlayerPrimaryGroup(player.getName()); + + case VAULT: + // Vault + return vaultPerms.getPrimaryGroup(player); + + case NONE: + // Not hooked into any permissions system, return null + return null; + + default: + // Something went wrong, return null to prevent problems + return null; + } + } + /** * Check whether the player is in the specified group. * From 18af8f31715cbcd6088802d339db18e7d85851c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 21:02:53 +0100 Subject: [PATCH 080/199] Fixed addLimboPlayer method --- .../xephi/authme/cache/limbo/LimboCache.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java index 7205c708..c468a90c 100644 --- a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java @@ -2,6 +2,7 @@ package fr.xephi.authme.cache.limbo; import java.util.concurrent.ConcurrentHashMap; +import fr.xephi.authme.permission.PermissionsManager; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; @@ -13,6 +14,7 @@ import fr.xephi.authme.cache.backup.DataFileCache; import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.events.ResetInventoryEvent; import fr.xephi.authme.settings.Settings; +import org.bukkit.permissions.Permission; /** */ @@ -34,8 +36,9 @@ public class LimboCache { } /** - * Method addLimboPlayer. - * @param player Player + * Add a limbo player. + * + * @param player Player instance to add. */ public void addLimboPlayer(Player player) { String name = player.getName().toLowerCase(); @@ -45,6 +48,12 @@ public class LimboCache { String playerGroup = ""; boolean flying = false; + // Get the permissions manager, and make sure it's valid + PermissionsManager permsMan = this.plugin.getPermissionsManager(); + if(permsMan == null) + ConsoleLogger.showError("Unable to access permissions manager!"); + assert permsMan != null; + if (playerData.doesCacheExist(player)) { DataFileCache cache = playerData.readCache(player); if (cache != null) { @@ -55,14 +64,10 @@ public class LimboCache { } else { operator = player.isOp(); flying = player.isFlying(); - if (plugin.vaultGroupManagement != null) { - try { - playerGroup = plugin.vaultGroupManagement.getPrimaryGroup(player); - } catch (UnsupportedOperationException e) { - ConsoleLogger.showError("Your permission system (" + plugin.vaultGroupManagement.getName() + ") do not support Group system with that config... unhook!"); - plugin.vaultGroupManagement = null; - } - } + + // Check whether groups are supported + if(permsMan.hasGroupSupport()) + playerGroup = permsMan.getPrimaryGroup(player); } if (Settings.isForceSurvivalModeEnabled) { From e45aa885044409a1f7925c6606d0d3c3fa7fd611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 21:03:50 +0100 Subject: [PATCH 081/199] Fixed result not being returned --- src/main/java/fr/xephi/authme/util/Utils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index aa1ba5c5..957a948f 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -130,8 +130,9 @@ public class Utils { * False is also returned if groups aren't supported with the current permissions system. */ public static boolean setGroup(Player player, GroupType group) { + // Check whether the permissions check is enabled if(!Settings.isPermissionCheckEnabled) - return; + return false; // Get the permissions manager, and make sure it's valid PermissionsManager permsMan = plugin.getPermissionsManager(); From ada991904bdc2a52c68639c97c06c47e46737780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 21:07:32 +0100 Subject: [PATCH 082/199] Improved purgePermissions method --- .../java/fr/xephi/authme/DataManager.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/java/fr/xephi/authme/DataManager.java b/src/main/java/fr/xephi/authme/DataManager.java index edc96eee..3d74da41 100644 --- a/src/main/java/fr/xephi/authme/DataManager.java +++ b/src/main/java/fr/xephi/authme/DataManager.java @@ -7,6 +7,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import fr.xephi.authme.permission.PermissionsManager; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; @@ -172,13 +173,28 @@ public class DataManager { ConsoleLogger.info("AutoPurgeDatabase : Remove " + i + " EssentialsFiles"); } + // TODO: What is this method for? Is it correct? /** - * Method purgePermissions. - * @param cleared List - * @param permission Permission + * @param cleared Cleared players. */ - public synchronized void purgePermissions(List cleared, Permission permission) { + public synchronized void purgePermissions(List cleared) { + // Get the permissions manager, and make sure it's valid + PermissionsManager permsMan = this.plugin.getPermissionsManager(); + if(permsMan == null) + ConsoleLogger.showError("Unable to access permissions manager instance!"); + assert permsMan != null; + int i = 0; + for (String name : cleared) { + try { + permsMan.removeAllGroups(this.getOnlinePlayerLower(name.toLowerCase())); + i++; + } catch(Exception e) { + } + } + ConsoleLogger.info("AutoPurgeDatabase : Removed " + i + " permissions"); + + /*int i = 0; for (String name : cleared) { try { OfflinePlayer p = this.getOfflinePlayer(name); @@ -189,7 +205,7 @@ public class DataManager { } catch (Exception e) { } } - ConsoleLogger.info("AutoPurgeDatabase : Remove " + i + " Permissions"); + ConsoleLogger.info("AutoPurgeDatabase : Remove " + i + " Permissions");*/ } /** From 1ba18439e9692960ce40fa37e8f0b0be9cfd36f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sat, 21 Nov 2015 21:08:07 +0100 Subject: [PATCH 083/199] Fixed invalid method arguments --- src/main/java/fr/xephi/authme/AuthMe.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index de1a3d39..ba834251 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -694,7 +694,7 @@ public class AuthMe extends JavaPlugin { if (Settings.purgeAntiXray) dataManager.purgeAntiXray(cleared); if (Settings.purgePermissions) - dataManager.purgePermissions(cleared, vaultGroupManagement); + dataManager.purgePermissions(cleared); } // Return the spawn location of a player From cd728b569ee1c3f3392fd2ae60331da6739574d8 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 22:32:16 +0100 Subject: [PATCH 084/199] Create test for CommandManager; fix javadoc in CommandDescription --- .../authme/command/CommandDescription.java | 132 ++++++++---------- .../authme/command/CommandManagerTest.java | 41 ++++++ 2 files changed, 99 insertions(+), 74 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/command/CommandManagerTest.java diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java index 4feec54d..53054069 100644 --- a/src/main/java/fr/xephi/authme/command/CommandDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandDescription.java @@ -96,27 +96,13 @@ public class CommandDescription { setArguments(arguments); } - /** - * Get the first relative command label. - * - - * @return First relative command label. */ - public String getLabel() { - // Ensure there's any item in the command list - if(this.labels.size() == 0) - return ""; - - // Return the first command on the list - return this.labels.get(0); - } - /** * Get the label most similar to the reference. The first label will be returned if no reference was supplied. * * @param reference The command reference. * - - * @return The most similar label, or the first label. An empty label will be returned if no label was set. */ + * @return The most similar label, or the first label. An empty label will be returned if no label was set. + */ public String getLabel(CommandParts reference) { // Ensure there's any item in the command list if(this.labels.size() == 0) @@ -147,8 +133,8 @@ public class CommandDescription { /** * Get all relative command labels. * - - * @return All relative labels labels. */ + * @return All relative labels labels. + */ public List getLabels() { return this.labels; } @@ -160,11 +146,11 @@ public class CommandDescription { */ public void setLabels(List labels) { // Check whether the command label list should be cleared - if(labels == null) + if (labels == null) { this.labels.clear(); - - else + } else { this.labels = labels; + } } /** @@ -307,8 +293,9 @@ public class CommandDescription { /** * Get the absolute command label, without a slash. - - * @return String */ + * + * @return the absolute label + */ public String getAbsoluteLabel() { return getAbsoluteLabel(false); } @@ -316,9 +303,9 @@ public class CommandDescription { /** * Get the absolute command label. * - * @param includeSlash boolean - * @return Absolute command label. */ + * @return Absolute command label. + */ public String getAbsoluteLabel(boolean includeSlash) { return getAbsoluteLabel(includeSlash, null); } @@ -326,10 +313,10 @@ public class CommandDescription { /** * Get the absolute command label. * - * @param includeSlash boolean * @param reference CommandParts - * @return Absolute command label. */ + * @return Absolute command label. + */ public String getAbsoluteLabel(boolean includeSlash, CommandParts reference) { // Get the command reference, and make sure it is valid CommandParts out = getCommandReference(reference); @@ -345,8 +332,8 @@ public class CommandDescription { * * @param reference The reference to use as template, which is used to choose the most similar reference. * - - * @return Command reference. */ + * @return Command reference. + */ public CommandParts getCommandReference(CommandParts reference) { // Build the reference List referenceList = new ArrayList<>(); @@ -367,8 +354,8 @@ public class CommandDescription { * * @param other The other command reference. * - - * @return The command difference. Zero if there's no difference. A negative number on error. */ + * @return The command difference. Zero if there's no difference. A negative number on error. + */ public double getCommandDifference(CommandParts other) { return getCommandDifference(other, false); } @@ -379,8 +366,8 @@ public class CommandDescription { * @param other The other command reference. * @param fullCompare True to fully compare both command references. * - - * @return The command difference. Zero if there's no difference. A negative number on error. */ + * @return The command difference. Zero if there's no difference. A negative number on error. + */ public double getCommandDifference(CommandParts other, boolean fullCompare) { // Make sure the reference is valid if(other == null) @@ -396,8 +383,8 @@ public class CommandDescription { /** * Get the executable command. * - - * @return The executable command. */ + * @return The executable command. + */ public ExecutableCommand getExecutableCommand() { return this.executableCommand; } @@ -414,8 +401,8 @@ public class CommandDescription { /** * Check whether this command is executable, based on the assigned executable command. * - - * @return True if this command is executable. */ + * @return True if this command is executable. + */ public boolean isExecutable() { return this.executableCommand != null; } @@ -427,8 +414,8 @@ public class CommandDescription { * @param commandReference The command reference. * @param commandArguments The command arguments. * - - * @return True on success, false on failure. */ + * @return True on success, false on failure. + */ public boolean execute(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // Make sure the command is executable if(!isExecutable()) @@ -592,8 +579,8 @@ public class CommandDescription { /** * Get all command arguments. * - - * @return Command arguments. */ + * @return Command arguments. + */ public List getArguments() { return this.arguments; } @@ -605,11 +592,11 @@ public class CommandDescription { */ public void setArguments(List arguments) { // Convert null into an empty argument list - if(arguments == null) + if (arguments == null) { this.arguments.clear(); - - else + } else { this.arguments = arguments; + } } /** @@ -617,22 +604,17 @@ public class CommandDescription { * * @param argument The argument to check for. * - - * @return True if this argument already exists, false otherwise. */ + * @return True if this argument already exists, false otherwise. + */ public boolean hasArgument(CommandArgumentDescription argument) { - // Make sure the argument is valid - if(argument == null) - return false; - - // Check whether the argument exists, return the result - return this.arguments.contains(argument); + return argument != null && arguments.contains(argument); } /** * Check whether this command has any arguments. * - - * @return True if this command has any arguments. */ + * @return True if this command has any arguments. + */ public boolean hasArguments() { return (this.arguments.size() != 0); } @@ -640,8 +622,8 @@ public class CommandDescription { /** * The minimum number of arguments required for this command. * - - * @return The minimum number of required arguments. */ + * @return The minimum number of required arguments. + */ public int getMinimumArguments() { // Get the number of required and optional arguments int requiredArguments = 0; @@ -838,23 +820,26 @@ public class CommandDescription { * * @param commandReference The command reference. * - - * @return The difference in argument count between the reference and the actual command. */ + * @return The difference in argument count between the reference and the actual command. + */ public int getSuitableArgumentsDifference(CommandParts commandReference) { // Make sure the command reference is valid - if(commandReference.getCount() <= 0) + if (commandReference.getCount() <= 0) { return -1; + } // Get the remaining command reference element count int remainingElementCount = commandReference.getCount() - getParentCount() - 1; - // Check if there are too less arguments - if(getMinimumArguments() > remainingElementCount) + // Check if there are too few arguments + if (getMinimumArguments() > remainingElementCount) { return Math.abs(getMinimumArguments() - remainingElementCount); + } // Check if there are too many arguments - if(getMaximumArguments() < remainingElementCount && getMaximumArguments() >= 0) + if (getMaximumArguments() < remainingElementCount && getMaximumArguments() >= 0) { return Math.abs(remainingElementCount - getMaximumArguments()); + } // The arguments seem to be EQUALS, return the result return 0; @@ -863,8 +848,8 @@ public class CommandDescription { /** * Get the command permissions. * - - * @return The command permissions. */ + * @return The command permissions. + */ public CommandPermissions getCommandPermissions() { return this.permissions; } @@ -894,30 +879,29 @@ public class CommandDescription { * @param commandLabel The first command label. * @param otherCommandLabel The other command label. * - - * @return True if the labels are equal to each other. */ + * @return True if the labels are equal to each other. + */ private static boolean commandLabelEquals(String commandLabel, String otherCommandLabel) { - // Trim the command labels from unwanted whitespaces commandLabel = commandLabel.trim(); otherCommandLabel = otherCommandLabel.trim(); - - // Check whether the the two command labels are equal (case insensitive) - return (commandLabel.equalsIgnoreCase(otherCommandLabel)); + return commandLabel.equalsIgnoreCase(otherCommandLabel); } /** * Check whether the command description has been set up properly. * - - * @return True if the command description is valid, false otherwise. */ + * @return True if the command description is valid, false otherwise. + */ public boolean isValid() { // Make sure any command label is set - if(getLabels().size() == 0) + if (getLabels().size() == 0) { return false; + } // Make sure the permissions are set up properly - if(this.permissions == null) + if (this.permissions == null) { return false; + } // Everything seems to be correct, return the result return true; diff --git a/src/test/java/fr/xephi/authme/command/CommandManagerTest.java b/src/test/java/fr/xephi/authme/command/CommandManagerTest.java new file mode 100644 index 00000000..9309d4a0 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/CommandManagerTest.java @@ -0,0 +1,41 @@ +package fr.xephi.authme.command; + +import org.junit.Test; + +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link CommandManager}. + */ +public class CommandManagerTest { + + @Test + public void shouldInitializeCommands() { + // given/when + CommandManager manager = new CommandManager(true); + int commandCount = manager.getCommandDescriptionCount(); + List commands = manager.getCommandDescriptions(); + + // then + // It obviously doesn't make sense to test much of the concrete data + // that is being initialized; we just want to guarantee with this test + // that data is indeed being initialized and we take a few "probes" + assertThat(commandCount, equalTo(9)); + assertThat(commandsIncludeLabel(commands, "authme"), equalTo(true)); + assertThat(commandsIncludeLabel(commands, "register"), equalTo(true)); + assertThat(commandsIncludeLabel(commands, "help"), equalTo(false)); + } + + private static boolean commandsIncludeLabel(Iterable commands, String label) { + for (CommandDescription command : commands) { + if (command.getLabels().contains(label)) { + return true; + } + } + return false; + } + +} From fc3f685de0cf75a78f78b48ce8b220f43724b5d8 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Nov 2015 23:09:04 +0100 Subject: [PATCH 085/199] Create UtilsTest Had to create a getGameServer() method in AuthMe in order to be able to catch this call in tests and replace it with a mock implementation of the actual server. The supertype method getServer() is declared as final, which makes it impossible to mock with Mockito. While there are frameworks that manipulate the bytecode to mock final calls, it is much easier and less risky to simply delegate the server retrieval through another method. --- src/main/java/fr/xephi/authme/AuthMe.java | 11 ++- src/main/java/fr/xephi/authme/util/Utils.java | 73 ++++--------------- .../java/fr/xephi/authme/util/UtilsTest.java | 73 +++++++++++++++++++ 3 files changed, 98 insertions(+), 59 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/util/UtilsTest.java diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index ba834251..fda2bde0 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -22,7 +22,6 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitTask; import org.mcstats.Metrics; @@ -933,4 +932,14 @@ public class AuthMe extends JavaPlugin { public Management getManagement() { return management; } + + /** + * Returns the server instance running this plugin. Use this method in favor of + * {@link AuthMe#getServer()} for testability purposes. + * + * @return the server instance + */ + public Server getGameServer() { + return super.getServer(); + } } diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 957a948f..aceb6d74 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -27,7 +27,7 @@ import java.util.zip.GZIPInputStream; /** */ -public class Utils { +public final class Utils { public static AuthMe plugin; @@ -45,11 +45,11 @@ public class Utils { } } - // Check and Download GeoIP data if not exist - /** - * Method checkGeoIP. - - * @return boolean */ + private Utils() { + // Utility class + } + + // Check and Download GeoIP data if it doesn't exist public static boolean checkGeoIP() { if (lookupService != null) { return true; @@ -59,14 +59,15 @@ public class Utils { if (lookupService == null) { try { lookupService = new LookupService(data); - ConsoleLogger.info("[LICENSE] This product uses data from the GeoLite API created by MaxMind, available at http://www.maxmind.com"); + ConsoleLogger.info("[LICENSE] This product uses data from the GeoLite API created by MaxMind, " + + "available at http://www.maxmind.com"); return true; } catch (IOException e) { return false; } } } - plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { + plugin.getGameServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { @Override public void run() { try { @@ -96,11 +97,6 @@ public class Utils { return false; } - /** - * Method getCountryCode. - * @param ip String - - * @return String */ public static String getCountryCode(String ip) { if (checkGeoIP()) { return lookupService.getCountry(ip).getCode(); @@ -108,11 +104,6 @@ public class Utils { return "--"; } - /** - * Method getCountryName. - * @param ip String - - * @return String */ public static String getCountryName(String ip) { if (checkGeoIP()) { return lookupService.getCountry(ip).getName(); @@ -121,7 +112,7 @@ public class Utils { } /** - * Set the group of a player, by it's AuthMe group type. + * Set the group of a player, by its AuthMe group type. * * @param player The player. * @param group The group type. @@ -206,11 +197,6 @@ public class Utils { } // TODO: Move to a Manager - /** - * Method checkAuth. - * @param player Player - - * @return boolean */ public static boolean checkAuth(Player player) { if (player == null || Utils.isUnrestricted(player)) { return true; @@ -230,20 +216,11 @@ public class Utils { return false; } - /** - * Method isUnrestricted. - * @param player Player - - * @return boolean */ public static boolean isUnrestricted(Player player) { return Settings.isAllowRestrictedIp && !Settings.getUnrestrictedName.isEmpty() && (Settings.getUnrestrictedName.contains(player.getName())); } - /** - * Method useGroupSystem. - - * @return boolean */ private static boolean useGroupSystem() { return Settings.isPermissionCheckEnabled && !Settings.getUnloggedinGroup.isEmpty(); } @@ -286,12 +263,14 @@ public class Utils { * Used for force player GameMode */ /** - * Method forceGM. - * @param player Player + * Force the game mode of a player. + * + * @param player the player to modify. */ public static void forceGM(Player player) { - if (!plugin.getPermissionsManager().hasPermission(player, "authme.bypassforcesurvival")) + if (!plugin.getPermissionsManager().hasPermission(player, "authme.bypassforcesurvival")) { player.setGameMode(GameMode.SURVIVAL); + } } /** @@ -303,10 +282,6 @@ public class Utils { LOGGEDIN } - /** - * Method purgeDirectory. - * @param file File - */ public static void purgeDirectory(File file) { if (!file.isDirectory()) { return; @@ -325,10 +300,6 @@ public class Utils { } } - /** - * Method getOnlinePlayers. - - * @return Collection */ @SuppressWarnings("unchecked") public static Collection getOnlinePlayers() { if (getOnlinePlayersIsCollection) { @@ -348,22 +319,12 @@ public class Utils { return Collections.emptyList(); } - /** - * Method getPlayer. - * @param name String - - * @return Player */ @SuppressWarnings("deprecation") public static Player getPlayer(String name) { name = name.toLowerCase(); return plugin.getServer().getPlayer(name); } - /** - * Method isNPC. - * @param player Entity - - * @return boolean */ public static boolean isNPC(final Entity player) { try { if (player.hasMetadata("NPC")) { @@ -379,10 +340,6 @@ public class Utils { } } - /** - * Method teleportToSpawn. - * @param player Player - */ public static void teleportToSpawn(Player player) { if (Settings.isTeleportToSpawnEnabled && !Settings.noTeleport) { Location spawn = plugin.getSpawnLocation(player); diff --git a/src/test/java/fr/xephi/authme/util/UtilsTest.java b/src/test/java/fr/xephi/authme/util/UtilsTest.java new file mode 100644 index 00000000..85aaf4e2 --- /dev/null +++ b/src/test/java/fr/xephi/authme/util/UtilsTest.java @@ -0,0 +1,73 @@ +package fr.xephi.authme.util; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.AuthMeMockUtil; +import fr.xephi.authme.permission.PermissionsManager; +import org.bukkit.GameMode; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test for the {@link Utils} class. + */ +public class UtilsTest { + + private AuthMe authMeMock; + private PermissionsManager permissionsManagerMock; + + @Before + public void setUpMocks() { + AuthMeMockUtil.mockAuthMeInstance(); + authMeMock = AuthMe.getInstance(); + + permissionsManagerMock = mock(PermissionsManager.class); + when(authMeMock.getPermissionsManager()).thenReturn(permissionsManagerMock); + + Server serverMock = mock(Server.class); + when(authMeMock.getGameServer()).thenReturn(serverMock); + + BukkitScheduler schedulerMock = mock(BukkitScheduler.class); + when(serverMock.getScheduler()).thenReturn(schedulerMock); + when(schedulerMock.runTaskAsynchronously(any(Plugin.class), any(Runnable.class))) + .thenReturn(mock(BukkitTask.class)); + } + + @Test + public void shouldForceSurvivalGameMode() { + // given + Player player = mock(Player.class); + given(permissionsManagerMock.hasPermission(player, "authme.bypassforcesurvival")).willReturn(false); + + // when + Utils.forceGM(player); + + // then + verify(player).setGameMode(GameMode.SURVIVAL); + } + + @Test + public void shouldNotForceGameModeForUserWithBypassPermission() { + // given + Player player = mock(Player.class); + given(permissionsManagerMock.hasPermission(player, "authme.bypassforcesurvival")).willReturn(true); + + // when + Utils.forceGM(player); + + // then + verify(player, never()).setGameMode(GameMode.SURVIVAL); + } + +} From e456203fc6238f0aee052a13710182de4326fa4d Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 22 Nov 2015 01:30:07 +0100 Subject: [PATCH 086/199] Refactor Utils#getOnlinePlayers and add background info --- .../executable/authme/VersionCommand.java | 12 +++-- src/main/java/fr/xephi/authme/util/Utils.java | 51 ++++++++++++++----- .../java/fr/xephi/authme/util/UtilsTest.java | 50 +++++++++++++++--- 3 files changed, 91 insertions(+), 22 deletions(-) diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java index 5367b8b1..a09fc1b6 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java @@ -73,12 +73,16 @@ public class VersionCommand extends ExecutableCommand { * * @param minecraftName The Minecraft player name. * - - * @return True if the player is online, false otherwise. */ + * @return True if the player is online, false otherwise. + */ private boolean isPlayerOnline(String minecraftName) { - for(Player player : Bukkit.getOnlinePlayers()) - if(player.getName().equalsIgnoreCase(minecraftName)) + // Note ljacqu 20151121: Generally you should use Utils#getOnlinePlayers to retrieve the list of online players. + // If it's only used in a for-each loop such as here, it's fine. For other purposes, go through the Utils class. + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.getName().equalsIgnoreCase(minecraftName)) { return true; + } + } return false; } } diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index aceb6d74..7e220e3a 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -17,6 +17,7 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import java.io.*; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLConnection; @@ -26,23 +27,20 @@ import java.util.Collections; import java.util.zip.GZIPInputStream; /** + * Utility class for various operations used in the codebase. */ public final class Utils { public static AuthMe plugin; - private static boolean getOnlinePlayersIsCollection; + private static boolean getOnlinePlayersIsCollection = false; private static Method getOnlinePlayers; private static LookupService lookupService; static { plugin = AuthMe.getInstance(); checkGeoIP(); - try { - Method m = Bukkit.class.getDeclaredMethod("getOnlinePlayers"); - getOnlinePlayersIsCollection = m.getReturnType() == Collection.class; - } catch (Exception ignored) { - } + initializeOnlinePlayersIsCollectionField(); } private Utils() { @@ -259,9 +257,6 @@ public final class Utils { }); } - /* - * Used for force player GameMode - */ /** * Force the game mode of a player. * @@ -300,25 +295,57 @@ public final class Utils { } } + /** + * 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 SpigotMC forum + * @see StackOverflow + */ @SuppressWarnings("unchecked") public static Collection 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.getMethod("getOnlinePlayers"); } Object obj = getOnlinePlayers.invoke(null); - if (obj instanceof Collection) { + if (obj instanceof Collection) { return (Collection) obj; + } else if (obj instanceof Player[]) { + return Arrays.asList((Player[]) obj); + } else { + String type = (obj != null) ? obj.getClass().getName() : "null"; + ConsoleLogger.showError("Unknown list of online players of type " + type); } - return Arrays.asList((Player[]) obj); - } catch (Exception ignored) { + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + ConsoleLogger.showError("Could not retrieve list of online players: [" + + e.getClass().getName() + "] " + e.getMessage()); } return Collections.emptyList(); } + /** + * Method run when the Utils class is loaded to verify whether or not the Bukkit + * implementation returns the online players as a Collection. + * + * @see Utils#getOnlinePlayers() + */ + private static void initializeOnlinePlayersIsCollectionField() { + try { + Method method = Bukkit.class.getDeclaredMethod("getOnlinePlayers"); + getOnlinePlayersIsCollection = method.getReturnType() == Collection.class; + } catch (NoSuchMethodException e) { + ConsoleLogger.showError("Error verifying if getOnlinePlayers is a collection! Method doesn't exist"); + } + } + @SuppressWarnings("deprecation") public static Player getPlayer(String name) { name = name.toLowerCase(); diff --git a/src/test/java/fr/xephi/authme/util/UtilsTest.java b/src/test/java/fr/xephi/authme/util/UtilsTest.java index 85aaf4e2..d095746b 100644 --- a/src/test/java/fr/xephi/authme/util/UtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/UtilsTest.java @@ -3,7 +3,6 @@ package fr.xephi.authme.util; import fr.xephi.authme.AuthMe; import fr.xephi.authme.AuthMeMockUtil; import fr.xephi.authme.permission.PermissionsManager; -import org.bukkit.GameMode; import org.bukkit.Server; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; @@ -12,11 +11,13 @@ import org.bukkit.scheduler.BukkitTask; import org.junit.Before; import org.junit.Test; -import static org.mockito.BDDMockito.given; +import java.lang.reflect.Field; +import java.util.Collection; + +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -44,7 +45,8 @@ public class UtilsTest { .thenReturn(mock(BukkitTask.class)); } - @Test + // TODO ljacques 20151122: The tests for Utils.forceGM somehow can't be set up with the mocks correctly + /*@Test public void shouldForceSurvivalGameMode() { // given Player player = mock(Player.class); @@ -67,7 +69,43 @@ public class UtilsTest { Utils.forceGM(player); // then - verify(player, never()).setGameMode(GameMode.SURVIVAL); + verify(authMeMock).getPermissionsManager(); + verify(permissionsManagerMock).hasPermission(player, "authme.bypassforcesurvival"); + verify(player, never()).setGameMode(any(GameMode.class)); + }*/ + + @Test + // Note ljacqu 20151122: This is a heavy test setup with Reflections... If it causes trouble, skip it with @Ignore + public void shouldRetrieveListOfOnlinePlayersFromReflectedMethod() { + // given + setField("getOnlinePlayersIsCollection", false); + try { + setField("getOnlinePlayers", UtilsTest.class.getDeclaredMethod("onlinePlayersImpl")); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Cannot initialize test with custom test method", e); + } + + // when + Collection players = Utils.getOnlinePlayers(); + + // then + assertThat(players, hasSize(2)); + } + + private static void setField(String name, Object value) { + try { + Field field = Utils.class.getDeclaredField(name); + field.setAccessible(true); + field.set(null, value); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Could not set field '" + name + "'", e); + } + } + + public static Player[] onlinePlayersImpl() { + return new Player[]{ + mock(Player.class), mock(Player.class) + }; } } From 215fedc585e9e66a2dc6048339bd7ab7168dd4b4 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 22 Nov 2015 02:02:49 +0100 Subject: [PATCH 087/199] Minor refactoring in CustomConfiguration / Messages - CustomConfiguration should be abstract - Reduce visibility of internal method - Fix typo in English messages - Remove or replace unnecessary javadoc --- .../authme/settings/CustomConfiguration.java | 19 +++-------------- .../fr/xephi/authme/settings/Messages.java | 21 ++++--------------- src/main/resources/messages/messages_en.yml | 2 +- 3 files changed, 8 insertions(+), 34 deletions(-) diff --git a/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java b/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java index bb470359..15e651aa 100644 --- a/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java +++ b/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java @@ -15,13 +15,13 @@ import fr.xephi.authme.ConsoleLogger; /** */ -public class CustomConfiguration extends YamlConfiguration { +public abstract class CustomConfiguration extends YamlConfiguration { private File configFile; /** * Constructor for CustomConfiguration. - * @param file File + * @param file the config file */ public CustomConfiguration(File file) { this.configFile = file; @@ -41,10 +41,6 @@ public class CustomConfiguration extends YamlConfiguration { } } - /** - * Method reLoad. - - * @return boolean */ public boolean reLoad() { boolean out = true; if (!configFile.exists()) { @@ -63,20 +59,11 @@ public class CustomConfiguration extends YamlConfiguration { } } - /** - * Method getConfigFile. - - * @return File */ public File getConfigFile() { return configFile; } - /** - * Method loadResource. - * @param file File - - * @return boolean */ - public boolean loadResource(File file) { + private boolean loadResource(File file) { if (!file.exists()) { try { if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) { diff --git a/src/main/java/fr/xephi/authme/settings/Messages.java b/src/main/java/fr/xephi/authme/settings/Messages.java index 46957da6..15b10c4d 100644 --- a/src/main/java/fr/xephi/authme/settings/Messages.java +++ b/src/main/java/fr/xephi/authme/settings/Messages.java @@ -15,8 +15,8 @@ public class Messages extends CustomConfiguration { /** * Constructor for Messages. - * @param file File - * @param lang String + * @param file the configuration file + * @param lang the code of the language to use */ public Messages(File file, String lang) { super(file); @@ -25,14 +25,10 @@ public class Messages extends CustomConfiguration { this.lang = lang; } - /** - * Method send. - * @param sender CommandSender - * @param msg String - */ public void send(CommandSender sender, String msg) { - if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.lang)) + if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.lang)) { singleton.reloadMessages(); + } String loc = (String) singleton.get(msg); if (loc == null) { loc = "Error with Translation files, please contact the admin for verify or update translation"; @@ -43,11 +39,6 @@ public class Messages extends CustomConfiguration { } } - /** - * Method send. - * @param msg String - - * @return String[] */ public String[] send(String msg) { if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.lang)) { singleton.reloadMessages(); @@ -71,10 +62,6 @@ public class Messages extends CustomConfiguration { return loc; } - /** - * Method getInstance. - - * @return Messages */ public static Messages getInstance() { if (singleton == null) { singleton = new Messages(Settings.messageFile, Settings.messagesLanguage); diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml index 16218f32..dd191d24 100644 --- a/src/main/resources/messages/messages_en.yml +++ b/src/main/resources/messages/messages_en.yml @@ -52,7 +52,7 @@ email_added: '&2Email address successfully added to your account!' email_confirm: '&cPlease confirm your email address!' email_changed: '&2Email address changed correctly!' email_send: '&2Recovery email sent correctly! Check your email inbox!' -email_exists: '&cA recovery email was already sent! You can discart it and send a new one using the command below:' +email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' country_banned: '&4Your country is banned from this server!' antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' antibot_auto_disabled: '&2[AntiBotService] AntiBot disabled disabled after %m minutes!' From da662afcf46794b69aeb0c6a66ef7b95ed95cd16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Sun, 22 Nov 2015 21:01:31 +0100 Subject: [PATCH 088/199] Created method to get an exception stack trace as a string --- .../fr/xephi/authme/util/StringUtils.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java index f0dd3d9c..c5d34c57 100644 --- a/src/main/java/fr/xephi/authme/util/StringUtils.java +++ b/src/main/java/fr/xephi/authme/util/StringUtils.java @@ -4,6 +4,9 @@ import net.ricecode.similarity.LevenshteinDistanceStrategy; import net.ricecode.similarity.StringSimilarityService; import net.ricecode.similarity.StringSimilarityServiceImpl; +import java.io.PrintWriter; +import java.io.StringWriter; + /** * Utility class for String operations. */ @@ -84,4 +87,22 @@ public class StringUtils { return sb.toString(); } + /** + * Get a full stack trace of an exception as a string. + * + * @param exception The exception. + * + * @return Stack trace as a string. + */ + public static String getStackTrace(Exception exception) { + // Create a string and print writer to print the stack trace into + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + // Print the stack trace into the print writer + exception.printStackTrace(printWriter); + + // Return the result as a string + return stringWriter.toString(); + } } From 69a09aec17218aa45de29714cb562faa47282bb3 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sun, 22 Nov 2015 17:20:23 +0100 Subject: [PATCH 089/199] cleanup --- src/main/java/fr/xephi/authme/AuthMe.java | 1 - src/main/java/fr/xephi/authme/DataManager.java | 1 - src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java | 1 - .../java/fr/xephi/authme/permission/PermissionsManager.java | 2 +- src/main/java/fr/xephi/authme/util/Utils.java | 4 ---- 5 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index fda2bde0..f78c1ebd 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -55,7 +55,6 @@ import fr.xephi.authme.settings.OtherAccounts; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Spawn; import fr.xephi.authme.util.Utils; -import net.milkbowl.vault.permission.Permission; import net.minelink.ctplus.CombatTagPlus; /** diff --git a/src/main/java/fr/xephi/authme/DataManager.java b/src/main/java/fr/xephi/authme/DataManager.java index 3d74da41..d4688e5c 100644 --- a/src/main/java/fr/xephi/authme/DataManager.java +++ b/src/main/java/fr/xephi/authme/DataManager.java @@ -14,7 +14,6 @@ import org.bukkit.entity.Player; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; -import net.milkbowl.vault.permission.Permission; /** */ diff --git a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java index c468a90c..59dee468 100644 --- a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java @@ -14,7 +14,6 @@ import fr.xephi.authme.cache.backup.DataFileCache; import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.events.ResetInventoryEvent; import fr.xephi.authme.settings.Settings; -import org.bukkit.permissions.Permission; /** */ diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 9ba1dcf1..a2af1ba4 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -500,7 +500,7 @@ public class PermissionsManager { * * @return The name of the primary permission group. Or null. */ - @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) + @SuppressWarnings("deprecation") public String getPrimaryGroup(Player player) { // If no permissions system is used, return an empty list if(!isEnabled()) diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 7e220e3a..6623c8dc 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -219,10 +219,6 @@ public final class Utils { && (Settings.getUnrestrictedName.contains(player.getName())); } - private static boolean useGroupSystem() { - return Settings.isPermissionCheckEnabled && !Settings.getUnloggedinGroup.isEmpty(); - } - /** * Method packCoords. * @param x double From 9ec2d6d059e7e9fe07bcf8d8ace65d13edc9baff Mon Sep 17 00:00:00 2001 From: AuthMe-Team Date: Mon, 23 Nov 2015 20:20:42 +0100 Subject: [PATCH 090/199] Stuff from the common floobits workspace Author: AuthMe-Team --- .floo | 3 + .flooignore | 123 +++ .gitignore | 2 - pom.xml | 2 +- src/main/java/fr/xephi/authme/AuthMe.java | 802 +++++++++--------- .../java/fr/xephi/authme/ConsoleFilter.java | 7 +- .../java/fr/xephi/authme/ConsoleLogger.java | 70 +- .../java/fr/xephi/authme/DataManager.java | 46 +- .../java/fr/xephi/authme/ImageGenerator.java | 12 +- .../java/fr/xephi/authme/Log4JFilter.java | 118 +-- .../java/fr/xephi/authme/PerformBackup.java | 102 ++- .../java/fr/xephi/authme/SendMailSSL.java | 17 +- src/main/java/fr/xephi/authme/api/API.java | 121 ++- src/main/java/fr/xephi/authme/api/NewAPI.java | 88 +- .../xephi/authme/cache/auth/PlayerAuth.java | 280 +++--- .../xephi/authme/cache/auth/PlayerCache.java | 46 +- .../authme/cache/backup/DataFileCache.java | 20 +- .../xephi/authme/cache/backup/JsonCache.java | 140 ++- .../xephi/authme/cache/limbo/LimboCache.java | 60 +- .../xephi/authme/cache/limbo/LimboPlayer.java | 74 +- .../command/CommandArgumentDescription.java | 18 +- .../authme/command/CommandDescription.java | 594 +++++++------ .../xephi/authme/command/CommandHandler.java | 77 +- .../xephi/authme/command/CommandManager.java | 52 +- .../fr/xephi/authme/command/CommandParts.java | 67 +- .../authme/command/CommandPermissions.java | 98 +-- .../authme/command/ExecutableCommand.java | 3 +- .../authme/command/FoundCommandResult.java | 71 +- .../command/executable/HelpCommand.java | 3 +- .../executable/authme/AccountsCommand.java | 11 +- .../executable/authme/AuthMeCommand.java | 7 +- .../authme/ChangePasswordCommand.java | 9 +- .../executable/authme/FirstSpawnCommand.java | 5 +- .../executable/authme/ForceLoginCommand.java | 9 +- .../executable/authme/GetEmailCommand.java | 10 +- .../executable/authme/GetIpCommand.java | 9 +- .../executable/authme/LastLoginCommand.java | 9 +- .../authme/PurgeBannedPlayersCommand.java | 16 +- .../executable/authme/PurgeCommand.java | 28 +- .../authme/PurgeLastPositionCommand.java | 12 +- .../executable/authme/RegisterCommand.java | 14 +- .../executable/authme/ReloadCommand.java | 7 +- .../executable/authme/ResetNameCommand.java | 16 +- .../executable/authme/SetEmailCommand.java | 8 +- .../authme/SetFirstSpawnCommand.java | 10 +- .../executable/authme/SetSpawnCommand.java | 10 +- .../executable/authme/SpawnCommand.java | 10 +- .../authme/SwitchAntiBotCommand.java | 16 +- .../executable/authme/UnregisterCommand.java | 19 +- .../executable/authme/VersionCommand.java | 31 +- .../executable/captcha/CaptchaCommand.java | 17 +- .../changepassword/ChangePasswordCommand.java | 7 +- .../converter/ConverterCommand.java | 97 +-- .../executable/email/AddEmailCommand.java | 5 +- .../executable/email/ChangeEmailCommand.java | 5 +- .../executable/email/RecoverEmailCommand.java | 11 +- .../executable/login/LoginCommand.java | 5 +- .../executable/logout/LogoutCommand.java | 5 +- .../executable/register/RegisterCommand.java | 7 +- .../unregister/UnregisterCommand.java | 14 +- .../authme/command/help/HelpPrinter.java | 93 +- .../authme/command/help/HelpProvider.java | 55 +- .../authme/command/help/HelpSyntaxHelper.java | 22 +- .../authme/converter/CrazyLoginConverter.java | 22 +- .../fr/xephi/authme/converter/FlatToSql.java | 10 +- .../xephi/authme/converter/FlatToSqlite.java | 36 +- .../authme/converter/ForceFlatToSqlite.java | 5 +- .../authme/converter/RakamakConverter.java | 28 +- .../authme/converter/RoyalAuthConverter.java | 9 +- .../authme/converter/RoyalAuthYamlReader.java | 15 +- .../fr/xephi/authme/converter/SqlToFlat.java | 9 +- .../authme/converter/vAuthConverter.java | 5 +- .../authme/converter/vAuthFileReader.java | 33 +- .../authme/converter/xAuthConverter.java | 5 +- .../xephi/authme/converter/xAuthToFlat.java | 40 +- .../authme/datasource/CacheDataSource.java | 161 ++-- .../xephi/authme/datasource/DataSource.java | 364 ++++---- .../authme/datasource/DatabaseCalls.java | 150 ++-- .../fr/xephi/authme/datasource/FlatFile.java | 157 ++-- .../fr/xephi/authme/datasource/MySQL.java | 189 +++-- .../fr/xephi/authme/datasource/SQLite.java | 175 ++-- .../events/AuthMeAsyncPreLoginEvent.java | 20 +- .../authme/events/AuthMeTeleportEvent.java | 32 +- .../fr/xephi/authme/events/CustomEvent.java | 37 +- .../events/FirstSpawnTeleportEvent.java | 34 +- .../fr/xephi/authme/events/LoginEvent.java | 52 +- .../fr/xephi/authme/events/LogoutEvent.java | 32 +- .../events/PasswordEncryptionEvent.java | 54 +- .../authme/events/ProtectInventoryEvent.java | 29 +- .../authme/events/RegisterTeleportEvent.java | 32 +- .../authme/events/ResetInventoryEvent.java | 8 +- .../authme/events/RestoreInventoryEvent.java | 10 +- .../authme/events/SpawnTeleportEvent.java | 43 +- .../xephi/authme/hooks/BungeeCordMessage.java | 14 +- .../java/fr/xephi/authme/hooks/EssSpawn.java | 15 +- .../authme/listener/AuthMeBlockListener.java | 8 +- .../authme/listener/AuthMeEntityListener.java | 29 +- .../AuthMeInventoryPacketAdapter.java | 25 +- .../authme/listener/AuthMePlayerListener.java | 99 ++- .../listener/AuthMePlayerListener16.java | 7 +- .../listener/AuthMePlayerListener18.java | 7 +- .../authme/listener/AuthMeServerListener.java | 23 +- .../java/fr/xephi/authme/modules/Module.java | 38 +- .../xephi/authme/modules/ModuleManager.java | 37 +- .../authme/permission/PermissionsManager.java | 159 ++-- .../fr/xephi/authme/process/Management.java | 35 +- .../process/email/AsyncChangeEmail.java | 21 +- .../authme/process/join/AsyncronousJoin.java | 36 +- .../process/login/AsyncronousLogin.java | 42 +- .../login/ProcessSyncronousPlayerLogin.java | 33 +- .../process/logout/AsyncronousLogout.java | 12 +- .../logout/ProcessSyncronousPlayerLogout.java | 15 +- .../authme/process/quit/AsyncronousQuit.java | 18 +- .../quit/ProcessSyncronousPlayerQuit.java | 15 +- .../process/register/AsyncRegister.java | 34 +- .../register/ProcessSyncEmailRegister.java | 11 +- .../ProcessSyncronousPasswordRegister.java | 16 +- .../unregister/AsyncronousUnregister.java | 35 +- .../xephi/authme/security/HashAlgorithm.java | 6 +- .../authme/security/PasswordSecurity.java | 53 +- .../xephi/authme/security/RandomString.java | 7 +- .../xephi/authme/security/crypts/BCRYPT.java | 378 ++++----- .../authme/security/crypts/BCRYPT2Y.java | 24 +- .../authme/security/crypts/CRAZYCRYPT1.java | 53 +- .../authme/security/crypts/CryptPBKDF2.java | 28 +- .../security/crypts/CryptPBKDF2Django.java | 29 +- .../authme/security/crypts/DOUBLEMD5.java | 64 +- .../security/crypts/EncryptionMethod.java | 12 +- .../fr/xephi/authme/security/crypts/IPB3.java | 70 +- .../xephi/authme/security/crypts/JOOMLA.java | 66 +- .../fr/xephi/authme/security/crypts/MD5.java | 64 +- .../xephi/authme/security/crypts/MD5VB.java | 66 +- .../fr/xephi/authme/security/crypts/MYBB.java | 70 +- .../xephi/authme/security/crypts/PHPBB.java | 192 +++-- .../authme/security/crypts/PHPFUSION.java | 63 +- .../authme/security/crypts/PLAINTEXT.java | 24 +- .../authme/security/crypts/ROYALAUTH.java | 32 +- .../authme/security/crypts/SALTED2MD5.java | 70 +- .../authme/security/crypts/SALTEDSHA512.java | 70 +- .../fr/xephi/authme/security/crypts/SHA1.java | 64 +- .../xephi/authme/security/crypts/SHA256.java | 66 +- .../xephi/authme/security/crypts/SHA512.java | 64 +- .../fr/xephi/authme/security/crypts/SMF.java | 64 +- .../fr/xephi/authme/security/crypts/WBB3.java | 70 +- .../fr/xephi/authme/security/crypts/WBB4.java | 24 +- .../authme/security/crypts/WHIRLPOOL.java | 154 ++-- .../authme/security/crypts/WORDPRESS.java | 48 +- .../xephi/authme/security/crypts/XAUTH.java | 53 +- .../fr/xephi/authme/security/crypts/XF.java | 41 +- .../authme/security/pbkdf2/BinTools.java | 34 +- .../authme/security/pbkdf2/MacBasedPRF.java | 30 +- .../xephi/authme/security/pbkdf2/PBKDF2.java | 51 +- .../authme/security/pbkdf2/PBKDF2Engine.java | 488 ++++++----- .../security/pbkdf2/PBKDF2Formatter.java | 20 +- .../security/pbkdf2/PBKDF2HexFormatter.java | 14 +- .../security/pbkdf2/PBKDF2Parameters.java | 70 +- .../fr/xephi/authme/security/pbkdf2/PRF.java | 26 +- .../authme/settings/CustomConfiguration.java | 12 +- .../fr/xephi/authme/settings/Messages.java | 20 +- .../xephi/authme/settings/OtherAccounts.java | 38 +- .../fr/xephi/authme/settings/Settings.java | 403 +++++---- .../java/fr/xephi/authme/settings/Spawn.java | 53 +- .../xephi/authme/task/ChangePasswordTask.java | 13 +- .../fr/xephi/authme/task/MessageTask.java | 13 +- .../fr/xephi/authme/task/TimeoutTask.java | 14 +- .../java/fr/xephi/authme/util/GeoLiteAPI.java | 96 +++ .../java/fr/xephi/authme/util/Profiler.java | 50 +- .../fr/xephi/authme/util/StringUtils.java | 33 +- src/main/java/fr/xephi/authme/util/Utils.java | 95 +-- 169 files changed, 5161 insertions(+), 4806 deletions(-) create mode 100644 .floo create mode 100644 .flooignore create mode 100644 src/main/java/fr/xephi/authme/util/GeoLiteAPI.java diff --git a/.floo b/.floo new file mode 100644 index 00000000..b0f2c28e --- /dev/null +++ b/.floo @@ -0,0 +1,3 @@ +{ + "url": "https://floobits.com/AuthMe-Team/AuthMeReloaded" +} \ No newline at end of file diff --git a/.flooignore b/.flooignore new file mode 100644 index 00000000..e9c355d0 --- /dev/null +++ b/.flooignore @@ -0,0 +1,123 @@ +### Java ### +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +#*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + + +### Eclipse ### +*.pydevproject +.metadata +.gradle +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath + +# Eclipse Core +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties + + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +nbactions.xml +nb-configuration.xml +.nb-gradle/ diff --git a/.gitignore b/.gitignore index a0348ece..e9c355d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -# Created by https://www.gitignore.io - ### Java ### *.class diff --git a/pom.xml b/pom.xml index 1a9a6439..9c19232f 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ UTF-8 fr.xephi.authme.AuthMe - CustomBuild + 100 [Xephi, sgdc3, DNx5, timvisee, games647, ljacqu] diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index f78c1ebd..7630b99e 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -1,18 +1,31 @@ package fr.xephi.authme; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.URL; -import java.net.URLConnection; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; - +import com.earth2me.essentials.Essentials; +import com.onarandombox.MultiverseCore.MultiverseCore; +import fr.xephi.authme.api.API; +import fr.xephi.authme.api.NewAPI; +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.cache.backup.JsonCache; +import fr.xephi.authme.cache.limbo.LimboCache; +import fr.xephi.authme.cache.limbo.LimboPlayer; +import fr.xephi.authme.command.CommandHandler; +import fr.xephi.authme.converter.Converter; +import fr.xephi.authme.converter.ForceFlatToSqlite; +import fr.xephi.authme.datasource.*; +import fr.xephi.authme.hooks.BungeeCordMessage; +import fr.xephi.authme.hooks.EssSpawn; import fr.xephi.authme.listener.*; +import fr.xephi.authme.modules.ModuleManager; import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.process.Management; +import fr.xephi.authme.settings.Messages; +import fr.xephi.authme.settings.OtherAccounts; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.Spawn; +import fr.xephi.authme.util.GeoLiteAPI; +import fr.xephi.authme.util.Utils; +import net.minelink.ctplus.CombatTagPlus; import org.apache.logging.log4j.LogManager; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -27,99 +40,148 @@ import org.bukkit.scheduler.BukkitTask; import org.mcstats.Metrics; import org.mcstats.Metrics.Graph; -import com.earth2me.essentials.Essentials; -import com.onarandombox.MultiverseCore.MultiverseCore; - -import fr.xephi.authme.api.API; -import fr.xephi.authme.api.NewAPI; -import fr.xephi.authme.cache.auth.PlayerAuth; -import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.cache.backup.JsonCache; -import fr.xephi.authme.cache.limbo.LimboCache; -import fr.xephi.authme.cache.limbo.LimboPlayer; -import fr.xephi.authme.command.CommandHandler; -import fr.xephi.authme.converter.Converter; -import fr.xephi.authme.converter.ForceFlatToSqlite; -import fr.xephi.authme.datasource.CacheDataSource; -import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.datasource.DatabaseCalls; -import fr.xephi.authme.datasource.FlatFile; -import fr.xephi.authme.datasource.MySQL; -import fr.xephi.authme.datasource.SQLite; -import fr.xephi.authme.hooks.BungeeCordMessage; -import fr.xephi.authme.hooks.EssSpawn; -import fr.xephi.authme.modules.ModuleManager; -import fr.xephi.authme.process.Management; -import fr.xephi.authme.settings.Messages; -import fr.xephi.authme.settings.OtherAccounts; -import fr.xephi.authme.settings.Settings; -import fr.xephi.authme.settings.Spawn; -import fr.xephi.authme.util.Utils; -import net.minelink.ctplus.CombatTagPlus; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; /** * The AuthMe main class. */ public class AuthMe extends JavaPlugin { - /** Defines the name of the plugin. */ - // TODO: Create a getter method for this constant, and make it private - public static final String PLUGIN_NAME = "AuthMeReloaded"; - /** Defines the current AuthMeReloaded version name. */ + /** + * Defines the name of the plugin. + */ + private static final String PLUGIN_NAME = "AuthMeReloaded"; + /** + * Defines the current AuthMeReloaded version name. + */ private static final String PLUGIN_VERSION_NAME = "5.1-SNAPSHOT"; - /** Defines the current AuthMeReloaded version code. */ - // Increase this number by one when an update is released + /** + * Defines the current AuthMeReloaded version code. + */ + // TODO: Increase this number by one when an update is release + // TODO: Increase the count via maven private static final int PLUGIN_VERSION_CODE = 100; private static AuthMe plugin; private static Server server; - private Logger authmeLogger; - - // TODO: Move this to a better place! -- timvisee - private CommandHandler commandHandler = null; - private PermissionsManager permsMan = null; public Management management; public NewAPI api; public SendMailSSL mail; - private Settings settings; - private Messages m; public DataManager dataManager; public DataSource database; - private JsonCache playerBackup; public OtherAccounts otherAccounts; public Location essentialsSpawn; - public boolean antibotMod = false; - public boolean delayedAntiBot = true; - // Hooks TODO: move into modules public Essentials ess; public MultiverseCore multiverse; public CombatTagPlus combatTagPlus; public AuthMeInventoryPacketAdapter inventoryProtector; - - // Module manager - private ModuleManager moduleManager; - - // TODO: Create Manager for fields below + // Random data maps and stuff + // TODO: Create Manager for this public ConcurrentHashMap sessions = new ConcurrentHashMap<>(); public ConcurrentHashMap captcha = new ConcurrentHashMap<>(); public ConcurrentHashMap cap = new ConcurrentHashMap<>(); public ConcurrentHashMap realIp = new ConcurrentHashMap<>(); - - // In case we need to cache PlayerAuths, prevent connection before it's done + // AntiBot Status + // TODO: Create Manager for this + public boolean antiBotMod = false; + public boolean delayedAntiBot = true; + private CommandHandler commandHandler = null; + private PermissionsManager permsMan = null; + private Settings settings; + private Messages messages; + private JsonCache playerBackup; + private ModuleManager moduleManager; + // If cache is enabled, prevent any connection before the players data caching is completed. + // TODO: Move somewhere private boolean canConnect = true; /** - * Method canConnect. - - * @return boolean */ + * Returns the plugin's instance. + * + * @return AuthMe + */ + public static AuthMe getInstance() { + return plugin; + } + + /** + * Get the plugin's name. + * + * @return Plugin name. + */ + public static String getPluginName() { + return PLUGIN_NAME; + } + + /** + * Get the current installed AuthMeReloaded version name. + * + * @return The version name of the currently installed AuthMeReloaded instance. + */ + public static String getVersionName() { + return PLUGIN_VERSION_NAME; + } + + /** + * Get the current installed AuthMeReloaded version code. + * + * @return The version code of the currently installed AuthMeReloaded instance. + */ + public static int getVersionCode() { + return PLUGIN_VERSION_CODE; + } + + /** + * Returns the plugin's Settings. + * + * @return Settings + */ + public Settings getSettings() { + return settings; + } + + /** + * Returns the Messages instance. + * + * @return Messages + */ + + public Messages getMessages() { + return messages; + } + + /** + * Set the Messages instance. + * + * @param m Messages + */ + public void setMessages(Messages m) { + this.messages = m; + } + + /** + * Returns if players are allowed to join the server. + * + * @return boolean + */ public boolean canConnect() { return canConnect; } /** - * Method setCanConnect. + * Define if players are allowed to join the server. + * * @param canConnect boolean */ public void setCanConnect(boolean canConnect) { @@ -127,69 +189,246 @@ public class AuthMe extends JavaPlugin { } /** - * Method getInstance. - - * @return AuthMe */ - public static AuthMe getInstance() { - return plugin; - } - - /** - * Method getSettings. - - * @return Settings */ - public Settings getSettings() { - return settings; - } - - /** - * Method setMessages. - * @param m Messages - */ - public void setMessages(Messages m) { - this.m = m; - } - - /** - * Method getMessages. - - * @return Messages */ - public Messages getMessages() { - return m; - } - - /** - * Method onEnable. + * Method called when the server enables the plugin. + * * @see org.bukkit.plugin.Plugin#onEnable() */ @Override public void onEnable() { - // Set the Instance + // Set various instances server = getServer(); - authmeLogger = Logger.getLogger("AuthMe"); plugin = this; // Set up the permissions manager setupPermissionsManager(); // Set up and initialize the command handler + setupCommandHandler(); + + // Set up the module manager + setupModuleManager(); + + // Load settings and custom configurations, if it fails, stop the server due to security reasons. + if (loadSettings()) { + server.shutdown(); + setEnabled(false); + return; + } + + // Setup otherAccounts file + this.otherAccounts = OtherAccounts.getInstance(); + + // Setup messages + this.messages = Messages.getInstance(); + + // Set up Metrics + setupMetrics(); + + // Set console filter + setupConsoleFilter(); + + // AntiBot delay + setupAntiBotDelay(); + + // Download and load GeoIp.dat file if absent + GeoLiteAPI.isDataAvailable(); + + // Set up the mail API + setupMailApi(); + + // Hooks + // Check Combat Tag Plus Version + checkCombatTagPlus(); + + // Check Multiverse + checkMultiverse(); + + // Check Essentials + checkEssentials(); + + // Check if the protocollib is available. If so we could listen for + // inventory protection + checkProtocolLib(); + // End of Hooks + + // Do a backup on start + new PerformBackup(plugin).doBackup(PerformBackup.BackupCause.START); + + // Connect to the database and setup tables + try { + setupDatabase(); + } catch (Exception e) { + ConsoleLogger.writeStackTrace(e); + ConsoleLogger.showError(e.getMessage()); + ConsoleLogger.showError("Fatal error occurred during database connection! Authme initialization ABORTED!"); + stopOrUnload(); + return; + } + + // Setup the inventory backup + playerBackup = new JsonCache(); + + // Set the DataManager + dataManager = new DataManager(this); + + // Set up the new API + setupApi(); + + // Set up the management + management = new Management(this); + + // Set up the Bungeecord hook + setupBungeecordHook(); + + // Reload support hook + reloadSupportHook(); + + // Register event listeners + registerEventListeners(); + + // Purge on start if enabled + autoPurge(); + + // Start Email recall task if needed + recallEmail(); + + // Show settings warnings + showSettingsWarnings(); + + // Sponsor messages + ConsoleLogger.info("AuthMe hooks perfectly with the VeryGames server hosting!"); + ConsoleLogger.info("Development builds are available on our jenkins, thanks to f14stelt."); + ConsoleLogger.info("Do you want a good game server? Look at our sponsor GameHosting.it leader in Italy as Game Server Provider!"); + + // Successful message + ConsoleLogger.info("AuthMe " + this.getDescription().getVersion() + " correctly enabled!"); + } + + /** + * Set up the module manager. + */ + private void setupModuleManager() { + // TODO: Clean this up! + // TODO: split the plugin in more modules + // TODO: log number of loaded modules + + // Define the module manager instance + moduleManager = new ModuleManager(this); + + // Load the modules + // int loaded = moduleManager.loadModules(); + } + + /** + * Set up the mail API, if enabled. + */ + private void setupMailApi() { + // Make sure the mail API is enabled + if (Settings.getmailAccount.isEmpty() || Settings.getmailPassword.isEmpty()) { + return; + } + + // Set up the mail API + this.mail = new SendMailSSL(this); + } + + /** + * Show the settings warnings, for various risky settings. + */ + private void showSettingsWarnings() { + // Force single session disabled + if (!Settings.isForceSingleSessionEnabled) { + ConsoleLogger.showError("WARNING!!! By disabling ForceSingleSession, your server protection is inadequate!"); + } + + // Session timeout disabled + if (Settings.getSessionTimeout == 0 && Settings.isSessionsEnabled) { + ConsoleLogger.showError("WARNING!!! You set session timeout to 0, this may cause security issues!"); + } + } + + /** + * Register all event listeners. + */ + private void registerEventListeners() { + // Get the plugin manager instance + PluginManager pluginManager = server.getPluginManager(); + + // Register event listeners + pluginManager.registerEvents(new AuthMePlayerListener(this), this); + pluginManager.registerEvents(new AuthMeBlockListener(this), this); + pluginManager.registerEvents(new AuthMeEntityListener(this), this); + pluginManager.registerEvents(new AuthMeServerListener(this), this); + + // Try to register 1.6 player listeners + try { + Class.forName("org.bukkit.event.player.PlayerEditBookEvent"); + pluginManager.registerEvents(new AuthMePlayerListener16(this), this); + } catch (ClassNotFoundException ignore) { + } + + // Try to register 1.8 player listeners + try { + Class.forName("org.bukkit.event.player.PlayerInteractAtEntityEvent"); + pluginManager.registerEvents(new AuthMePlayerListener18(this), this); + } catch (ClassNotFoundException ignore) { + } + } + + private void reloadSupportHook() { + if (database != null) { + int playersOnline = Utils.getOnlinePlayers().size(); + if (playersOnline < 1) { + database.purgeLogged(); + } else if (Settings.reloadSupport) { + for (PlayerAuth auth : database.getLoggedPlayers()) { + if (auth == null) { + continue; + } + auth.setLastLogin(new Date().getTime()); + database.updateSession(auth); + PlayerCache.getInstance().addPlayer(auth); + } + } + } + } + + /** + * Set up the Bungecoord hook. + */ + private void setupBungeecordHook() { + if (Settings.bungee) { + Bukkit.getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); + Bukkit.getMessenger().registerIncomingPluginChannel(this, "BungeeCord", new BungeeCordMessage(this)); + } + } + + /** + * Set up the API. This sets up the new and the old API. + */ + @SuppressWarnings("deprecation") + private void setupApi() { + // Set up the API + api = new NewAPI(this); + + // Setup the old deprecated API + new API(this); + } + + /** + * Set up the command handler. + */ + private void setupCommandHandler() { this.commandHandler = new CommandHandler(false); this.commandHandler.init(); + } - // TODO: split the plugin in more modules - moduleManager = new ModuleManager(this); - @SuppressWarnings("unused") - int loaded = moduleManager.loadModules(); - - // TODO: remove vault as hard dependency - PluginManager pm = server.getPluginManager(); - - // Setup the Logger - if (authmeLogger == null) - authmeLogger = this.getLogger(); - else authmeLogger.setParent(this.getLogger()); - - // Load settings and custom configurations + /** + * Load the plugin's settings. + * + * @return True on success, false on failure. + */ + private boolean loadSettings() { // TODO: new configuration style (more files) try { settings = new Settings(this); @@ -198,16 +437,48 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.writeStackTrace(e); ConsoleLogger.showError("Can't load the configuration file... Something went wrong, to avoid security issues the server will shutdown!"); server.shutdown(); - return; + return true; } + return false; + } - // Setup otherAccounts file - otherAccounts = OtherAccounts.getInstance(); + /** + * Set up the antibot delay. + */ + private void setupAntiBotDelay() { + if (Settings.enableAntiBot) { + Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() { + @Override + public void run() { + delayedAntiBot = false; + } + }, 2400); + } + } - // Setup messages - m = Messages.getInstance(); + /** + * Set up the console filter. + */ + private void setupConsoleFilter() { + if (Settings.removePassword) { + ConsoleFilter filter = new ConsoleFilter(); + ConsoleLogger.getLogger().setFilter(filter); + Bukkit.getLogger().setFilter(filter); + Logger.getLogger("Minecraft").setFilter(filter); + // Set Log4J Filter + try { + Class.forName("org.apache.logging.log4j.core.Filter"); + setLog4JFilter(); + } catch (ClassNotFoundException | NoClassDefFoundError e) { + ConsoleLogger.info("You're using Minecraft 1.6.x or older, Log4J support will be disabled"); + } + } + } - // Start the metrics service + /** + * Set up Metrics. + */ + private void setupMetrics() { try { Metrics metrics = new Metrics(this); Graph messagesLanguage = metrics.createGraph("Messages language"); @@ -238,155 +509,14 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.writeStackTrace(e); ConsoleLogger.showError("Can't start Metrics! The plugin will work anyway..."); } - - // Set Console Filter - if (Settings.removePassword) { - ConsoleFilter filter = new ConsoleFilter(); - this.getLogger().setFilter(filter); - Bukkit.getLogger().setFilter(filter); - Logger.getLogger("Minecraft").setFilter(filter); - authmeLogger.setFilter(filter); - // Set Log4J Filter - try { - Class.forName("org.apache.logging.log4j.core.Filter"); - setLog4JFilter(); - } catch (ClassNotFoundException | NoClassDefFoundError e) { - ConsoleLogger.info("You're using Minecraft 1.6.x or older, Log4J support will be disabled"); - } - } - - // AntiBot delay - if (Settings.enableAntiBot) { - Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() { - - @Override - public void run() { - delayedAntiBot = false; - } - }, 2400); - } - - // Download GeoIp.dat file - Utils.checkGeoIP(); - - // Load MailApi if needed - if (!Settings.getmailAccount.isEmpty() && !Settings.getmailPassword.isEmpty()) { - mail = new SendMailSSL(this); - } - - // Check Combat Tag Plus Version - checkCombatTagPlus(); - - // Check Multiverse - checkMultiverse(); - - // Check Essentials - checkEssentials(); - - // Check if the protocollib is available. If so we could listen for - // inventory protection - checkProtocolLib(); - - // Do backup on start if enabled - if (Settings.isBackupActivated && Settings.isBackupOnStart) { - // Do backup and check return value! - if (new PerformBackup(this).doBackup()) { - ConsoleLogger.info("Backup performed correctly"); - } else { - ConsoleLogger.showError("Error while performing the backup!"); - } - } - - // Connect to the database and setup tables - try { - setupDatabase(); - } catch (Exception e) { - ConsoleLogger.writeStackTrace(e); - ConsoleLogger.showError(e.getMessage()); - ConsoleLogger.showError("Fatal error occurred during database connection! Authme initialization ABORTED!"); - stopOrUnload(); - return; - } - - // Setup the inventory backup - playerBackup = new JsonCache(); - - // Set the DataManager - dataManager = new DataManager(this); - - // Setup the new API - api = new NewAPI(this); - // Setup the old deprecated API - new API(this); - - // Setup Management - management = new Management(this); - - // Bungeecord hook - if (Settings.bungee) { - Bukkit.getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); - Bukkit.getMessenger().registerIncomingPluginChannel(this, "BungeeCord", new BungeeCordMessage(this)); - } - - // Reload support hook - if (database != null) { - int playersOnline = Utils.getOnlinePlayers().size(); - if (playersOnline < 1) { - database.purgeLogged(); - } else if (Settings.reloadSupport) { - for (PlayerAuth auth : database.getLoggedPlayers()) { - if (auth == null) - continue; - auth.setLastLogin(new Date().getTime()); - database.updateSession(auth); - PlayerCache.getInstance().addPlayer(auth); - } - } - } - - // Register events - pm.registerEvents(new AuthMePlayerListener(this), this); - // Try to register 1.6 player listeners - try { - Class.forName("org.bukkit.event.player.PlayerEditBookEvent"); - pm.registerEvents(new AuthMePlayerListener16(this), this); - } catch (ClassNotFoundException ignore) { - } - // Try to register 1.8 player listeners - try { - Class.forName("org.bukkit.event.player.PlayerInteractAtEntityEvent"); - pm.registerEvents(new AuthMePlayerListener18(this), this); - } catch (ClassNotFoundException ignore) { - } - pm.registerEvents(new AuthMeBlockListener(this), this); - pm.registerEvents(new AuthMeEntityListener(this), this); - pm.registerEvents(new AuthMeServerListener(this), this); - - // Purge on start if enabled - autoPurge(); - - // Start Email recall task if needed - recallEmail(); - - // Configuration Security Warnings - if (!Settings.isForceSingleSessionEnabled) { - ConsoleLogger.showError("WARNING!!! By disabling ForceSingleSession, your server protection is inadequate!"); - } - if (Settings.getSessionTimeout == 0 && Settings.isSessionsEnabled) { - ConsoleLogger.showError("WARNING!!! You set session timeout to 0, this may cause security issues!"); - } - - // Sponsor messages - ConsoleLogger.info("AuthMe hooks perfectly with the VERYGAMES server hosting!"); - ConsoleLogger.info("Development builds are available on our jenkins, thanks to f14stelt."); - ConsoleLogger.info("Do you want a good gameserver? Look at our sponsor GameHosting.it leader in Italy as Game Server Provider!"); - - // Successful message - ConsoleLogger.info("AuthMe " + this.getDescription().getVersion() + " correctly enabled!"); } + // Show the exception message and stop/unload the server/plugin as defined + // in the configuration + /** * Method onDisable. + * * @see org.bukkit.plugin.Plugin#onDisable() */ @Override @@ -400,12 +530,7 @@ public class AuthMe extends JavaPlugin { } // Do backup on stop if enabled - if (Settings.isBackupActivated && Settings.isBackupOnStop) { - boolean Backup = new PerformBackup(this).doBackup(); - if (Backup) - ConsoleLogger.info("Backup performed correctly."); - else ConsoleLogger.showError("Error while performing the backup!"); - } + new PerformBackup(plugin).doBackup(PerformBackup.BackupCause.STOP); // Unload modules moduleManager.unloadModules(); @@ -419,6 +544,8 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.info("AuthMe " + this.getDescription().getVersion() + " disabled!"); } + // Initialize and setup the database + // Stop/unload the server/plugin as defined in the configuration public void stopOrUnload() { if (Settings.isStopEnabled) { @@ -429,22 +556,9 @@ public class AuthMe extends JavaPlugin { } } - // Show the exception message and stop/unload the server/plugin as defined - // in the configuration - /** - * Method stopOrUnload. - * @param e Exception - */ - public void stopOrUnload(Exception e) { - ConsoleLogger.showError(e.getMessage()); - stopOrUnload(); - } - - // Initialize and setup the database /** * Method setupDatabase. - - * @throws Exception */ + */ public void setupDatabase() throws Exception { if (database != null) database.close(); @@ -584,49 +698,6 @@ public class AuthMe extends JavaPlugin { } } - /** - * Check if a player/command sender have a permission - * - * @deprecated Deprecated since v5.1. Use the permissions manager instead! See: getPermissionsManager() - * - * @param player - * @param perm - - - * @return boolean */ - public boolean authmePermissible(Player player, String perm) { - // New code: - return getPermissionsManager().hasPermission(player, perm); - - // Legacy code: - /*if (player.hasPermission(perm)) { - return true; - } else if (permission != null) { - return permission.playerHas(player, perm); - } - return false;*/ - } - - /** - * @deprecated Deprecated since v5.1. Use the permissions manager instead! See: getPermissionsManager() - * - * @param sender - * @param perm - - - * @return boolean */ - public boolean authmePermissible(CommandSender sender, String perm) { - // Handle players with the permissions manager - if(sender instanceof Player) { - // Get the player instance - Player player = (Player) sender; - - // Check whether the player has permission, return the result - return getPermissionsManager().hasPermission(player, perm); - } - return false; - } - // Save Player Data public void savePlayer(Player player) { if ((Utils.isNPC(player)) || (Utils.isUnrestricted(player))) { @@ -754,12 +825,12 @@ public class AuthMe extends JavaPlugin { } public void switchAntiBotMod(boolean mode) { - this.antibotMod = mode; + this.antiBotMod = mode; Settings.switchAntiBotMod(mode); } public boolean getAntiBotModMode() { - return this.antibotMod; + return this.antiBotMod; } private void recallEmail() { @@ -776,7 +847,7 @@ public class AuthMe extends JavaPlugin { if (PlayerCache.getInstance().isAuthenticated(name)) { String email = database.getAuth(name).getEmail(); if (email == null || email.isEmpty() || email.equalsIgnoreCase("your@email.com")) - m.send(player, "add_email"); + messages.send(player, "add_email"); } } } @@ -784,7 +855,6 @@ public class AuthMe extends JavaPlugin { }, 1, 1200 * Settings.delayRecall); } - public String replaceAllInfos(String message, Player player) { int playersOnline = Utils.getOnlinePlayers().size(); message = message.replace("&", "\u00a7"); @@ -796,11 +866,10 @@ public class AuthMe extends JavaPlugin { message = message.replace("{WORLD}", player.getWorld().getName()); message = message.replace("{SERVER}", server.getServerName()); message = message.replace("{VERSION}", server.getBukkitVersion()); - message = message.replace("{COUNTRY}", Utils.getCountryName(getIP(player))); + message = message.replace("{COUNTRY}", GeoLiteAPI.getCountryName(getIP(player))); return message; } - public String getIP(Player player) { String name = player.getName().toLowerCase(); String ip = player.getAddress().getAddress().getHostAddress(); @@ -814,7 +883,6 @@ public class AuthMe extends JavaPlugin { return ip; } - public boolean isLoggedIp(String name, String ip) { int count = 0; for (Player player : Utils.getOnlinePlayers()) { @@ -824,7 +892,6 @@ public class AuthMe extends JavaPlugin { return count >= Settings.getMaxLoginPerIp; } - public boolean hasJoinedIp(String name, String ip) { int count = 0; for (Player player : Utils.getOnlinePlayers()) { @@ -834,7 +901,6 @@ public class AuthMe extends JavaPlugin { return count >= Settings.getMaxJoinPerIp; } - public ModuleManager getModuleManager() { return moduleManager; } @@ -843,7 +909,6 @@ public class AuthMe extends JavaPlugin { * Gets a player's real IP through VeryGames method. * * @param player the player to process - * * @return the real IP of the player */ @Deprecated @@ -864,19 +929,6 @@ public class AuthMe extends JavaPlugin { return realIP; } - - @Deprecated - public String getCountryCode(String ip) { - return Utils.getCountryCode(ip); - } - - - @Deprecated - public String getCountryName(String ip) { - return Utils.getCountryName(ip); - } - - public CommandHandler getCommandHandler() { return this.commandHandler; } @@ -884,20 +936,15 @@ public class AuthMe extends JavaPlugin { /** * Handle Bukkit commands. * - * @param sender - * The command sender (Bukkit). - * @param cmd - * The command (Bukkit). - * @param commandLabel - * The command label (Bukkit). - * @param args - * The command arguments (Bukkit). - * - * @return True if the command was executed, false otherwise. + * @param sender The command sender (Bukkit). + * @param cmd The command (Bukkit). + * @param commandLabel The command label (Bukkit). + * @param args The command arguments (Bukkit). + * @return True if the command was executed, false otherwise. */ @Override public boolean onCommand(CommandSender sender, Command cmd, - String commandLabel, String[] args) { + String commandLabel, String[] args) { // Get the command handler, and make sure it's valid CommandHandler commandHandler = this.getCommandHandler(); if (commandHandler == null) @@ -908,37 +955,20 @@ public class AuthMe extends JavaPlugin { } /** - * Get the current installed AuthMeReloaded version name. - * - * @return The version name of the currently installed AuthMeReloaded - * instance. + * Returns the management instance. */ - public static String getVersionName() { - return PLUGIN_VERSION_NAME; - } - - /** - * Get the current installed AuthMeReloaded version code. - * - * @return The version code of the currently installed AuthMeReloaded - * instance. - */ - public static int getVersionCode() { - return PLUGIN_VERSION_CODE; - } - - /** Returns the management instance. */ public Management getManagement() { return management; } /** - * Returns the server instance running this plugin. Use this method in favor of - * {@link AuthMe#getServer()} for testability purposes. + * Returns the server instance running this plugin. Use this method in favor of {@link + * AuthMe#getServer()} for testability purposes. * * @return the server instance */ public Server getGameServer() { return super.getServer(); } + } diff --git a/src/main/java/fr/xephi/authme/ConsoleFilter.java b/src/main/java/fr/xephi/authme/ConsoleFilter.java index 668ff206..09da4983 100644 --- a/src/main/java/fr/xephi/authme/ConsoleFilter.java +++ b/src/main/java/fr/xephi/authme/ConsoleFilter.java @@ -4,6 +4,7 @@ import java.util.logging.Filter; import java.util.logging.LogRecord; /** + * Console filter Class * * @author Xephi59 * @version $Revision: 1.0 $ @@ -15,10 +16,10 @@ public class ConsoleFilter implements Filter { /** * Method isLoggable. + * * @param record LogRecord - - - * @return boolean * @see java.util.logging.Filter#isLoggable(LogRecord) */ + * @return boolean * @see java.util.logging.Filter#isLoggable(LogRecord) + */ @Override public boolean isLoggable(LogRecord record) { try { diff --git a/src/main/java/fr/xephi/authme/ConsoleLogger.java b/src/main/java/fr/xephi/authme/ConsoleLogger.java index 1e58e913..0bbacbfc 100644 --- a/src/main/java/fr/xephi/authme/ConsoleLogger.java +++ b/src/main/java/fr/xephi/authme/ConsoleLogger.java @@ -1,5 +1,9 @@ package fr.xephi.authme; +import com.google.common.base.Throwables; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.StringUtils; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardOpenOption; @@ -8,12 +12,8 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.Logger; -import com.google.common.base.Throwables; - -import fr.xephi.authme.api.NewAPI; -import fr.xephi.authme.settings.Settings; - /** + * The plugin's static logger. */ public class ConsoleLogger { @@ -21,42 +21,52 @@ public class ConsoleLogger { private static final DateFormat df = new SimpleDateFormat("[MM-dd HH:mm:ss]"); /** - * Method info. + * Returns the plugin's logger. + * + * @return Logger + */ + public static Logger getLogger() { + return log; + } + + /** + * Print an info message. + * * @param message String */ public static void info(String message) { - log.info("[AuthMe] " + message); - if (Settings.useLogging) { - String dateTime; - synchronized (df) { - dateTime = df.format(new Date()); - } - writeLog(dateTime + " " + message); + log.info(message); + if (!Settings.useLogging) { + return; } + writeLog("" + message); } /** - * Method showError. + * Print an error message. + * * @param message String */ public static void showError(String message) { - log.warning("[AuthMe] " + message); - if (Settings.useLogging) { - String dateTime; - synchronized (df) { - dateTime = df.format(new Date()); - } - writeLog(dateTime + " ERROR: " + message); + log.warning(message); + if (!Settings.useLogging) { + return; } + writeLog("ERROR: " + message); } /** - * Method writeLog. + * Write a message into the log file with a TimeStamp. + * * @param message String */ - public static void writeLog(String message) { + private static void writeLog(String message) { + String dateTime; + synchronized (df) { + dateTime = df.format(new Date()); + } try { - Files.write(Settings.LOG_FILE.toPath(), (message + NewAPI.newline).getBytes(), + Files.write(Settings.LOG_FILE.toPath(), (dateTime + ": " + message + StringUtils.newline).getBytes(), StandardOpenOption.APPEND, StandardOpenOption.CREATE); } catch (IOException ignored) { @@ -64,16 +74,14 @@ public class ConsoleLogger { } /** - * Method writeStackTrace. + * Write a StackTrace into the log. + * * @param ex Exception */ public static void writeStackTrace(Exception ex) { - if (Settings.useLogging) { - String dateTime; - synchronized (df) { - dateTime = df.format(new Date()); - } - writeLog(dateTime + " " + Throwables.getStackTraceAsString(ex)); + if (!Settings.useLogging) { + return; } + writeLog("" + Throwables.getStackTraceAsString(ex)); } } diff --git a/src/main/java/fr/xephi/authme/DataManager.java b/src/main/java/fr/xephi/authme/DataManager.java index d4688e5c..58bdca4d 100644 --- a/src/main/java/fr/xephi/authme/DataManager.java +++ b/src/main/java/fr/xephi/authme/DataManager.java @@ -1,5 +1,12 @@ package fr.xephi.authme; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.Utils; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + import java.io.File; import java.util.List; import java.util.concurrent.Callable; @@ -7,14 +14,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import fr.xephi.authme.permission.PermissionsManager; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; - -import fr.xephi.authme.settings.Settings; -import fr.xephi.authme.util.Utils; - /** */ public class DataManager { @@ -23,6 +22,7 @@ public class DataManager { /** * Constructor for DataManager. + * * @param plugin AuthMe */ public DataManager(AuthMe plugin) { @@ -34,9 +34,10 @@ public class DataManager { /** * Method getOfflinePlayer. + * * @param name String - - * @return OfflinePlayer */ + * @return OfflinePlayer + */ public synchronized OfflinePlayer getOfflinePlayer(final String name) { ExecutorService executor = Executors.newSingleThreadExecutor(); Future result = executor.submit(new Callable() { @@ -65,6 +66,7 @@ public class DataManager { /** * Method purgeAntiXray. + * * @param cleared List */ public synchronized void purgeAntiXray(List cleared) { @@ -88,6 +90,7 @@ public class DataManager { /** * Method purgeLimitedCreative. + * * @param cleared List */ public synchronized void purgeLimitedCreative(List cleared) { @@ -121,6 +124,7 @@ public class DataManager { /** * Method purgeDat. + * * @param cleared List */ public synchronized void purgeDat(List cleared) { @@ -136,7 +140,7 @@ public class DataManager { File playerFile = new File(plugin.getServer().getWorldContainer() + File.separator + Settings.defaultWorld + File.separator + "players" + File.separator + player.getUniqueId() + ".dat"); playerFile.delete(); i++; - } catch(Exception ignore) { + } catch (Exception ignore) { File playerFile = new File(plugin.getServer().getWorldContainer() + File.separator + Settings.defaultWorld + File.separator + "players" + File.separator + player.getName() + ".dat"); if (playerFile.exists()) { playerFile.delete(); @@ -151,6 +155,7 @@ public class DataManager { /** * Method purgeEssentials. + * * @param cleared List */ @SuppressWarnings("deprecation") @@ -158,7 +163,7 @@ public class DataManager { int i = 0; for (String name : cleared) { try { - File playerFile = new File(plugin.ess.getDataFolder() + File.separator + "userdata" + File.separator + plugin.getServer().getOfflinePlayer(name).getUniqueId() + ".yml"); + File playerFile = new File(plugin.ess.getDataFolder() + File.separator + "userdata" + File.separator + plugin.getServer().getOfflinePlayer(name).getUniqueId() + ".yml"); playerFile.delete(); i++; } catch (Exception e) { @@ -173,13 +178,14 @@ public class DataManager { } // TODO: What is this method for? Is it correct? + /** * @param cleared Cleared players. */ public synchronized void purgePermissions(List cleared) { // Get the permissions manager, and make sure it's valid PermissionsManager permsMan = this.plugin.getPermissionsManager(); - if(permsMan == null) + if (permsMan == null) ConsoleLogger.showError("Unable to access permissions manager instance!"); assert permsMan != null; @@ -188,7 +194,7 @@ public class DataManager { try { permsMan.removeAllGroups(this.getOnlinePlayerLower(name.toLowerCase())); i++; - } catch(Exception e) { + } catch (Exception e) { } } ConsoleLogger.info("AutoPurgeDatabase : Removed " + i + " permissions"); @@ -209,10 +215,11 @@ public class DataManager { /** * Method isOnline. + * * @param player Player - * @param name String - - * @return boolean */ + * @param name String + * @return boolean + */ public boolean isOnline(Player player, final String name) { if (player.isOnline()) return true; @@ -239,9 +246,10 @@ public class DataManager { /** * Method getOnlinePlayerLower. + * * @param name String - - * @return Player */ + * @return Player + */ public Player getOnlinePlayerLower(String name) { name = name.toLowerCase(); for (Player player : Utils.getOnlinePlayers()) { diff --git a/src/main/java/fr/xephi/authme/ImageGenerator.java b/src/main/java/fr/xephi/authme/ImageGenerator.java index 2b6a187e..bdcefa44 100644 --- a/src/main/java/fr/xephi/authme/ImageGenerator.java +++ b/src/main/java/fr/xephi/authme/ImageGenerator.java @@ -1,9 +1,6 @@ package fr.xephi.authme; -import java.awt.Color; -import java.awt.Font; -import java.awt.GradientPaint; -import java.awt.Graphics2D; +import java.awt.*; import java.awt.image.BufferedImage; /** @@ -11,8 +8,10 @@ import java.awt.image.BufferedImage; public class ImageGenerator { private String pass; + /** * Constructor for ImageGenerator. + * * @param pass String */ public ImageGenerator(String pass) { @@ -21,8 +20,9 @@ public class ImageGenerator { /** * Method generateImage. - - * @return BufferedImage */ + * + * @return BufferedImage + */ public BufferedImage generateImage() { BufferedImage image = new BufferedImage(200, 60, BufferedImage.TYPE_BYTE_INDEXED); Graphics2D graphics = image.createGraphics(); diff --git a/src/main/java/fr/xephi/authme/Log4JFilter.java b/src/main/java/fr/xephi/authme/Log4JFilter.java index 917a7536..0db37bb7 100644 --- a/src/main/java/fr/xephi/authme/Log4JFilter.java +++ b/src/main/java/fr/xephi/authme/Log4JFilter.java @@ -1,56 +1,95 @@ package fr.xephi.authme; +import fr.xephi.authme.util.StringUtils; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.message.Message; -import fr.xephi.authme.util.StringUtils; - /** * Implements a filter for Log4j to skip sensitive AuthMe commands. + * * @author Xephi59 * @version $Revision: 1.0 $ */ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { - - /** List of commands (lower-case) to skip. */ - private static final String[] COMMANDS_TO_SKIP = { "/login ", "/l ", "/reg ", "/changepassword ", - "/unregister ", "/authme register ", "/authme changepassword ", "/authme reg ", "/authme cp ", - "/register " }; - /** Constructor. */ + /** + * List of commands (lower-case) to skip. + */ + private static final String[] COMMANDS_TO_SKIP = {"/login ", "/l ", "/reg ", "/changepassword ", + "/unregister ", "/authme register ", "/authme changepassword ", "/authme reg ", "/authme cp ", + "/register "}; + + /** + * Constructor. + */ public Log4JFilter() { } + /** + * Validates a Message instance and returns the {@link Result} value + * depending depending on whether the message contains sensitive AuthMe + * data. + * + * @param message the Message object to verify + * @return the Result value + */ + private static Result validateMessage(Message message) { + if (message == null) { + return Result.NEUTRAL; + } + return validateMessage(message.getFormattedMessage()); + } + + /** + * Validates a message and returns the {@link Result} value depending + * depending on whether the message contains sensitive AuthMe data. + * + * @param message the message to verify + * @return the Result value + */ + private static Result validateMessage(String message) { + if (message == null) { + return Result.NEUTRAL; + } + + String lowerMessage = message.toLowerCase(); + if (lowerMessage.contains("issued server command:") + && StringUtils.containsAny(lowerMessage, COMMANDS_TO_SKIP)) { + return Result.DENY; + } + return Result.NEUTRAL; + } + @Override public Result filter(LogEvent record) { - if (record == null) { - return Result.NEUTRAL; - } - return validateMessage(record.getMessage()); + if (record == null) { + return Result.NEUTRAL; + } + return validateMessage(record.getMessage()); } @Override public Result filter(Logger arg0, Level arg1, Marker arg2, String message, - Object... arg4) { - return validateMessage(message); + Object... arg4) { + return validateMessage(message); } @Override public Result filter(Logger arg0, Level arg1, Marker arg2, Object message, - Throwable arg4) { - if (message == null) { - return Result.NEUTRAL; - } - return validateMessage(message.toString()); + Throwable arg4) { + if (message == null) { + return Result.NEUTRAL; + } + return validateMessage(message.toString()); } @Override public Result filter(Logger arg0, Level arg1, Marker arg2, Message message, - Throwable arg4) { - return validateMessage(message); + Throwable arg4) { + return validateMessage(message); } @Override @@ -63,41 +102,4 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter { return Result.NEUTRAL; } - /** - * Validates a Message instance and returns the {@link Result} value - * depending depending on whether the message contains sensitive AuthMe - * data. - * - * @param message the Message object to verify - * - * @return the Result value - */ - private static Result validateMessage(Message message) { - if (message == null) { - return Result.NEUTRAL; - } - return validateMessage(message.getFormattedMessage()); - } - - /** - * Validates a message and returns the {@link Result} value depending - * depending on whether the message contains sensitive AuthMe data. - * - * @param message the message to verify - * - * @return the Result value - */ - private static Result validateMessage(String message) { - if (message == null) { - return Result.NEUTRAL; - } - - String lowerMessage = message.toLowerCase(); - if (lowerMessage.contains("issued server command:") - && StringUtils.containsAny(lowerMessage, COMMANDS_TO_SKIP)) { - return Result.DENY; - } - return Result.NEUTRAL; - } - } diff --git a/src/main/java/fr/xephi/authme/PerformBackup.java b/src/main/java/fr/xephi/authme/PerformBackup.java index 355c1649..92194949 100644 --- a/src/main/java/fr/xephi/authme/PerformBackup.java +++ b/src/main/java/fr/xephi/authme/PerformBackup.java @@ -1,44 +1,66 @@ package fr.xephi.authme; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.text.SimpleDateFormat; -import java.util.Date; - import fr.xephi.authme.settings.Settings; +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.Date; + /** + * The backup management class * * @author stefano * @version $Revision: 1.0 $ */ public class PerformBackup { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd_HH-mm"); + String dateString = format.format(new Date()); private String dbName = Settings.getMySQLDatabase; private String dbUserName = Settings.getMySQLUsername; private String dbPassword = Settings.getMySQLPassword; private String tblname = Settings.getMySQLTablename; - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd_HH-mm"); - String dateString = format.format(new Date()); private String path = AuthMe.getInstance().getDataFolder() + File.separator + "backups" + File.separator + "backup" + dateString; private AuthMe instance; /** * Constructor for PerformBackup. + * * @param instance AuthMe */ public PerformBackup(AuthMe instance) { this.setInstance(instance); } + /** + * + */ + public void doBackup(BackupCause cause) { + // Check whether a backup should be made at the specified point in time + switch (cause) { + case START: + if (!Settings.isBackupOnStart) + return; + case STOP: + if (!Settings.isBackupOnStop) + return; + case COMMAND: + case OTHER: + } + + // Do backup and check return value! + if (doBackup()) { + ConsoleLogger.info("A backup has been performed successfully"); + } else { + ConsoleLogger.showError("Error while performing a backup!"); + } + } + /** * Method doBackup. - - * @return boolean */ + * + * @return boolean + */ public boolean doBackup() { switch (Settings.getDataSource) { @@ -55,8 +77,9 @@ public class PerformBackup { /** * Method MySqlBackup. - - * @return boolean */ + * + * @return boolean + */ private boolean MySqlBackup() { File dirBackup = new File(AuthMe.getInstance().getDataFolder() + "/backups"); @@ -98,9 +121,10 @@ public class PerformBackup { /** * Method FileBackup. + * * @param backend String - - * @return boolean */ + * @return boolean + */ private boolean FileBackup(String backend) { File dirBackup = new File(AuthMe.getInstance().getDataFolder() + "/backups"); @@ -117,15 +141,12 @@ public class PerformBackup { return false; } - /* - * Check if we are under Windows and correct location of mysqldump.exe - * otherwise return error. - */ /** * Method checkWindows. + * * @param windowsPath String - - * @return boolean */ + * @return boolean + */ private boolean checkWindows(String windowsPath) { String isWin = System.getProperty("os.name").toLowerCase(); if (isWin.indexOf("win") >= 0) { @@ -139,14 +160,17 @@ public class PerformBackup { } /* - * Copyr src bytefile into dst file + * Check if we are under Windows and correct location of mysqldump.exe + * otherwise return error. */ + /** * Method copy. + * * @param src File * @param dst File - - * @throws IOException */ + * @throws IOException + */ void copy(File src, File dst) throws IOException { InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst); @@ -161,8 +185,22 @@ public class PerformBackup { out.close(); } + /* + * Copyr src bytefile into dst file + */ + + /** + * Method getInstance. + * + * @return AuthMe + */ + public AuthMe getInstance() { + return instance; + } + /** * Method setInstance. + * * @param instance AuthMe */ public void setInstance(AuthMe instance) { @@ -170,11 +208,13 @@ public class PerformBackup { } /** - * Method getInstance. - - * @return AuthMe */ - public AuthMe getInstance() { - return instance; + * Possible backup causes. + */ + public enum BackupCause { + START, + STOP, + COMMAND, + OTHER, } } diff --git a/src/main/java/fr/xephi/authme/SendMailSSL.java b/src/main/java/fr/xephi/authme/SendMailSSL.java index f3ed378d..60f5eaed 100644 --- a/src/main/java/fr/xephi/authme/SendMailSSL.java +++ b/src/main/java/fr/xephi/authme/SendMailSSL.java @@ -1,19 +1,16 @@ package fr.xephi.authme; -import java.io.File; +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.settings.Settings; +import org.apache.commons.mail.HtmlEmail; +import org.bukkit.Bukkit; import javax.activation.DataSource; import javax.activation.FileDataSource; import javax.imageio.ImageIO; - -import org.apache.commons.mail.HtmlEmail; -import org.bukkit.Bukkit; - -import fr.xephi.authme.cache.auth.PlayerAuth; -import fr.xephi.authme.settings.Settings; +import java.io.File; /** - * * @author Xephi59 * @version $Revision: 1.0 $ */ @@ -23,6 +20,7 @@ public class SendMailSSL { /** * Constructor for SendMailSSL. + * * @param plugin AuthMe */ public SendMailSSL(AuthMe plugin) { @@ -31,7 +29,8 @@ public class SendMailSSL { /** * Method main. - * @param auth PlayerAuth + * + * @param auth PlayerAuth * @param newPass String */ public void main(final PlayerAuth auth, final String newPass) { diff --git a/src/main/java/fr/xephi/authme/api/API.java b/src/main/java/fr/xephi/authme/api/API.java index 6119f28e..595e5202 100644 --- a/src/main/java/fr/xephi/authme/api/API.java +++ b/src/main/java/fr/xephi/authme/api/API.java @@ -1,19 +1,18 @@ package fr.xephi.authme.api; -import java.security.NoSuchAlgorithmException; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; + +import java.security.NoSuchAlgorithmException; /** */ @@ -24,6 +23,7 @@ public class API { /** * Constructor for API. + * * @param instance AuthMe */ @Deprecated @@ -34,8 +34,8 @@ public class API { /** * Hook into AuthMe * - - * @return AuthMe instance */ + * @return AuthMe instance + */ @Deprecated public static AuthMe hookAuthMe() { if (instance != null) @@ -49,49 +49,18 @@ public class API { } /** - * Method getPlugin. - - * @return AuthMe */ - @Deprecated - public AuthMe getPlugin() { - return instance; - } - - /** - * * @param player - - * @return true if player is authenticate */ + * @return true if player is authenticate + */ @Deprecated public static boolean isAuthenticated(Player player) { return PlayerCache.getInstance().isAuthenticated(player.getName()); } /** - * * @param player - - * @return true if player is a npc */ - @Deprecated - public boolean isaNPC(Player player) { - return Utils.isNPC(player); - } - - /** - * - * @param player - - * @return true if player is a npc */ - @Deprecated - public boolean isNPC(Player player) { - return Utils.isNPC(player); - } - - /** - * - * @param player - - * @return true if the player is unrestricted */ + * @return true if the player is unrestricted + */ @Deprecated public static boolean isUnrestricted(Player player) { return Utils.isUnrestricted(player); @@ -99,9 +68,10 @@ public class API { /** * Method getLastLocation. + * * @param player Player - - * @return Location */ + * @return Location + */ @Deprecated public static Location getLastLocation(Player player) { try { @@ -121,13 +91,14 @@ public class API { /** * Method setPlayerInventory. - * @param player Player + * + * @param player Player * @param content ItemStack[] - * @param armor ItemStack[] + * @param armor ItemStack[] */ @Deprecated public static void setPlayerInventory(Player player, ItemStack[] content, - ItemStack[] armor) { + ItemStack[] armor) { try { player.getInventory().setContents(content); player.getInventory().setArmorContents(armor); @@ -136,10 +107,9 @@ public class API { } /** - * * @param playerName - - * @return true if player is registered */ + * @return true if player is registered + */ @Deprecated public static boolean isRegistered(String playerName) { String player = playerName.toLowerCase(); @@ -147,14 +117,13 @@ public class API { } /** - - - * @param playerName String + * @param playerName String * @param passwordToCheck String - * @return true if the password is correct , false else */ + * @return true if the password is correct , false else + */ @Deprecated public static boolean checkPassword(String playerName, - String passwordToCheck) { + String passwordToCheck) { if (!isRegistered(playerName)) return false; String player = playerName.toLowerCase(); @@ -169,11 +138,10 @@ public class API { /** * Register a player * - - * @param playerName String - * @param password String - * @return true if the player is register correctly */ + * @param password String + * @return true if the player is register correctly + */ @Deprecated public static boolean registerPlayer(String playerName, String password) { try { @@ -195,7 +163,6 @@ public class API { /** * Force a player to login * - * @param player * player */ @Deprecated @@ -203,4 +170,32 @@ public class API { instance.management.performLogin(player, "dontneed", true); } + /** + * Method getPlugin. + * + * @return AuthMe + */ + @Deprecated + public AuthMe getPlugin() { + return instance; + } + + /** + * @param player + * @return true if player is a npc + */ + @Deprecated + public boolean isaNPC(Player player) { + return Utils.isNPC(player); + } + + /** + * @param player + * @return true if player is a npc + */ + @Deprecated + public boolean isNPC(Player player) { + return Utils.isNPC(player); + } + } diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index b1aa27a6..a5bc9a68 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -1,30 +1,29 @@ package fr.xephi.authme.api; -import java.security.NoSuchAlgorithmException; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import java.security.NoSuchAlgorithmException; /** */ public class NewAPI { - public static final String newline = System.getProperty("line.separator"); public static NewAPI singleton; public AuthMe plugin; /** * Constructor for NewAPI. + * * @param plugin AuthMe */ public NewAPI(AuthMe plugin) { @@ -33,6 +32,7 @@ public class NewAPI { /** * Constructor for NewAPI. + * * @param serv Server */ public NewAPI(Server serv) { @@ -42,8 +42,8 @@ public class NewAPI { /** * Hook into AuthMe * - - * @return AuthMe plugin */ + * @return AuthMe plugin + */ public static NewAPI getInstance() { if (singleton != null) return singleton; @@ -58,44 +58,43 @@ public class NewAPI { /** * Method getPlugin. - - * @return AuthMe */ + * + * @return AuthMe + */ public AuthMe getPlugin() { return plugin; } /** - * * @param player - - * @return true if player is authenticate */ + * @return true if player is authenticate + */ public boolean isAuthenticated(Player player) { return PlayerCache.getInstance().isAuthenticated(player.getName()); } /** - * * @param player - - * @return true if player is a npc */ + * @return true if player is a npc + */ public boolean isNPC(Player player) { return Utils.isNPC(player); } /** - * * @param player - - * @return true if the player is unrestricted */ + * @return true if the player is unrestricted + */ public boolean isUnrestricted(Player player) { return Utils.isUnrestricted(player); } /** * Method getLastLocation. + * * @param player Player - - * @return Location */ + * @return Location + */ public Location getLastLocation(Player player) { try { PlayerAuth auth = PlayerCache.getInstance().getAuth(player.getName().toLowerCase()); @@ -112,21 +111,19 @@ public class NewAPI { } /** - * * @param playerName - - * @return true if player is registered */ + * @return true if player is registered + */ public boolean isRegistered(String playerName) { String player = playerName.toLowerCase(); return plugin.database.isAuthAvailable(player); } /** - - - * @param playerName String + * @param playerName String * @param passwordToCheck String - * @return true if the password is correct , false else */ + * @return true if the password is correct , false else + */ public boolean checkPassword(String playerName, String passwordToCheck) { if (!isRegistered(playerName)) return false; @@ -142,11 +139,10 @@ public class NewAPI { /** * Register a player * - - * @param playerName String - * @param password String - * @return true if the player is register correctly */ + * @param password String + * @return true if the player is register correctly + */ public boolean registerPlayer(String playerName, String password) { try { String name = playerName.toLowerCase(); @@ -164,7 +160,6 @@ public class NewAPI { /** * Force a player to login * - * @param player * player */ public void forceLogin(Player player) { @@ -174,35 +169,28 @@ public class NewAPI { /** * Force a player to logout * - * @param player * player */ - public void forceLogout(Player player) - { - plugin.management.performLogout(player); + public void forceLogout(Player player) { + plugin.management.performLogout(player); } /** * Force a player to register * - - - * @param player * player + * @param player * player * @param password String */ - public void forceRegister(Player player, String password) - { - plugin.management.performRegister(player, password, null); + public void forceRegister(Player player, String password) { + plugin.management.performRegister(player, password, null); } /** * Force a player to unregister * - * @param player * player */ - public void forceUnregister(Player player) - { - plugin.management.performUnregister(player, "", true); + public void forceUnregister(Player player) { + plugin.management.performUnregister(player, "", true); } } diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java index 5b229823..2bfcd4f6 100644 --- a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java +++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java @@ -22,10 +22,11 @@ public class PlayerAuth { /** * Constructor for PlayerAuth. - * @param nickname String - * @param ip String + * + * @param nickname String + * @param ip String * @param lastLogin long - * @param realName String + * @param realName String */ public PlayerAuth(String nickname, String ip, long lastLogin, String realName) { this(nickname, "", "", -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); @@ -33,11 +34,12 @@ public class PlayerAuth { /** * Constructor for PlayerAuth. + * * @param nickname String - * @param x double - * @param y double - * @param z double - * @param world String + * @param x double + * @param y double + * @param z double + * @param world String * @param realName String */ public PlayerAuth(String nickname, double x, double y, double z, String world, String realName) { @@ -46,11 +48,12 @@ public class PlayerAuth { /** * Constructor for PlayerAuth. - * @param nickname String - * @param hash String - * @param ip String + * + * @param nickname String + * @param hash String + * @param ip String * @param lastLogin long - * @param realName String + * @param realName String */ public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String realName) { this(nickname, hash, "", -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); @@ -58,12 +61,13 @@ public class PlayerAuth { /** * Constructor for PlayerAuth. - * @param nickname String - * @param hash String - * @param ip String + * + * @param nickname String + * @param hash String + * @param ip String * @param lastLogin long - * @param email String - * @param realName String + * @param email String + * @param realName String */ public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String email, String realName) { this(nickname, hash, "", -1, ip, lastLogin, 0, 0, 0, "world", email, realName); @@ -71,12 +75,13 @@ public class PlayerAuth { /** * Constructor for PlayerAuth. - * @param nickname String - * @param hash String - * @param salt String - * @param ip String + * + * @param nickname String + * @param hash String + * @param salt String + * @param ip String * @param lastLogin long - * @param realName String + * @param realName String */ public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, String realName) { this(nickname, hash, salt, -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); @@ -84,16 +89,17 @@ public class PlayerAuth { /** * Constructor for PlayerAuth. - * @param nickname String - * @param hash String - * @param ip String + * + * @param nickname String + * @param hash String + * @param ip String * @param lastLogin long - * @param x double - * @param y double - * @param z double - * @param world String - * @param email String - * @param realName String + * @param x double + * @param y double + * @param z double + * @param world String + * @param email String + * @param realName String */ public PlayerAuth(String nickname, String hash, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { this(nickname, hash, "", -1, ip, lastLogin, x, y, z, world, email, realName); @@ -101,17 +107,18 @@ public class PlayerAuth { /** * Constructor for PlayerAuth. - * @param nickname String - * @param hash String - * @param salt String - * @param ip String + * + * @param nickname String + * @param hash String + * @param salt String + * @param ip String * @param lastLogin long - * @param x double - * @param y double - * @param z double - * @param world String - * @param email String - * @param realName String + * @param x double + * @param y double + * @param z double + * @param world String + * @param email String + * @param realName String */ public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { this(nickname, hash, salt, -1, ip, lastLogin, x, y, z, world, email, realName); @@ -119,13 +126,14 @@ public class PlayerAuth { /** * Constructor for PlayerAuth. - * @param nickname String - * @param hash String - * @param salt String - * @param groupId int - * @param ip String + * + * @param nickname String + * @param hash String + * @param salt String + * @param groupId int + * @param ip String * @param lastLogin long - * @param realName String + * @param realName String */ public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip, long lastLogin, String realName) { this(nickname, hash, salt, groupId, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); @@ -133,18 +141,19 @@ public class PlayerAuth { /** * Constructor for PlayerAuth. - * @param nickname String - * @param hash String - * @param salt String - * @param groupId int - * @param ip String + * + * @param nickname String + * @param hash String + * @param salt String + * @param groupId int + * @param ip String * @param lastLogin long - * @param x double - * @param y double - * @param z double - * @param world String - * @param email String - * @param realName String + * @param x double + * @param y double + * @param z double + * @param world String + * @param email String + * @param realName String */ public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { this.nickname = nickname; @@ -163,6 +172,7 @@ public class PlayerAuth { /** * Method set. + * * @param auth PlayerAuth */ public void set(PlayerAuth auth) { @@ -181,6 +191,7 @@ public class PlayerAuth { /** * Method setName. + * * @param nickname String */ public void setName(String nickname) { @@ -189,22 +200,25 @@ public class PlayerAuth { /** * Method getNickname. - - * @return String */ + * + * @return String + */ public String getNickname() { return nickname; } /** * Method getRealName. - - * @return String */ + * + * @return String + */ public String getRealName() { return realName; } /** * Method setRealName. + * * @param realName String */ public void setRealName(String realName) { @@ -213,14 +227,25 @@ public class PlayerAuth { /** * Method getGroupId. - - * @return int */ + * + * @return int + */ public int getGroupId() { return groupId; } + /** + * Method getQuitLocX. + * + * @return double + */ + public double getQuitLocX() { + return x; + } + /** * Method setQuitLocX. + * * @param d double */ public void setQuitLocX(double d) { @@ -228,15 +253,17 @@ public class PlayerAuth { } /** - * Method getQuitLocX. - - * @return double */ - public double getQuitLocX() { - return x; + * Method getQuitLocY. + * + * @return double + */ + public double getQuitLocY() { + return y; } /** * Method setQuitLocY. + * * @param d double */ public void setQuitLocY(double d) { @@ -244,15 +271,17 @@ public class PlayerAuth { } /** - * Method getQuitLocY. - - * @return double */ - public double getQuitLocY() { - return y; + * Method getQuitLocZ. + * + * @return double + */ + public double getQuitLocZ() { + return z; } /** * Method setQuitLocZ. + * * @param d double */ public void setQuitLocZ(double d) { @@ -260,15 +289,17 @@ public class PlayerAuth { } /** - * Method getQuitLocZ. - - * @return double */ - public double getQuitLocZ() { - return z; + * Method getWorld. + * + * @return String + */ + public String getWorld() { + return world; } /** * Method setWorld. + * * @param world String */ public void setWorld(String world) { @@ -276,15 +307,17 @@ public class PlayerAuth { } /** - * Method getWorld. - - * @return String */ - public String getWorld() { - return world; + * Method getIp. + * + * @return String + */ + public String getIp() { + return ip; } /** * Method setIp. + * * @param ip String */ public void setIp(String ip) { @@ -292,15 +325,17 @@ public class PlayerAuth { } /** - * Method getIp. - - * @return String */ - public String getIp() { - return ip; + * Method getLastLogin. + * + * @return long + */ + public long getLastLogin() { + return lastLogin; } /** * Method setLastLogin. + * * @param lastLogin long */ public void setLastLogin(long lastLogin) { @@ -308,15 +343,17 @@ public class PlayerAuth { } /** - * Method getLastLogin. - - * @return long */ - public long getLastLogin() { - return lastLogin; + * Method getEmail. + * + * @return String + */ + public String getEmail() { + return email; } /** * Method setEmail. + * * @param email String */ public void setEmail(String email) { @@ -324,41 +361,28 @@ public class PlayerAuth { } /** - * Method getEmail. - - * @return String */ - public String getEmail() { - return email; + * Method getSalt. + * + * @return String + */ + public String getSalt() { + return this.salt; } /** * Method setSalt. + * * @param salt String */ public void setSalt(String salt) { this.salt = salt; } - /** - * Method getSalt. - - * @return String */ - public String getSalt() { - return this.salt; - } - - /** - * Method setHash. - * @param hash String - */ - public void setHash(String hash) { - this.hash = hash; - } - /** * Method getHash. - - * @return String */ + * + * @return String + */ public String getHash() { if (Settings.getPasswordHash == HashAlgorithm.MD5VB) { if (salt != null && !salt.isEmpty() && Settings.getPasswordHash == HashAlgorithm.MD5VB) { @@ -368,11 +392,21 @@ public class PlayerAuth { return hash; } + /** + * Method setHash. + * + * @param hash String + */ + public void setHash(String hash) { + this.hash = hash; + } + /** * Method equals. + * * @param obj Object - - * @return boolean */ + * @return boolean + */ @Override public boolean equals(Object obj) { if (!(obj instanceof PlayerAuth)) { @@ -384,8 +418,9 @@ public class PlayerAuth { /** * Method hashCode. - - * @return int */ + * + * @return int + */ @Override public int hashCode() { int hashCode = 7; @@ -396,8 +431,9 @@ public class PlayerAuth { /** * Method toString. - - * @return String */ + * + * @return String + */ @Override public String toString() { return ("Player : " + nickname + " | " + realName diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java index e72fcd68..53f0a799 100644 --- a/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java +++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java @@ -13,8 +13,21 @@ public class PlayerCache { cache = new ConcurrentHashMap<>(); } + /** + * Method getInstance. + * + * @return PlayerCache + */ + public static PlayerCache getInstance() { + if (singleton == null) { + singleton = new PlayerCache(); + } + return singleton; + } + /** * Method addPlayer. + * * @param auth PlayerAuth */ public void addPlayer(PlayerAuth auth) { @@ -23,6 +36,7 @@ public class PlayerCache { /** * Method updatePlayer. + * * @param auth PlayerAuth */ public void updatePlayer(PlayerAuth auth) { @@ -32,6 +46,7 @@ public class PlayerCache { /** * Method removePlayer. + * * @param user String */ public void removePlayer(String user) { @@ -40,45 +55,38 @@ public class PlayerCache { /** * Method isAuthenticated. + * * @param user String - - * @return boolean */ + * @return boolean + */ public boolean isAuthenticated(String user) { return cache.containsKey(user.toLowerCase()); } /** * Method getAuth. + * * @param user String - - * @return PlayerAuth */ + * @return PlayerAuth + */ public PlayerAuth getAuth(String user) { return cache.get(user.toLowerCase()); } - /** - * Method getInstance. - - * @return PlayerCache */ - public static PlayerCache getInstance() { - if (singleton == null) { - singleton = new PlayerCache(); - } - return singleton; - } - /** * Method getLogged. - - * @return int */ + * + * @return int + */ public int getLogged() { return cache.size(); } /** * Method getCache. - - * @return ConcurrentHashMap */ + * + * @return ConcurrentHashMap + */ public ConcurrentHashMap getCache() { return this.cache; } diff --git a/src/main/java/fr/xephi/authme/cache/backup/DataFileCache.java b/src/main/java/fr/xephi/authme/cache/backup/DataFileCache.java index 01751d78..c40cab96 100644 --- a/src/main/java/fr/xephi/authme/cache/backup/DataFileCache.java +++ b/src/main/java/fr/xephi/authme/cache/backup/DataFileCache.java @@ -10,9 +10,10 @@ public class DataFileCache { /** * Constructor for DataFileCache. - * @param group String + * + * @param group String * @param operator boolean - * @param flying boolean + * @param flying boolean */ public DataFileCache(String group, boolean operator, boolean flying) { this.group = group; @@ -22,24 +23,27 @@ public class DataFileCache { /** * Method getGroup. - - * @return String */ + * + * @return String + */ public String getGroup() { return group; } /** * Method getOperator. - - * @return boolean */ + * + * @return boolean + */ public boolean getOperator() { return operator; } /** * Method isFlying. - - * @return boolean */ + * + * @return boolean + */ public boolean isFlying() { return flying; } diff --git a/src/main/java/fr/xephi/authme/cache/backup/JsonCache.java b/src/main/java/fr/xephi/authme/cache/backup/JsonCache.java index 70d6614c..9b0b7f36 100644 --- a/src/main/java/fr/xephi/authme/cache/backup/JsonCache.java +++ b/src/main/java/fr/xephi/authme/cache/backup/JsonCache.java @@ -1,27 +1,17 @@ package fr.xephi.authme.cache.backup; +import com.google.common.base.Charsets; +import com.google.common.io.Files; +import com.google.gson.*; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.Utils; +import org.bukkit.entity.Player; + import java.io.File; import java.io.IOException; import java.lang.reflect.Type; -import org.bukkit.entity.Player; - -import com.google.common.base.Charsets; -import com.google.common.io.Files; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; - -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.settings.Settings; -import fr.xephi.authme.util.Utils; - /** */ public class JsonCache { @@ -43,7 +33,8 @@ public class JsonCache { /** * Method createCache. - * @param player Player + * + * @param player Player * @param playerData DataFileCache */ public void createCache(Player player, DataFileCache playerData) { @@ -77,9 +68,10 @@ public class JsonCache { /** * Method readCache. + * * @param player Player - - * @return DataFileCache */ + * @return DataFileCache + */ public DataFileCache readCache(Player player) { String path; try { @@ -103,24 +95,41 @@ public class JsonCache { } /** + * Method removeCache. + * + * @param player Player */ - private class PlayerDataSerializer implements JsonSerializer { - /** - * Method serialize. - * @param dataFileCache DataFileCache - * @param type Type - * @param jsonSerializationContext JsonSerializationContext - - * @return JsonElement */ - @Override - public JsonElement serialize(DataFileCache dataFileCache, Type type, JsonSerializationContext jsonSerializationContext) { - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("group", dataFileCache.getGroup()); - jsonObject.addProperty("operator", dataFileCache.getOperator()); - jsonObject.addProperty("flying", dataFileCache.isFlying()); - - return jsonObject; + public void removeCache(Player player) { + String path; + try { + path = player.getUniqueId().toString(); + } catch (Exception | Error e) { + path = player.getName().toLowerCase(); } + File file = new File(cacheDir, path); + if (file.exists()) { + Utils.purgeDirectory(file); + if (!file.delete()) { + ConsoleLogger.showError("Failed to remove" + player.getName() + "cache."); + } + } + } + + /** + * Method doesCacheExist. + * + * @param player Player + * @return boolean + */ + public boolean doesCacheExist(Player player) { + String path; + try { + path = player.getUniqueId().toString(); + } catch (Exception | Error e) { + path = player.getName().toLowerCase(); + } + File file = new File(cacheDir, path + File.separator + "cache.json"); + return file.exists(); } /** @@ -128,13 +137,12 @@ public class JsonCache { private static class PlayerDataDeserializer implements JsonDeserializer { /** * Method deserialize. - * @param jsonElement JsonElement - * @param type Type + * + * @param jsonElement JsonElement + * @param type Type * @param jsonDeserializationContext JsonDeserializationContext - - - - * @return DataFileCache * @throws JsonParseException * @see com.google.gson.JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext) */ + * @return DataFileCache * @throws JsonParseException * @see com.google.gson.JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext) + */ @Override public DataFileCache deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { JsonObject jsonObject = jsonElement.getAsJsonObject(); @@ -161,39 +169,25 @@ public class JsonCache { } /** - * Method removeCache. - * @param player Player */ - public void removeCache(Player player) { - String path; - try { - path = player.getUniqueId().toString(); - } catch (Exception | Error e) { - path = player.getName().toLowerCase(); - } - File file = new File(cacheDir, path); - if (file.exists()) { - Utils.purgeDirectory(file); - if (!file.delete()) { - ConsoleLogger.showError("Failed to remove" + player.getName() + "cache."); - } - } - } + private class PlayerDataSerializer implements JsonSerializer { + /** + * Method serialize. + * + * @param dataFileCache DataFileCache + * @param type Type + * @param jsonSerializationContext JsonSerializationContext + * @return JsonElement + */ + @Override + public JsonElement serialize(DataFileCache dataFileCache, Type type, JsonSerializationContext jsonSerializationContext) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("group", dataFileCache.getGroup()); + jsonObject.addProperty("operator", dataFileCache.getOperator()); + jsonObject.addProperty("flying", dataFileCache.isFlying()); - /** - * Method doesCacheExist. - * @param player Player - - * @return boolean */ - public boolean doesCacheExist(Player player) { - String path; - try { - path = player.getUniqueId().toString(); - } catch (Exception | Error e) { - path = player.getName().toLowerCase(); + return jsonObject; } - File file = new File(cacheDir, path + File.separator + "cache.json"); - return file.exists(); } } diff --git a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java index 59dee468..2fb08b66 100644 --- a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java @@ -1,19 +1,18 @@ package fr.xephi.authme.cache.limbo; -import java.util.concurrent.ConcurrentHashMap; - -import fr.xephi.authme.permission.PermissionsManager; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.entity.Player; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.backup.DataFileCache; import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.events.ResetInventoryEvent; +import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.settings.Settings; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.concurrent.ConcurrentHashMap; /** */ @@ -21,11 +20,12 @@ public class LimboCache { private volatile static LimboCache singleton; public ConcurrentHashMap cache; - private JsonCache playerData; public AuthMe plugin; + private JsonCache playerData; /** * Constructor for LimboCache. + * * @param plugin AuthMe */ private LimboCache(AuthMe plugin) { @@ -34,6 +34,18 @@ public class LimboCache { this.playerData = new JsonCache(); } + /** + * Method getInstance. + * + * @return LimboCache + */ + public static LimboCache getInstance() { + if (singleton == null) { + singleton = new LimboCache(AuthMe.getInstance()); + } + return singleton; + } + /** * Add a limbo player. * @@ -49,7 +61,7 @@ public class LimboCache { // Get the permissions manager, and make sure it's valid PermissionsManager permsMan = this.plugin.getPermissionsManager(); - if(permsMan == null) + if (permsMan == null) ConsoleLogger.showError("Unable to access permissions manager!"); assert permsMan != null; @@ -65,7 +77,7 @@ public class LimboCache { flying = player.isFlying(); // Check whether groups are supported - if(permsMan.hasGroupSupport()) + if (permsMan.hasGroupSupport()) playerGroup = permsMan.getPrimaryGroup(player); } @@ -91,8 +103,9 @@ public class LimboCache { /** * Method addLimboPlayer. + * * @param player Player - * @param group String + * @param group String */ public void addLimboPlayer(Player player, String group) { cache.put(player.getName().toLowerCase(), new LimboPlayer(player.getName().toLowerCase(), group)); @@ -100,6 +113,7 @@ public class LimboCache { /** * Method deleteLimboPlayer. + * * @param name String */ public void deleteLimboPlayer(String name) { @@ -108,35 +122,27 @@ public class LimboCache { /** * Method getLimboPlayer. + * * @param name String - - * @return LimboPlayer */ + * @return LimboPlayer + */ public LimboPlayer getLimboPlayer(String name) { return cache.get(name); } /** * Method hasLimboPlayer. + * * @param name String - - * @return boolean */ + * @return boolean + */ public boolean hasLimboPlayer(String name) { return cache.containsKey(name); } - /** - * Method getInstance. - - * @return LimboCache */ - public static LimboCache getInstance() { - if (singleton == null) { - singleton = new LimboCache(AuthMe.getInstance()); - } - return singleton; - } - /** * Method updateLimboPlayer. + * * @param player Player */ public void updateLimboPlayer(Player player) { diff --git a/src/main/java/fr/xephi/authme/cache/limbo/LimboPlayer.java b/src/main/java/fr/xephi/authme/cache/limbo/LimboPlayer.java index d6f7b02b..64e65dc5 100644 --- a/src/main/java/fr/xephi/authme/cache/limbo/LimboPlayer.java +++ b/src/main/java/fr/xephi/authme/cache/limbo/LimboPlayer.java @@ -19,15 +19,16 @@ public class LimboPlayer { /** * Constructor for LimboPlayer. - * @param name String - * @param loc Location + * + * @param name String + * @param loc Location * @param gameMode GameMode * @param operator boolean - * @param group String - * @param flying boolean + * @param group String + * @param flying boolean */ public LimboPlayer(String name, Location loc, GameMode gameMode, - boolean operator, String group, boolean flying) { + boolean operator, String group, boolean flying) { this.name = name; this.loc = loc; this.gameMode = gameMode; @@ -38,7 +39,8 @@ public class LimboPlayer { /** * Constructor for LimboPlayer. - * @param name String + * + * @param name String * @param group String */ public LimboPlayer(String name, String group) { @@ -48,46 +50,61 @@ public class LimboPlayer { /** * Method getName. - - * @return String */ + * + * @return String + */ public String getName() { return name; } /** * Method getLoc. - - * @return Location */ + * + * @return Location + */ public Location getLoc() { return loc; } /** * Method getGameMode. - - * @return GameMode */ + * + * @return GameMode + */ public GameMode getGameMode() { return gameMode; } /** * Method getOperator. - - * @return boolean */ + * + * @return boolean + */ public boolean getOperator() { return operator; } /** * Method getGroup. - - * @return String */ + * + * @return String + */ public String getGroup() { return group; } + /** + * Method getTimeoutTaskId. + * + * @return BukkitTask + */ + public BukkitTask getTimeoutTaskId() { + return timeoutTaskId; + } + /** * Method setTimeoutTaskId. + * * @param i BukkitTask */ public void setTimeoutTaskId(BukkitTask i) { @@ -97,15 +114,17 @@ public class LimboPlayer { } /** - * Method getTimeoutTaskId. - - * @return BukkitTask */ - public BukkitTask getTimeoutTaskId() { - return timeoutTaskId; + * Method getMessageTaskId. + * + * @return BukkitTask + */ + public BukkitTask getMessageTaskId() { + return messageTaskId; } /** * Method setMessageTaskId. + * * @param messageTaskId BukkitTask */ public void setMessageTaskId(BukkitTask messageTaskId) { @@ -114,18 +133,11 @@ public class LimboPlayer { this.messageTaskId = messageTaskId; } - /** - * Method getMessageTaskId. - - * @return BukkitTask */ - public BukkitTask getMessageTaskId() { - return messageTaskId; - } - /** * Method isFlying. - - * @return boolean */ + * + * @return boolean + */ public boolean isFlying() { return flying; } diff --git a/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java b/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java index 75d6e4ae..c486a382 100644 --- a/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandArgumentDescription.java @@ -6,17 +6,23 @@ public class CommandArgumentDescription { // TODO: Allow argument to consist of infinite parts.

* This event is called when we need to compare or get an hash password, for set * a custom EncryptionMethod *

* - * @see fr.xephi.authme.security.crypts.EncryptionMethod - * * @author Xephi59 * @version $Revision: 1.0 $ + * @see fr.xephi.authme.security.crypts.EncryptionMethod */ public class PasswordEncryptionEvent extends Event { @@ -24,7 +22,8 @@ public class PasswordEncryptionEvent extends Event { /** * Constructor for PasswordEncryptionEvent. - * @param method EncryptionMethod + * + * @param method EncryptionMethod * @param playerName String */ public PasswordEncryptionEvent(EncryptionMethod method, String playerName) { @@ -33,45 +32,50 @@ public class PasswordEncryptionEvent extends Event { this.playerName = playerName; } + /** + * Method getHandlerList. + * + * @return HandlerList + */ + public static HandlerList getHandlerList() { + return handlers; + } + /** * Method getHandlers. - - * @return HandlerList */ + * + * @return HandlerList + */ @Override public HandlerList getHandlers() { return handlers; } + /** + * Method getMethod. + * + * @return EncryptionMethod + */ + public EncryptionMethod getMethod() { + return method; + } + /** * Method setMethod. + * * @param method EncryptionMethod */ public void setMethod(EncryptionMethod method) { this.method = method; } - /** - * Method getMethod. - - * @return EncryptionMethod */ - public EncryptionMethod getMethod() { - return method; - } - /** * Method getPlayerName. - - * @return String */ + * + * @return String + */ public String getPlayerName() { return playerName; } - /** - * Method getHandlerList. - - * @return HandlerList */ - public static HandlerList getHandlerList() { - return handlers; - } - } diff --git a/src/main/java/fr/xephi/authme/events/ProtectInventoryEvent.java b/src/main/java/fr/xephi/authme/events/ProtectInventoryEvent.java index 937c3f75..38531a2b 100644 --- a/src/main/java/fr/xephi/authme/events/ProtectInventoryEvent.java +++ b/src/main/java/fr/xephi/authme/events/ProtectInventoryEvent.java @@ -4,7 +4,6 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; /** - * * This event is call just after store inventory into cache and will empty the * player inventory. * @@ -21,6 +20,7 @@ public class ProtectInventoryEvent extends CustomEvent { /** * Constructor for ProtectInventoryEvent. + * * @param player Player */ public ProtectInventoryEvent(Player player) { @@ -34,30 +34,34 @@ public class ProtectInventoryEvent extends CustomEvent { /** * Method getStoredInventory. - - * @return ItemStack[] */ + * + * @return ItemStack[] + */ public ItemStack[] getStoredInventory() { return this.storedinventory; } /** * Method getStoredArmor. - - * @return ItemStack[] */ + * + * @return ItemStack[] + */ public ItemStack[] getStoredArmor() { return this.storedarmor; } /** * Method getPlayer. - - * @return Player */ + * + * @return Player + */ public Player getPlayer() { return this.player; } /** * Method setNewInventory. + * * @param emptyInventory ItemStack[] */ public void setNewInventory(ItemStack[] emptyInventory) { @@ -66,14 +70,16 @@ public class ProtectInventoryEvent extends CustomEvent { /** * Method getEmptyInventory. - - * @return ItemStack[] */ + * + * @return ItemStack[] + */ public ItemStack[] getEmptyInventory() { return this.emptyInventory; } /** * Method setNewArmor. + * * @param emptyArmor ItemStack[] */ public void setNewArmor(ItemStack[] emptyArmor) { @@ -82,8 +88,9 @@ public class ProtectInventoryEvent extends CustomEvent { /** * Method getEmptyArmor. - - * @return ItemStack[] */ + * + * @return ItemStack[] + */ public ItemStack[] getEmptyArmor() { return this.emptyArmor; } diff --git a/src/main/java/fr/xephi/authme/events/RegisterTeleportEvent.java b/src/main/java/fr/xephi/authme/events/RegisterTeleportEvent.java index c7678e40..2d327340 100644 --- a/src/main/java/fr/xephi/authme/events/RegisterTeleportEvent.java +++ b/src/main/java/fr/xephi/authme/events/RegisterTeleportEvent.java @@ -4,7 +4,6 @@ import org.bukkit.Location; import org.bukkit.entity.Player; /** - * * This event is call if, and only if, a player is teleported just after a * register. * @@ -19,8 +18,9 @@ public class RegisterTeleportEvent extends CustomEvent { /** * Constructor for RegisterTeleportEvent. + * * @param player Player - * @param to Location + * @param to Location */ public RegisterTeleportEvent(Player player, Location to) { this.player = player; @@ -30,32 +30,36 @@ public class RegisterTeleportEvent extends CustomEvent { /** * Method getPlayer. - - * @return Player */ + * + * @return Player + */ public Player getPlayer() { return player; } + /** + * Method getTo. + * + * @return Location + */ + public Location getTo() { + return to; + } + /** * Method setTo. + * * @param to Location */ public void setTo(Location to) { this.to = to; } - /** - * Method getTo. - - * @return Location */ - public Location getTo() { - return to; - } - /** * Method getFrom. - - * @return Location */ + * + * @return Location + */ public Location getFrom() { return from; } diff --git a/src/main/java/fr/xephi/authme/events/ResetInventoryEvent.java b/src/main/java/fr/xephi/authme/events/ResetInventoryEvent.java index af5c1ce0..75c48636 100644 --- a/src/main/java/fr/xephi/authme/events/ResetInventoryEvent.java +++ b/src/main/java/fr/xephi/authme/events/ResetInventoryEvent.java @@ -3,7 +3,6 @@ package fr.xephi.authme.events; import org.bukkit.entity.Player; /** - * * This event is call when a creative inventory is reseted. * * @author Xephi59 @@ -15,6 +14,7 @@ public class ResetInventoryEvent extends CustomEvent { /** * Constructor for ResetInventoryEvent. + * * @param player Player */ public ResetInventoryEvent(Player player) { @@ -24,14 +24,16 @@ public class ResetInventoryEvent extends CustomEvent { /** * Method getPlayer. - - * @return Player */ + * + * @return Player + */ public Player getPlayer() { return this.player; } /** * Method setPlayer. + * * @param player Player */ public void setPlayer(Player player) { diff --git a/src/main/java/fr/xephi/authme/events/RestoreInventoryEvent.java b/src/main/java/fr/xephi/authme/events/RestoreInventoryEvent.java index de2dcb17..e34d7c97 100644 --- a/src/main/java/fr/xephi/authme/events/RestoreInventoryEvent.java +++ b/src/main/java/fr/xephi/authme/events/RestoreInventoryEvent.java @@ -14,6 +14,7 @@ public class RestoreInventoryEvent extends CustomEvent { /** * Constructor for RestoreInventoryEvent. + * * @param player Player */ public RestoreInventoryEvent(Player player) { @@ -22,8 +23,9 @@ public class RestoreInventoryEvent extends CustomEvent { /** * Constructor for RestoreInventoryEvent. + * * @param player Player - * @param async boolean + * @param async boolean */ public RestoreInventoryEvent(Player player, boolean async) { super(async); @@ -32,14 +34,16 @@ public class RestoreInventoryEvent extends CustomEvent { /** * Method getPlayer. - - * @return Player */ + * + * @return Player + */ public Player getPlayer() { return this.player; } /** * Method setPlayer. + * * @param player Player */ public void setPlayer(Player player) { diff --git a/src/main/java/fr/xephi/authme/events/SpawnTeleportEvent.java b/src/main/java/fr/xephi/authme/events/SpawnTeleportEvent.java index d2379648..e83b1de9 100644 --- a/src/main/java/fr/xephi/authme/events/SpawnTeleportEvent.java +++ b/src/main/java/fr/xephi/authme/events/SpawnTeleportEvent.java @@ -4,7 +4,6 @@ import org.bukkit.Location; import org.bukkit.entity.Player; /** - * * Called if a player is teleported to a specific spawn * * @author Xephi59 @@ -19,13 +18,14 @@ public class SpawnTeleportEvent extends CustomEvent { /** * Constructor for SpawnTeleportEvent. - * @param player Player - * @param from Location - * @param to Location + * + * @param player Player + * @param from Location + * @param to Location * @param isAuthenticated boolean */ public SpawnTeleportEvent(Player player, Location from, Location to, - boolean isAuthenticated) { + boolean isAuthenticated) { this.player = player; this.from = from; this.to = to; @@ -34,40 +34,45 @@ public class SpawnTeleportEvent extends CustomEvent { /** * Method getPlayer. - - * @return Player */ + * + * @return Player + */ public Player getPlayer() { return player; } + /** + * Method getTo. + * + * @return Location + */ + public Location getTo() { + return to; + } + /** * Method setTo. + * * @param to Location */ public void setTo(Location to) { this.to = to; } - /** - * Method getTo. - - * @return Location */ - public Location getTo() { - return to; - } - /** * Method getFrom. - - * @return Location */ + * + * @return Location + */ public Location getFrom() { return from; } /** * Method isAuthenticated. - - * @return boolean */ + * + * @return boolean + */ public boolean isAuthenticated() { return isAuthenticated; } diff --git a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java index 635f0277..65e7ce71 100644 --- a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java +++ b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java @@ -1,12 +1,10 @@ package fr.xephi.authme.hooks; -import org.bukkit.entity.Player; -import org.bukkit.plugin.messaging.PluginMessageListener; - import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteStreams; - import fr.xephi.authme.AuthMe; +import org.bukkit.entity.Player; +import org.bukkit.plugin.messaging.PluginMessageListener; /** */ @@ -16,6 +14,7 @@ public class BungeeCordMessage implements PluginMessageListener { /** * Constructor for BungeeCordMessage. + * * @param plugin AuthMe */ public BungeeCordMessage(AuthMe plugin) { @@ -24,11 +23,12 @@ public class BungeeCordMessage implements PluginMessageListener { /** * Method onPluginMessageReceived. + * * @param channel String - * @param player Player + * @param player Player * @param message byte[] - - * @see org.bukkit.plugin.messaging.PluginMessageListener#onPluginMessageReceived(String, Player, byte[]) */ + * @see org.bukkit.plugin.messaging.PluginMessageListener#onPluginMessageReceived(String, Player, byte[]) + */ @Override public void onPluginMessageReceived(String channel, Player player, byte[] message) { diff --git a/src/main/java/fr/xephi/authme/hooks/EssSpawn.java b/src/main/java/fr/xephi/authme/hooks/EssSpawn.java index b13d8050..0644ee10 100644 --- a/src/main/java/fr/xephi/authme/hooks/EssSpawn.java +++ b/src/main/java/fr/xephi/authme/hooks/EssSpawn.java @@ -1,11 +1,10 @@ package fr.xephi.authme.hooks; -import java.io.File; - +import fr.xephi.authme.settings.CustomConfiguration; import org.bukkit.Bukkit; import org.bukkit.Location; -import fr.xephi.authme.settings.CustomConfiguration; +import java.io.File; /** */ @@ -21,8 +20,9 @@ public class EssSpawn extends CustomConfiguration { /** * Method getInstance. - - * @return EssSpawn */ + * + * @return EssSpawn + */ public static EssSpawn getInstance() { if (spawn == null) { spawn = new EssSpawn(); @@ -32,8 +32,9 @@ public class EssSpawn extends CustomConfiguration { /** * Method getLocation. - - * @return Location */ + * + * @return Location + */ public Location getLocation() { try { if (!this.contains("spawns.default.world")) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeBlockListener.java b/src/main/java/fr/xephi/authme/listener/AuthMeBlockListener.java index 01df809b..c025c562 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeBlockListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeBlockListener.java @@ -1,14 +1,13 @@ package fr.xephi.authme.listener; +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.util.Utils; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPlaceEvent; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.util.Utils; - /** */ public class AuthMeBlockListener implements Listener { @@ -17,6 +16,7 @@ public class AuthMeBlockListener implements Listener { /** * Constructor for AuthMeBlockListener. + * * @param instance AuthMe */ public AuthMeBlockListener(AuthMe instance) { @@ -26,6 +26,7 @@ public class AuthMeBlockListener implements Listener { /** * Method onBlockPlace. + * * @param event BlockPlaceEvent */ @EventHandler(ignoreCancelled = true) @@ -37,6 +38,7 @@ public class AuthMeBlockListener implements Listener { /** * Method onBlockBreak. + * * @param event BlockBreakEvent */ @EventHandler(ignoreCancelled = true) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java b/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java index 602ec0fb..dfba44ec 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java @@ -1,7 +1,7 @@ package fr.xephi.authme.listener; -import java.lang.reflect.Method; - +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.util.Utils; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -9,29 +9,22 @@ import org.bukkit.entity.Projectile; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.entity.EntityInteractEvent; -import org.bukkit.event.entity.EntityRegainHealthEvent; -import org.bukkit.event.entity.EntityShootBowEvent; -import org.bukkit.event.entity.EntityTargetEvent; -import org.bukkit.event.entity.FoodLevelChangeEvent; -import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.entity.*; import org.bukkit.projectiles.ProjectileSource; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.util.Utils; +import java.lang.reflect.Method; /** */ public class AuthMeEntityListener implements Listener { - public AuthMe instance; private static Method getShooter; private static boolean shooterIsProjectileSource; + public AuthMe instance; /** * Constructor for AuthMeEntityListener. + * * @param instance AuthMe */ public AuthMeEntityListener(AuthMe instance) { @@ -45,6 +38,7 @@ public class AuthMeEntityListener implements Listener { /** * Method onEntityDamage. + * * @param event EntityDamageEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -65,6 +59,7 @@ public class AuthMeEntityListener implements Listener { /** * Method onEntityTarget. + * * @param event EntityTargetEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -84,6 +79,7 @@ public class AuthMeEntityListener implements Listener { /** * Method onDmg. + * * @param event EntityDamageByEntityEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -103,6 +99,7 @@ public class AuthMeEntityListener implements Listener { /** * Method onFoodLevelChange. + * * @param event FoodLevelChangeEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -121,6 +118,7 @@ public class AuthMeEntityListener implements Listener { /** * Method entityRegainHealthEvent. + * * @param event EntityRegainHealthEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -140,6 +138,7 @@ public class AuthMeEntityListener implements Listener { /** * Method onEntityInteract. + * * @param event EntityInteractEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @@ -158,6 +157,7 @@ public class AuthMeEntityListener implements Listener { /** * Method onLowestEntityInteract. + * * @param event EntityInteractEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -175,8 +175,10 @@ public class AuthMeEntityListener implements Listener { } // TODO: Need to check this, player can't throw snowball but the item is taken. + /** * Method onProjectileLaunch. + * * @param event ProjectileLaunchEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -213,6 +215,7 @@ public class AuthMeEntityListener implements Listener { /** * Method onShoot. + * * @param event EntityShootBowEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java b/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java index affc050d..b651af60 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java @@ -16,25 +16,23 @@ */ package fr.xephi.authme.listener; -import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; -import java.util.Collections; -import java.util.logging.Level; - -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.settings.Settings; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Collections; +import java.util.logging.Level; /** */ @@ -48,6 +46,7 @@ public class AuthMeInventoryPacketAdapter extends PacketAdapter { /** * Constructor for AuthMeInventoryPacketAdapter. + * * @param plugin AuthMe */ public AuthMeInventoryPacketAdapter(AuthMe plugin) { @@ -56,9 +55,10 @@ public class AuthMeInventoryPacketAdapter extends PacketAdapter { /** * Method onPacketSending. + * * @param packetEvent PacketEvent - - * @see com.comphenix.protocol.events.PacketListener#onPacketSending(PacketEvent) */ + * @see com.comphenix.protocol.events.PacketListener#onPacketSending(PacketEvent) + */ @Override public void onPacketSending(PacketEvent packetEvent) { Player player = packetEvent.getPlayer(); @@ -77,6 +77,7 @@ public class AuthMeInventoryPacketAdapter extends PacketAdapter { /** * Method sendInventoryPacket. + * * @param player Player */ public void sendInventoryPacket(Player player) { diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index ad73df05..9f17421f 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -1,12 +1,18 @@ package fr.xephi.authme.listener; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.PatternSyntaxException; - +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.cache.limbo.LimboCache; +import fr.xephi.authme.cache.limbo.LimboPlayer; import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.settings.Messages; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.GeoLiteAPI; +import fr.xephi.authme.util.Utils; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; @@ -19,52 +25,28 @@ import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryOpenEvent; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.AsyncPlayerPreLoginEvent; -import org.bukkit.event.player.PlayerBedEnterEvent; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.event.player.PlayerDropItemEvent; -import org.bukkit.event.player.PlayerFishEvent; -import org.bukkit.event.player.PlayerGameModeChangeEvent; -import org.bukkit.event.player.PlayerInteractEntityEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerItemConsumeEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerKickEvent; -import org.bukkit.event.player.PlayerLoginEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerPickupItemEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerRespawnEvent; -import org.bukkit.event.player.PlayerShearEntityEvent; +import org.bukkit.event.player.*; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; - -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.cache.auth.PlayerAuth; -import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.cache.limbo.LimboCache; -import fr.xephi.authme.cache.limbo.LimboPlayer; -import fr.xephi.authme.settings.Messages; -import fr.xephi.authme.settings.Settings; -import fr.xephi.authme.util.Utils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.PatternSyntaxException; /** */ public class AuthMePlayerListener implements Listener { - public AuthMe plugin; - private Messages m = Messages.getInstance(); - public static ConcurrentHashMap gameMode = new ConcurrentHashMap<>(); public static ConcurrentHashMap joinMessage = new ConcurrentHashMap<>(); public static ConcurrentHashMap causeByAuthMe = new ConcurrentHashMap<>(); + public AuthMe plugin; + private Messages m = Messages.getInstance(); private List antibot = new ArrayList<>(); /** * Constructor for AuthMePlayerListener. + * * @param plugin AuthMe */ public AuthMePlayerListener(AuthMe plugin) { @@ -73,6 +55,7 @@ public class AuthMePlayerListener implements Listener { /** * Method handleChat. + * * @param event AsyncPlayerChatEvent */ private void handleChat(AsyncPlayerChatEvent event) { @@ -99,6 +82,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerCommandPreprocess. + * * @param event PlayerCommandPreprocessEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -115,6 +99,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerNormalChat. + * * @param event AsyncPlayerChatEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL) @@ -124,6 +109,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerHighChat. + * * @param event AsyncPlayerChatEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) @@ -133,6 +119,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerChat. + * * @param event AsyncPlayerChatEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) @@ -142,6 +129,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerHighestChat. + * * @param event AsyncPlayerChatEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @@ -151,6 +139,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerEarlyChat. + * * @param event AsyncPlayerChatEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -160,6 +149,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerLowChat. + * * @param event AsyncPlayerChatEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) @@ -169,6 +159,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerMove. + * * @param event PlayerMoveEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) @@ -207,10 +198,11 @@ public class AuthMePlayerListener implements Listener { /** * Method checkAntiBotMod. + * * @param player Player */ private void checkAntiBotMod(final Player player) { - if (plugin.delayedAntiBot || plugin.antibotMod) + if (plugin.delayedAntiBot || plugin.antiBotMod) return; if (plugin.getPermissionsManager().hasPermission(player, "authme.bypassantibot")) return; @@ -222,7 +214,7 @@ public class AuthMePlayerListener implements Listener { @Override public void run() { - if (plugin.antibotMod) { + if (plugin.antiBotMod) { plugin.switchAntiBotMod(false); antibot.clear(); for (String s : m.send("antibot_auto_disabled")) @@ -244,6 +236,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerJoin. + * * @param event PlayerJoinEvent */ @EventHandler(priority = EventPriority.HIGHEST) @@ -267,7 +260,7 @@ public class AuthMePlayerListener implements Listener { @Override public void run() { if (delay) - joinMessage.put(name, joinMsg); + joinMessage.put(name, joinMsg); plugin.management.performJoin(player); } }); @@ -275,6 +268,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPreLogin. + * * @param event AsyncPlayerPreLoginEvent */ @EventHandler(priority = EventPriority.HIGHEST) @@ -306,6 +300,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerLogin. + * * @param event PlayerLoginEvent */ @EventHandler(priority = EventPriority.HIGHEST) @@ -329,7 +324,7 @@ public class AuthMePlayerListener implements Listener { boolean isAuthAvailable = plugin.database.isAuthAvailable(name); if (!Settings.countriesBlacklist.isEmpty() && !isAuthAvailable && !permsMan.hasPermission(player, "authme.bypassantibot")) { - String code = Utils.getCountryCode(event.getAddress().getHostAddress()); + String code = GeoLiteAPI.getCountryCode(event.getAddress().getHostAddress()); if (((code == null) || Settings.countriesBlacklist.contains(code))) { event.setKickMessage(m.send("country_banned")[0]); event.setResult(PlayerLoginEvent.Result.KICK_OTHER); @@ -337,7 +332,7 @@ public class AuthMePlayerListener implements Listener { } } if (Settings.enableProtection && !Settings.countries.isEmpty() && !isAuthAvailable && !permsMan.hasPermission(player, "authme.bypassantibot")) { - String code = Utils.getCountryCode(event.getAddress().getHostAddress()); + String code = GeoLiteAPI.getCountryCode(event.getAddress().getHostAddress()); if (((code == null) || !Settings.countries.contains(code))) { event.setKickMessage(m.send("country_banned")[0]); event.setResult(PlayerLoginEvent.Result.KICK_OTHER); @@ -429,6 +424,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerQuit. + * * @param event PlayerQuitEvent */ @EventHandler(priority = EventPriority.MONITOR) @@ -448,6 +444,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerKick. + * * @param event PlayerKickEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) @@ -467,6 +464,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerPickupItem. + * * @param event PlayerPickupItemEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @@ -478,6 +476,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerInteract. + * * @param event PlayerInteractEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @@ -490,6 +489,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerConsumeItem. + * * @param event PlayerItemConsumeEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL) @@ -501,6 +501,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerInventoryOpen. + * * @param event InventoryOpenEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @@ -525,6 +526,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerInventoryClick. + * * @param event InventoryClickEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -540,6 +542,7 @@ public class AuthMePlayerListener implements Listener { /** * Method playerHitPlayerEvent. + * * @param event EntityDamageByEntityEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -556,6 +559,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerInteractEntity. + * * @param event PlayerInteractEntityEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -568,6 +572,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerDropItem. + * * @param event PlayerDropItemEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -579,6 +584,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerBedEnter. + * * @param event PlayerBedEnterEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -590,6 +596,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onSignChange. + * * @param event SignChangeEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -601,6 +608,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerRespawn. + * * @param event PlayerRespawnEvent */ @EventHandler(priority = EventPriority.HIGHEST) @@ -621,6 +629,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerGameModeChange. + * * @param event PlayerGameModeChangeEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @@ -643,6 +652,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerShear. + * * @param event PlayerShearEntityEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL) @@ -655,6 +665,7 @@ public class AuthMePlayerListener implements Listener { /** * Method onPlayerFish. + * * @param event PlayerFishEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener16.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener16.java index 0f7a47b6..0dd34dae 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener16.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener16.java @@ -1,14 +1,13 @@ package fr.xephi.authme.listener; +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.util.Utils; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerEditBookEvent; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.util.Utils; - /** */ public class AuthMePlayerListener16 implements Listener { @@ -17,6 +16,7 @@ public class AuthMePlayerListener16 implements Listener { /** * Constructor for AuthMePlayerListener16. + * * @param plugin AuthMe */ public AuthMePlayerListener16(AuthMe plugin) { @@ -25,6 +25,7 @@ public class AuthMePlayerListener16 implements Listener { /** * Method onPlayerEditBook. + * * @param event PlayerEditBookEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener18.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener18.java index 48815929..ac1dc42b 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener18.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener18.java @@ -1,14 +1,13 @@ package fr.xephi.authme.listener; +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.util.Utils; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerInteractAtEntityEvent; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.util.Utils; - /** */ public class AuthMePlayerListener18 implements Listener { @@ -17,6 +16,7 @@ public class AuthMePlayerListener18 implements Listener { /** * Constructor for AuthMePlayerListener18. + * * @param plugin AuthMe */ public AuthMePlayerListener18(AuthMe plugin) { @@ -25,6 +25,7 @@ public class AuthMePlayerListener18 implements Listener { /** * Method onPlayerInteractAtEntity. + * * @param event PlayerInteractAtEntityEvent */ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java index ec17eaaf..51670d37 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java @@ -1,17 +1,16 @@ package fr.xephi.authme.listener; +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.settings.Messages; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.GeoLiteAPI; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.event.server.PluginEnableEvent; import org.bukkit.event.server.ServerListPingEvent; - -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.settings.Messages; -import fr.xephi.authme.settings.Settings; -import fr.xephi.authme.util.Utils; import org.bukkit.plugin.Plugin; /** @@ -23,6 +22,7 @@ public class AuthMeServerListener implements Listener { /** * Constructor for AuthMeServerListener. + * * @param plugin AuthMe */ public AuthMeServerListener(AuthMe plugin) { @@ -31,6 +31,7 @@ public class AuthMeServerListener implements Listener { /** * Method onServerPing. + * * @param event ServerListPingEvent */ @EventHandler(priority = EventPriority.HIGHEST) @@ -40,10 +41,10 @@ public class AuthMeServerListener implements Listener { if (Settings.countries.isEmpty()) return; if (!Settings.countriesBlacklist.isEmpty()) { - if (Settings.countriesBlacklist.contains(Utils.getCountryCode(event.getAddress().getHostAddress()))) + if (Settings.countriesBlacklist.contains(GeoLiteAPI.getCountryCode(event.getAddress().getHostAddress()))) event.setMotd(m.send("country_banned")[0]); } - if (Settings.countries.contains(Utils.getCountryCode(event.getAddress().getHostAddress()))) { + if (Settings.countries.contains(GeoLiteAPI.getCountryCode(event.getAddress().getHostAddress()))) { event.setMotd(plugin.getServer().getMotd()); } else { event.setMotd(m.send("country_banned")[0]); @@ -52,6 +53,7 @@ public class AuthMeServerListener implements Listener { /** * Method onPluginDisable. + * * @param event PluginDisableEvent */ @EventHandler(priority = EventPriority.HIGHEST) @@ -60,11 +62,11 @@ public class AuthMeServerListener implements Listener { Plugin pluginInstance = event.getPlugin(); // Make sure the plugin instance isn't null - if(pluginInstance == null) + if (pluginInstance == null) return; // Make sure it's not this plugin itself - if(pluginInstance.equals(this.plugin)) + if (pluginInstance.equals(this.plugin)) return; // Call the onPluginDisable method in the permissions manager @@ -98,6 +100,7 @@ public class AuthMeServerListener implements Listener { /** * Method onPluginEnable. + * * @param event PluginEnableEvent */ @EventHandler(priority = EventPriority.HIGHEST) diff --git a/src/main/java/fr/xephi/authme/modules/Module.java b/src/main/java/fr/xephi/authme/modules/Module.java index f6263ad2..f64349cf 100644 --- a/src/main/java/fr/xephi/authme/modules/Module.java +++ b/src/main/java/fr/xephi/authme/modules/Module.java @@ -4,6 +4,26 @@ package fr.xephi.authme.modules; */ public abstract class Module { + /** + * Method getName. + * + * @return String + */ + public abstract String getName(); + + /** + * Method getType. + * + * @return ModuleType + */ + public abstract ModuleType getType(); + + public void load() { + } + + public void unload() { + } + /** */ enum ModuleType { @@ -15,22 +35,4 @@ public abstract class Module { EMAILS, CUSTOM } - - /** - * Method getName. - - * @return String */ - public abstract String getName(); - - /** - * Method getType. - - * @return ModuleType */ - public abstract ModuleType getType(); - - public void load() { - } - - public void unload() { - } } diff --git a/src/main/java/fr/xephi/authme/modules/ModuleManager.java b/src/main/java/fr/xephi/authme/modules/ModuleManager.java index e83710f8..015c7e6c 100644 --- a/src/main/java/fr/xephi/authme/modules/ModuleManager.java +++ b/src/main/java/fr/xephi/authme/modules/ModuleManager.java @@ -1,5 +1,9 @@ package fr.xephi.authme.modules; +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.settings.Settings; + import java.io.File; import java.io.IOException; import java.net.URL; @@ -11,10 +15,6 @@ import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.settings.Settings; - /** */ public class ModuleManager { @@ -23,6 +23,7 @@ public class ModuleManager { /** * Constructor for ModuleManager. + * * @param plugin AuthMe */ public ModuleManager(AuthMe plugin) { @@ -30,9 +31,10 @@ public class ModuleManager { /** * Method isModuleEnabled. + * * @param name String - - * @return boolean */ + * @return boolean + */ public boolean isModuleEnabled(String name) { for (Module m : modules) { if (m.getName().equalsIgnoreCase(name)) @@ -43,9 +45,10 @@ public class ModuleManager { /** * Method isModuleEnabled. + * * @param type Module.ModuleType - - * @return boolean */ + * @return boolean + */ public boolean isModuleEnabled(Module.ModuleType type) { for (Module m : modules) { if (m.getType() == type) @@ -56,9 +59,10 @@ public class ModuleManager { /** * Method getModule. + * * @param name String - - * @return Module */ + * @return Module + */ public Module getModule(String name) { for (Module m : modules) { if (m.getName().equalsIgnoreCase(name)) @@ -69,9 +73,10 @@ public class ModuleManager { /** * Method getModule. + * * @param type Module.ModuleType - - * @return Module */ + * @return Module + */ public Module getModule(Module.ModuleType type) { for (Module m : modules) { if (m.getType() == type) @@ -82,8 +87,9 @@ public class ModuleManager { /** * Method loadModules. - - * @return int */ + * + * @return int + */ public int loadModules() { File dir = Settings.MODULE_FOLDER; int count = 0; @@ -142,13 +148,14 @@ public class ModuleManager { return count; } - public void reloadModules(){ + public void reloadModules() { unloadModules(); loadModules(); } /** * Method unloadModule. + * * @param name String */ public void unloadModule(String name) { diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index a2af1ba4..086d158f 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -29,10 +29,10 @@ import java.util.logging.Logger; /** * PermissionsManager. - * + *

* A permissions manager, to manage and use various permissions systems. * This manager supports dynamic plugin hooking and various other features. - * + *

* Written by Tim Visée. * * @author Tim Visée, http://timvisee.com @@ -40,6 +40,10 @@ import java.util.logging.Logger; */ public class PermissionsManager { + /** + * Vault instance. + */ + public Permission vaultPerms = null; /** * Server instance. */ @@ -52,38 +56,29 @@ public class PermissionsManager { * Logger instance. */ private Logger log; - /** * Type of permissions system that is currently used. */ private PermissionsSystemType permsType = PermissionsSystemType.NONE; - /** * Essentials group manager instance. */ private GroupManager groupManagerPerms; - /** * Permissions manager instance for the legacy permissions system. */ private PermissionHandler defaultPerms; - /** * zPermissions service instance. */ private ZPermissionsService zPermissionsService; - /** - * Vault instance. - */ - public Permission vaultPerms = null; - /** * Constructor. * * @param server Server instance * @param plugin Plugin instance - * @param log Logger + * @param log Logger */ public PermissionsManager(Server server, Plugin plugin, Logger log) { this.server = server; @@ -124,16 +119,16 @@ public class PermissionsManager { // PermissionsEx, check if it's available try { Plugin pex = pm.getPlugin("PermissionsEx"); - if(pex != null) { + if (pex != null) { PermissionManager pexPerms = PermissionsEx.getPermissionManager(); - if(pexPerms != null) { + if (pexPerms != null) { permsType = PermissionsSystemType.PERMISSIONS_EX; System.out.println("[" + plugin.getName() + "] Hooked into PermissionsEx!"); return permsType; } } - } catch(Exception ex) { + } catch (Exception ex) { // An error occurred, show a warning message System.out.println("[" + plugin.getName() + "] Error while hooking into PermissionsEx!"); } @@ -141,12 +136,12 @@ public class PermissionsManager { // PermissionsBukkit, check if it's available try { Plugin bukkitPerms = pm.getPlugin("PermissionsBukkit"); - if(bukkitPerms != null) { + if (bukkitPerms != null) { permsType = PermissionsSystemType.PERMISSIONS_BUKKIT; System.out.println("[" + plugin.getName() + "] Hooked into PermissionsBukkit!"); return permsType; } - } catch(Exception ex) { + } catch (Exception ex) { // An error occurred, show a warning message System.out.println("[" + plugin.getName() + "] Error while hooking into PermissionsBukkit!"); } @@ -154,12 +149,12 @@ public class PermissionsManager { // bPermissions, check if it's available try { Plugin bPerms = pm.getPlugin("bPermissions"); - if(bPerms != null) { + if (bPerms != null) { permsType = PermissionsSystemType.B_PERMISSIONS; System.out.println("[" + plugin.getName() + "] Hooked into bPermissions!"); return permsType; } - } catch(Exception ex) { + } catch (Exception ex) { // An error occurred, show a warning message System.out.println("[" + plugin.getName() + "] Error while hooking into bPermissions!"); } @@ -167,13 +162,13 @@ public class PermissionsManager { // Essentials Group Manager, check if it's available try { final Plugin groupManagerPlugin = pm.getPlugin("GroupManager"); - if(groupManagerPlugin != null && groupManagerPlugin.isEnabled()) { + if (groupManagerPlugin != null && groupManagerPlugin.isEnabled()) { permsType = PermissionsSystemType.ESSENTIALS_GROUP_MANAGER; groupManagerPerms = (GroupManager) groupManagerPlugin; System.out.println("[" + plugin.getName() + "] Hooked into Essentials Group Manager!"); return permsType; } - } catch(Exception ex) { + } catch (Exception ex) { // An error occurred, show a warning message System.out.println("[" + plugin.getName() + "] Error while hooking into Essentials Group Manager!"); } @@ -181,15 +176,15 @@ public class PermissionsManager { // zPermissions, check if it's available try { Plugin zPerms = pm.getPlugin("zPermissions"); - if(zPerms != null) { + if (zPerms != null) { zPermissionsService = Bukkit.getServicesManager().load(ZPermissionsService.class); - if(zPermissionsService != null) { + if (zPermissionsService != null) { permsType = PermissionsSystemType.Z_PERMISSIONS; System.out.println("[" + plugin.getName() + "] Hooked into zPermissions!"); return permsType; } } - } catch(Exception ex) { + } catch (Exception ex) { // An error occurred, show a warning message System.out.println("[" + plugin.getName() + "] Error while hooking into zPermissions!"); } @@ -197,11 +192,11 @@ public class PermissionsManager { // Vault, check if it's available try { final Plugin vaultPlugin = pm.getPlugin("Vault"); - if(vaultPlugin != null && vaultPlugin.isEnabled()) { + if (vaultPlugin != null && vaultPlugin.isEnabled()) { RegisteredServiceProvider permissionProvider = this.server.getServicesManager().getRegistration(Permission.class); - if(permissionProvider != null) { + if (permissionProvider != null) { vaultPerms = permissionProvider.getProvider(); - if(vaultPerms.isEnabled()) { + if (vaultPerms.isEnabled()) { permsType = PermissionsSystemType.VAULT; System.out.println("[" + plugin.getName() + "] Hooked into Vault Permissions!"); return permsType; @@ -210,7 +205,7 @@ public class PermissionsManager { } } } - } catch(Exception ex) { + } catch (Exception ex) { // An error occurred, show a warning message System.out.println("[" + plugin.getName() + "] Error while hooking into Vault Permissions!"); } @@ -218,13 +213,13 @@ public class PermissionsManager { // Permissions, check if it's available try { Plugin testPerms = pm.getPlugin("Permissions"); - if(testPerms != null) { + if (testPerms != null) { permsType = PermissionsSystemType.PERMISSIONS; this.defaultPerms = ((Permissions) testPerms).getHandler(); System.out.println("[" + plugin.getName() + "] Hooked into Permissions!"); return PermissionsSystemType.PERMISSIONS; } - } catch(Exception ex) { + } catch (Exception ex) { // An error occurred, show a warning message System.out.println("[" + plugin.getName() + "] Error while hooking into Permissions!"); } @@ -271,7 +266,7 @@ public class PermissionsManager { String pluginName = plugin.getName(); // Check if any known permissions system is enabling - if(pluginName.equals("PermissionsEx") || pluginName.equals("PermissionsBukkit") || + if (pluginName.equals("PermissionsEx") || pluginName.equals("PermissionsBukkit") || pluginName.equals("bPermissions") || pluginName.equals("GroupManager") || pluginName.equals("zPermissions") || pluginName.equals("Vault") || pluginName.equals("Permissions")) { @@ -291,7 +286,7 @@ public class PermissionsManager { String pluginName = plugin.getName(); // Is the WorldGuard plugin disabled - if(pluginName.equals("PermissionsEx") || pluginName.equals("PermissionsBukkit") || + if (pluginName.equals("PermissionsEx") || pluginName.equals("PermissionsBukkit") || pluginName.equals("bPermissions") || pluginName.equals("GroupManager") || pluginName.equals("zPermissions") || pluginName.equals("Vault") || pluginName.equals("Permissions")) { @@ -321,9 +316,8 @@ public class PermissionsManager { /** * Check if the player has permission. If no permissions system is used, the player has to be OP. * - * @param player The player. + * @param player The player. * @param permsNode Permissions node. - * * @return True if the player has permission. */ public boolean hasPermission(Player player, String permsNode) { @@ -333,18 +327,17 @@ public class PermissionsManager { /** * Check if a player has permission. * - * @param player The player. + * @param player The player. * @param permsNode The permission node. - * @param def Default returned if no permissions system is used. - * + * @param def Default returned if no permissions system is used. * @return True if the player has permission. */ public boolean hasPermission(Player player, String permsNode, boolean def) { // If no permissions system is used, return the default value - if(!isEnabled()) + if (!isEnabled()) return def; - switch(this.permsType) { + switch (this.permsType) { case PERMISSIONS_EX: // Permissions Ex PermissionUser user = PermissionsEx.getUser(player); @@ -367,7 +360,7 @@ public class PermissionsManager { // zPermissions @SuppressWarnings("deprecation") Map perms = zPermissionsService.getPlayerPermissions(player.getWorld().getName(), null, player.getName()); - if(perms.containsKey(permsNode)) + if (perms.containsKey(permsNode)) return perms.get(permsNode); else return def; @@ -398,10 +391,10 @@ public class PermissionsManager { */ public boolean hasGroupSupport() { // If no permissions system is used, return false - if(!isEnabled()) + if (!isEnabled()) return false; - switch(this.permsType) { + switch (this.permsType) { case PERMISSIONS_EX: case PERMISSIONS_BUKKIT: case B_PERMISSIONS: @@ -432,16 +425,15 @@ public class PermissionsManager { * Get the permission groups of a player, if available. * * @param player The player. - * * @return Permission groups, or an empty list if this feature is not supported. */ @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public List getGroups(Player player) { // If no permissions system is used, return an empty list - if(!isEnabled()) + if (!isEnabled()) return new ArrayList<>(); - switch(this.permsType) { + switch (this.permsType) { case PERMISSIONS_EX: // Permissions Ex PermissionUser user = PermissionsEx.getUser(player); @@ -459,7 +451,7 @@ public class PermissionsManager { case ESSENTIALS_GROUP_MANAGER: // Essentials Group Manager final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); - if(handler == null) + if (handler == null) return new ArrayList<>(); return Arrays.asList(handler.getGroups(player.getName())); @@ -477,7 +469,7 @@ public class PermissionsManager { List groups = new ArrayList<>(); // Get the groups and add each to the list - for(Group group : this.defaultPerms.getGroups(player.getName())) + for (Group group : this.defaultPerms.getGroups(player.getName())) groups.add(group.getName()); // Return the groups @@ -497,16 +489,15 @@ public class PermissionsManager { * Get the primary group of a player, if available. * * @param player The player. - * * @return The name of the primary permission group. Or null. */ @SuppressWarnings("deprecation") public String getPrimaryGroup(Player player) { // If no permissions system is used, return an empty list - if(!isEnabled()) + if (!isEnabled()) return null; - switch(this.permsType) { + switch (this.permsType) { case PERMISSIONS_EX: case PERMISSIONS_BUKKIT: case B_PERMISSIONS: @@ -515,7 +506,7 @@ public class PermissionsManager { List groups = getGroups(player); // Make sure there is any group available, or return null - if(groups.size() == 0) + if (groups.size() == 0) return null; // Return the first group @@ -524,7 +515,7 @@ public class PermissionsManager { case ESSENTIALS_GROUP_MANAGER: // Essentials Group Manager final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); - if(handler == null) + if (handler == null) return null; return handler.getGroup(player.getName()); @@ -549,18 +540,17 @@ public class PermissionsManager { /** * Check whether the player is in the specified group. * - * @param player The player. + * @param player The player. * @param groupName The group name. - * * @return True if the player is in the specified group, false otherwise. * False is also returned if groups aren't supported by the used permissions system. */ public boolean inGroup(Player player, String groupName) { // If no permissions system is used, return false - if(!isEnabled()) + if (!isEnabled()) return false; - switch(this.permsType) { + switch (this.permsType) { case PERMISSIONS_EX: // Permissions Ex PermissionUser user = PermissionsEx.getUser(player); @@ -572,8 +562,8 @@ public class PermissionsManager { List groupNames = getGroups(player); // Check whether the list contains the group name, return the result - for(String entry : groupNames) - if(entry.equals(groupName)) + for (String entry : groupNames) + if (entry.equals(groupName)) return true; return false; @@ -607,19 +597,18 @@ public class PermissionsManager { /** * Add the permission group of a player, if supported. * - * @param player The player + * @param player The player * @param groupName The name of the group. - * * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ public boolean addGroup(Player player, String groupName) { // If no permissions system is used, return false - if(!isEnabled()) + if (!isEnabled()) return false; // Set the group the proper way - switch(this.permsType) { + switch (this.permsType) { case PERMISSIONS_EX: // Permissions Ex PermissionUser user = PermissionsEx.getUser(player); @@ -669,21 +658,20 @@ public class PermissionsManager { /** * Add the permission groups of a player, if supported. * - * @param player The player + * @param player The player * @param groupNames The name of the groups to add. - * * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ public boolean addGroups(Player player, List groupNames) { // If no permissions system is used, return false - if(!isEnabled()) + if (!isEnabled()) return false; // Add each group to the user boolean result = true; - for(String groupName : groupNames) - if(!addGroup(player, groupName)) + for (String groupName : groupNames) + if (!addGroup(player, groupName)) result = false; // Return the result @@ -693,19 +681,18 @@ public class PermissionsManager { /** * Remove the permission group of a player, if supported. * - * @param player The player + * @param player The player * @param groupName The name of the group. - * * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ public boolean removeGroup(Player player, String groupName) { // If no permissions system is used, return false - if(!isEnabled()) + if (!isEnabled()) return false; // Set the group the proper way - switch(this.permsType) { + switch (this.permsType) { case PERMISSIONS_EX: // Permissions Ex PermissionUser user = PermissionsEx.getUser(player); @@ -755,21 +742,20 @@ public class PermissionsManager { /** * Remove the permission groups of a player, if supported. * - * @param player The player + * @param player The player * @param groupNames The name of the groups to add. - * * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ public boolean removeGroups(Player player, List groupNames) { // If no permissions system is used, return false - if(!isEnabled()) + if (!isEnabled()) return false; // Add each group to the user boolean result = true; - for(String groupName : groupNames) - if(!removeGroup(player, groupName)) + for (String groupName : groupNames) + if (!removeGroup(player, groupName)) result = false; // Return the result @@ -780,15 +766,14 @@ public class PermissionsManager { * Set the permission group of a player, if supported. * This clears the current groups of the player. * - * @param player The player + * @param player The player * @param groupName The name of the group. - * * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ public boolean setGroup(Player player, String groupName) { // If no permissions system is used, return false - if(!isEnabled()) + if (!isEnabled()) return false; // Create a list of group names @@ -796,7 +781,7 @@ public class PermissionsManager { groupNames.add(groupName); // Set the group the proper way - switch(this.permsType) { + switch (this.permsType) { case PERMISSIONS_EX: // Permissions Ex PermissionUser user = PermissionsEx.getUser(player); @@ -850,29 +835,28 @@ public class PermissionsManager { * Set the permission groups of a player, if supported. * This clears the current groups of the player. * - * @param player The player + * @param player The player * @param groupNames The name of the groups to set. - * * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ public boolean setGroups(Player player, List groupNames) { // If no permissions system is used or if there's no group supplied, return false - if(!isEnabled() || groupNames.size() <= 0) + if (!isEnabled() || groupNames.size() <= 0) return false; // Set the main group - if(!setGroup(player, groupNames.get(0))) + if (!setGroup(player, groupNames.get(0))) return false; // Add the rest of the groups boolean result = true; - for(int i = 1; i < groupNames.size(); i++) { + for (int i = 1; i < groupNames.size(); i++) { // Get the group name String groupName = groupNames.get(0); // Add this group - if(!addGroup(player, groupName)) + if (!addGroup(player, groupName)) result = false; } @@ -886,13 +870,12 @@ public class PermissionsManager { * in it's primary group. All the subgroups are removed just fine. * * @param player The player to remove all groups from. - * * @return True if succeed, false otherwise. * False will also be returned if this feature isn't supported for the used permissions system. */ public boolean removeAllGroups(Player player) { // If no permissions system is used, return false - if(!isEnabled()) + if (!isEnabled()) return false; // Get a list of current groups diff --git a/src/main/java/fr/xephi/authme/process/Management.java b/src/main/java/fr/xephi/authme/process/Management.java index 1030eedd..75c0899c 100644 --- a/src/main/java/fr/xephi/authme/process/Management.java +++ b/src/main/java/fr/xephi/authme/process/Management.java @@ -1,8 +1,5 @@ package fr.xephi.authme.process; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitScheduler; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.process.email.AsyncChangeEmail; import fr.xephi.authme.process.join.AsyncronousJoin; @@ -13,20 +10,22 @@ import fr.xephi.authme.process.register.AsyncRegister; import fr.xephi.authme.process.unregister.AsyncronousUnregister; import fr.xephi.authme.security.RandomString; import fr.xephi.authme.settings.Settings; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitScheduler; /** - * @author Gabriele * @version $Revision: 1.0 $ */ public class Management { + public static RandomString rdm = new RandomString(Settings.captchaLength); private final AuthMe plugin; private final BukkitScheduler sched; - public static RandomString rdm = new RandomString(Settings.captchaLength); /** * Constructor for Management. + * * @param plugin AuthMe */ public Management(AuthMe plugin) { @@ -36,8 +35,9 @@ public class Management { /** * Method performLogin. - * @param player Player - * @param password String + * + * @param player Player + * @param password String * @param forceLogin boolean */ public void performLogin(final Player player, final String password, final boolean forceLogin) { @@ -52,6 +52,7 @@ public class Management { /** * Method performLogout. + * * @param player Player */ public void performLogout(final Player player) { @@ -66,9 +67,10 @@ public class Management { /** * Method performRegister. - * @param player Player + * + * @param player Player * @param password String - * @param email String + * @param email String */ public void performRegister(final Player player, final String password, final String email) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -82,9 +84,10 @@ public class Management { /** * Method performUnregister. - * @param player Player + * + * @param player Player * @param password String - * @param force boolean + * @param force boolean */ public void performUnregister(final Player player, final String password, final boolean force) { sched.runTaskAsynchronously(plugin, new Runnable() { @@ -98,6 +101,7 @@ public class Management { /** * Method performJoin. + * * @param player Player */ public void performJoin(final Player player) { @@ -113,6 +117,7 @@ public class Management { /** * Method performQuit. + * * @param player Player * @param isKick boolean */ @@ -129,8 +134,9 @@ public class Management { /** * Method performAddEmail. - * @param player Player - * @param newEmail String + * + * @param player Player + * @param newEmail String * @param newEmailVerify String */ public void performAddEmail(final Player player, final String newEmail, final String newEmailVerify) { @@ -144,7 +150,8 @@ public class Management { /** * Method performChangeEmail. - * @param player Player + * + * @param player Player * @param oldEmail String * @param newEmail String */ diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java index 3fddc162..e85bac0a 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java @@ -1,15 +1,14 @@ package fr.xephi.authme.process.email; -import java.util.Arrays; - -import org.bukkit.entity.Player; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; +import org.bukkit.entity.Player; + +import java.util.Arrays; /** */ @@ -24,10 +23,11 @@ public class AsyncChangeEmail { /** * Constructor for AsyncChangeEmail. - * @param player Player - * @param plugin AuthMe - * @param oldEmail String - * @param newEmail String + * + * @param player Player + * @param plugin AuthMe + * @param oldEmail String + * @param newEmail String * @param newEmailVerify String */ public AsyncChangeEmail(Player player, AuthMe plugin, String oldEmail, String newEmail, String newEmailVerify) { @@ -41,8 +41,9 @@ public class AsyncChangeEmail { /** * Constructor for AsyncChangeEmail. - * @param player Player - * @param plugin AuthMe + * + * @param player Player + * @param plugin AuthMe * @param oldEmail String * @param newEmail String */ diff --git a/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java index 87faa3d2..5bddc07d 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java @@ -1,15 +1,5 @@ package fr.xephi.authme.process.join; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitScheduler; -import org.bukkit.scheduler.BukkitTask; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; @@ -27,6 +17,15 @@ import fr.xephi.authme.task.MessageTask; import fr.xephi.authme.task.TimeoutTask; import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.Utils.GroupType; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; /** */ @@ -41,8 +40,9 @@ public class AsyncronousJoin { /** * Constructor for AsyncronousJoin. - * @param player Player - * @param plugin AuthMe + * + * @param player Player + * @param plugin AuthMe * @param database DataSource */ public AsyncronousJoin(Player player, AuthMe plugin, DataSource database) { @@ -68,7 +68,7 @@ public class AsyncronousJoin { } if (!plugin.canConnect()) { - final GameMode gM = AuthMePlayerListener.gameMode.get(name); + final GameMode gM = AuthMePlayerListener.gameMode.get(name); sched.scheduleSyncDelayedTask(plugin, new Runnable() { @Override @@ -248,15 +248,16 @@ public class AsyncronousJoin { } String[] msg = isAuthAvailable ? m.send("login_msg") : - m.send("reg_" + (Settings.emailRegistration? "email_" : "") + "msg"); + m.send("reg_" + (Settings.emailRegistration ? "email_" : "") + "msg"); BukkitTask msgTask = sched.runTaskAsynchronously(plugin, new MessageTask(plugin, name, msg, msgInterval)); LimboCache.getInstance().getLimboPlayer(name).setMessageTaskId(msgTask); } /** * Method needFirstSpawn. - - * @return boolean */ + * + * @return boolean + */ private boolean needFirstSpawn() { if (player.hasPlayedBefore()) return false; @@ -283,7 +284,8 @@ public class AsyncronousJoin { /** * Method placePlayerSafely. - * @param player Player + * + * @param player Player * @param spawnLoc Location */ private void placePlayerSafely(final Player player, final Location spawnLoc) { diff --git a/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java index e6439d79..2f75c718 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsyncronousLogin.java @@ -1,12 +1,5 @@ package fr.xephi.authme.process.login; -import java.util.Date; -import java.util.List; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitTask; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; @@ -21,11 +14,18 @@ import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.task.MessageTask; import fr.xephi.authme.util.Utils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; + +import java.util.Date; +import java.util.List; /** */ public class AsyncronousLogin { + private static RandomString rdm = new RandomString(Settings.captchaLength); protected Player player; protected String name; protected String realName; @@ -33,16 +33,16 @@ public class AsyncronousLogin { protected boolean forceLogin; private AuthMe plugin; private DataSource database; - private static RandomString rdm = new RandomString(Settings.captchaLength); private Messages m = Messages.getInstance(); /** * Constructor for AsyncronousLogin. - * @param player Player - * @param password String + * + * @param player Player + * @param password String * @param forceLogin boolean - * @param plugin AuthMe - * @param data DataSource + * @param plugin AuthMe + * @param data DataSource */ public AsyncronousLogin(Player player, String password, boolean forceLogin, AuthMe plugin, DataSource data) { @@ -57,16 +57,18 @@ public class AsyncronousLogin { /** * Method getIP. - - * @return String */ + * + * @return String + */ protected String getIP() { return plugin.getIP(player); } /** * Method needsCaptcha. - - * @return boolean */ + * + * @return boolean + */ protected boolean needsCaptcha() { if (Settings.useCaptcha) { if (!plugin.captcha.containsKey(name)) { @@ -93,8 +95,9 @@ public class AsyncronousLogin { /** * Checks the precondition for authentication (like user known) and returns * the playerAuth-State - - * @return PlayerAuth */ + * + * @return PlayerAuth + */ protected PlayerAuth preAuth() { if (PlayerCache.getInstance().isAuthenticated(name)) { m.send(player, "logged_in"); @@ -221,8 +224,9 @@ public class AsyncronousLogin { /** * Method displayOtherAccounts. + * * @param auth PlayerAuth - * @param p Player + * @param p Player */ public void displayOtherAccounts(PlayerAuth auth, Player p) { if (!Settings.displayOtherAccounts) { diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java index 2b63d4ff..8176ccb2 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java @@ -1,11 +1,5 @@ package fr.xephi.authme.process.login; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.plugin.PluginManager; -import org.bukkit.potion.PotionEffectType; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.backup.JsonCache; @@ -20,6 +14,11 @@ import fr.xephi.authme.listener.AuthMePlayerListener; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.Utils.GroupType; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.plugin.PluginManager; +import org.bukkit.potion.PotionEffectType; /** */ @@ -36,12 +35,13 @@ public class ProcessSyncronousPlayerLogin implements Runnable { /** * Constructor for ProcessSyncronousPlayerLogin. + * * @param player Player * @param plugin AuthMe - * @param data DataSource + * @param data DataSource */ public ProcessSyncronousPlayerLogin(Player player, AuthMe plugin, - DataSource data) { + DataSource data) { this.plugin = plugin; this.database = data; this.pm = plugin.getServer().getPluginManager(); @@ -54,8 +54,9 @@ public class ProcessSyncronousPlayerLogin implements Runnable { /** * Method getLimbo. - - * @return LimboPlayer */ + * + * @return LimboPlayer + */ public LimboPlayer getLimbo() { return limbo; } @@ -65,7 +66,7 @@ public class ProcessSyncronousPlayerLogin implements Runnable { } protected void restoreFlyghtState() { - if(Settings.isForceSurvivalModeEnabled) { + if (Settings.isForceSurvivalModeEnabled) { player.setAllowFlight(false); player.setFlying(false); return; @@ -117,6 +118,7 @@ public class ProcessSyncronousPlayerLogin implements Runnable { /** * Method run. + * * @see java.lang.Runnable#run() */ @Override @@ -135,8 +137,7 @@ public class ProcessSyncronousPlayerLogin implements Runnable { } else { teleportBackFromSpawn(); } - } else - if (Settings.isForceSpawnLocOnJoinEnabled && Settings.getForcedWorlds.contains(player.getWorld().getName())) { + } else if (Settings.isForceSpawnLocOnJoinEnabled && Settings.getForcedWorlds.contains(player.getWorld().getName())) { teleportToSpawn(); } else if (Settings.isSaveQuitLocationEnabled && auth.getQuitLocY() != 0) { packQuitLocation(); @@ -150,10 +151,10 @@ public class ProcessSyncronousPlayerLogin implements Runnable { } else { player.setGameMode(limbo.getGameMode()); } - + restoreFlyghtState(); - if (Settings.protectInventoryBeforeLogInEnabled && plugin.inventoryProtector != null) { + if (Settings.protectInventoryBeforeLogInEnabled && plugin.inventoryProtector != null) { restoreInventory(); } @@ -167,7 +168,7 @@ public class ProcessSyncronousPlayerLogin implements Runnable { // We can now display the join message (if delayed) String jm = AuthMePlayerListener.joinMessage.get(name); if (jm != null) { - if(!jm.isEmpty()) { + if (!jm.isEmpty()) { for (Player p : Utils.getOnlinePlayers()) { if (p.isOnline()) p.sendMessage(jm); diff --git a/src/main/java/fr/xephi/authme/process/logout/AsyncronousLogout.java b/src/main/java/fr/xephi/authme/process/logout/AsyncronousLogout.java index 6adb1452..8d614e93 100644 --- a/src/main/java/fr/xephi/authme/process/logout/AsyncronousLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/AsyncronousLogout.java @@ -1,8 +1,5 @@ package fr.xephi.authme.process.logout; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitScheduler; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; @@ -11,6 +8,8 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.Utils.GroupType; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitScheduler; /** */ @@ -25,12 +24,13 @@ public class AsyncronousLogout { /** * Constructor for AsyncronousLogout. - * @param player Player - * @param plugin AuthMe + * + * @param player Player + * @param plugin AuthMe * @param database DataSource */ public AsyncronousLogout(Player player, AuthMe plugin, - DataSource database) { + DataSource database) { this.player = player; this.plugin = plugin; this.database = database; diff --git a/src/main/java/fr/xephi/authme/process/logout/ProcessSyncronousPlayerLogout.java b/src/main/java/fr/xephi/authme/process/logout/ProcessSyncronousPlayerLogout.java index 3281d3e1..850a14d2 100644 --- a/src/main/java/fr/xephi/authme/process/logout/ProcessSyncronousPlayerLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/ProcessSyncronousPlayerLogout.java @@ -1,12 +1,5 @@ package fr.xephi.authme.process.logout; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitScheduler; -import org.bukkit.scheduler.BukkitTask; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.limbo.LimboCache; @@ -15,6 +8,12 @@ import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.task.MessageTask; import fr.xephi.authme.task.TimeoutTask; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; /** */ @@ -27,6 +26,7 @@ public class ProcessSyncronousPlayerLogout implements Runnable { /** * Constructor for ProcessSyncronousPlayerLogout. + * * @param player Player * @param plugin AuthMe */ @@ -38,6 +38,7 @@ public class ProcessSyncronousPlayerLogout implements Runnable { /** * Method run. + * * @see java.lang.Runnable#run() */ @Override diff --git a/src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java b/src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java index 5d5e1ebe..3cc273a5 100644 --- a/src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/AsyncronousQuit.java @@ -1,10 +1,5 @@ package fr.xephi.authme.process.quit; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitTask; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; @@ -14,6 +9,10 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.listener.AuthMePlayerListener; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; /** */ @@ -30,13 +29,14 @@ public class AsyncronousQuit { /** * Constructor for AsyncronousQuit. - * @param p Player - * @param plugin AuthMe + * + * @param p Player + * @param plugin AuthMe * @param database DataSource - * @param isKick boolean + * @param isKick boolean */ public AsyncronousQuit(Player p, AuthMe plugin, DataSource database, - boolean isKick) { + boolean isKick) { this.player = p; this.plugin = plugin; this.database = database; diff --git a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java index 125d3021..6f115659 100644 --- a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java @@ -1,10 +1,9 @@ package fr.xephi.authme.process.quit; -import org.bukkit.GameMode; -import org.bukkit.entity.Player; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.settings.Settings; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; /** */ @@ -18,10 +17,11 @@ public class ProcessSyncronousPlayerQuit implements Runnable { /** * Constructor for ProcessSyncronousPlayerQuit. - * @param plugin AuthMe - * @param player Player - * @param isOp boolean - * @param isFlying boolean + * + * @param plugin AuthMe + * @param player Player + * @param isOp boolean + * @param isFlying boolean * @param needToChange boolean */ public ProcessSyncronousPlayerQuit(AuthMe plugin, Player player @@ -36,6 +36,7 @@ public class ProcessSyncronousPlayerQuit implements Runnable { /** * Method run. + * * @see java.lang.Runnable#run() */ @Override diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index 15574f6c..9cbe2c83 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -1,10 +1,5 @@ package fr.xephi.authme.process.register; -import java.security.NoSuchAlgorithmException; -import java.util.Date; - -import org.bukkit.entity.Player; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; @@ -13,6 +8,10 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; +import org.bukkit.entity.Player; + +import java.security.NoSuchAlgorithmException; +import java.util.Date; /** */ @@ -28,11 +27,12 @@ public class AsyncRegister { /** * Constructor for AsyncRegister. - * @param player Player + * + * @param player Player * @param password String - * @param email String - * @param plugin AuthMe - * @param data DataSource + * @param email String + * @param plugin AuthMe + * @param data DataSource */ public AsyncRegister(Player player, String password, String email, AuthMe plugin, DataSource data) { @@ -46,17 +46,18 @@ public class AsyncRegister { /** * Method getIp. - - * @return String */ + * + * @return String + */ protected String getIp() { return plugin.getIP(player); } /** * Method preRegisterCheck. - - - * @return boolean * @throws Exception */ + * + * @return boolean * @throws Exception + */ protected boolean preRegisterCheck() throws Exception { String lowpass = password.toLowerCase(); if (PlayerCache.getInstance().isAuthenticated(name)) { @@ -113,8 +114,9 @@ public class AsyncRegister { /** * Method emailRegister. - - * @throws Exception */ + * + * @throws Exception + */ protected void emailRegister() throws Exception { if (Settings.getmaxRegPerEmail > 0) { if (!plugin.getPermissionsManager().hasPermission(player, "authme.allow2accounts") && database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) { diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java index 0908d8a4..eb7f5f20 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java @@ -1,9 +1,5 @@ package fr.xephi.authme.process.register; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitScheduler; -import org.bukkit.scheduler.BukkitTask; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.limbo.LimboCache; @@ -13,6 +9,9 @@ import fr.xephi.authme.settings.Settings; import fr.xephi.authme.task.MessageTask; import fr.xephi.authme.task.TimeoutTask; import fr.xephi.authme.util.Utils; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; /** */ @@ -25,6 +24,7 @@ public class ProcessSyncEmailRegister implements Runnable { /** * Constructor for ProcessSyncEmailRegister. + * * @param player Player * @param plugin AuthMe */ @@ -36,6 +36,7 @@ public class ProcessSyncEmailRegister implements Runnable { /** * Method run. + * * @see java.lang.Runnable#run() */ @Override @@ -54,7 +55,7 @@ public class ProcessSyncEmailRegister implements Runnable { BukkitTask id = sched.runTaskLaterAsynchronously(plugin, new TimeoutTask(plugin, name, player), time); limbo.setTimeoutTaskId(id); } - if (limbo != null){ + if (limbo != null) { limbo.getMessageTaskId().cancel(); BukkitTask nwMsg = sched.runTaskAsynchronously(plugin, new MessageTask(plugin, name, m.send("login_msg"), msgInterval)); limbo.setMessageTaskId(nwMsg); diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncronousPasswordRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncronousPasswordRegister.java index ca1a67e0..60b2865d 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncronousPasswordRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncronousPasswordRegister.java @@ -1,12 +1,5 @@ package fr.xephi.authme.process.register; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.entity.Player; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitScheduler; -import org.bukkit.scheduler.BukkitTask; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.limbo.LimboCache; @@ -18,6 +11,12 @@ import fr.xephi.authme.settings.Settings; import fr.xephi.authme.task.MessageTask; import fr.xephi.authme.task.TimeoutTask; import fr.xephi.authme.util.Utils; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; /** */ @@ -30,6 +29,7 @@ public class ProcessSyncronousPasswordRegister implements Runnable { /** * Constructor for ProcessSyncronousPasswordRegister. + * * @param player Player * @param plugin AuthMe */ @@ -53,6 +53,7 @@ public class ProcessSyncronousPasswordRegister implements Runnable { /** * Method forceLogin. + * * @param player Player */ protected void forceLogin(Player player) { @@ -76,6 +77,7 @@ public class ProcessSyncronousPasswordRegister implements Runnable { /** * Method run. + * * @see java.lang.Runnable#run() */ @Override diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsyncronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsyncronousUnregister.java index 80c34835..1dffbc1a 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsyncronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsyncronousUnregister.java @@ -1,13 +1,5 @@ package fr.xephi.authme.process.unregister; -import java.security.NoSuchAlgorithmException; - -import org.bukkit.entity.Player; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitScheduler; -import org.bukkit.scheduler.BukkitTask; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerCache; @@ -20,6 +12,13 @@ import fr.xephi.authme.task.MessageTask; import fr.xephi.authme.task.TimeoutTask; import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.Utils.GroupType; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; + +import java.security.NoSuchAlgorithmException; /** */ @@ -27,21 +26,22 @@ public class AsyncronousUnregister { protected Player player; protected String name; + protected String password; + protected boolean force; private AuthMe plugin; private Messages m = Messages.getInstance(); - protected String password; - protected boolean force; - private JsonCache playerCache; + private JsonCache playerCache; /** * Constructor for AsyncronousUnregister. - * @param player Player + * + * @param player Player * @param password String - * @param force boolean - * @param plugin AuthMe + * @param force boolean + * @param plugin AuthMe */ public AsyncronousUnregister(Player player, String password, - boolean force, AuthMe plugin) { + boolean force, AuthMe plugin) { this.player = player; this.password = password; this.force = force; @@ -52,8 +52,9 @@ public class AsyncronousUnregister { /** * Method getIp. - - * @return String */ + * + * @return String + */ protected String getIp() { return plugin.getIP(player); } diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index db3fb202..dc22a2aa 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -39,6 +39,7 @@ public enum HashAlgorithm { /** * Constructor for HashAlgorithm. + * * @param classe Class */ HashAlgorithm(Class classe) { @@ -47,8 +48,9 @@ public enum HashAlgorithm { /** * Method getclasse. - - * @return Class */ + * + * @return Class + */ public Class getclasse() { return classe; } diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 1bca5e18..453db7fb 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -1,33 +1,32 @@ package fr.xephi.authme.security; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.HashMap; - -import org.bukkit.Bukkit; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.events.PasswordEncryptionEvent; import fr.xephi.authme.security.crypts.BCRYPT; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.settings.Settings; +import org.bukkit.Bukkit; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.HashMap; /** */ public class PasswordSecurity { - private static SecureRandom rnd = new SecureRandom(); public static HashMap userSalt = new HashMap<>(); + private static SecureRandom rnd = new SecureRandom(); /** * Method createSalt. + * * @param length int - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ public static String createSalt(int length) throws NoSuchAlgorithmException { byte[] msg = new byte[40]; @@ -40,12 +39,12 @@ public class PasswordSecurity { /** * Method getHash. - * @param alg HashAlgorithm - * @param password String + * + * @param alg HashAlgorithm + * @param password String * @param playerName String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ public static String getHash(HashAlgorithm alg, String password, String playerName) throws NoSuchAlgorithmException { EncryptionMethod method; @@ -143,12 +142,12 @@ public class PasswordSecurity { /** * Method comparePasswordWithHash. - * @param password String - * @param hash String + * + * @param password String + * @param hash String * @param playerName String - - - * @return boolean * @throws NoSuchAlgorithmException */ + * @return boolean * @throws NoSuchAlgorithmException + */ public static boolean comparePasswordWithHash(String password, String hash, String playerName) throws NoSuchAlgorithmException { HashAlgorithm algo = Settings.getPasswordHash; @@ -181,12 +180,12 @@ public class PasswordSecurity { /** * Method compareWithAllEncryptionMethod. - * @param password String - * @param hash String + * + * @param password String + * @param hash String * @param playerName String - - - * @return boolean * @throws NoSuchAlgorithmException */ + * @return boolean * @throws NoSuchAlgorithmException + */ private static boolean compareWithAllEncryptionMethod(String password, String hash, String playerName) throws NoSuchAlgorithmException { for (HashAlgorithm algo : HashAlgorithm.values()) { diff --git a/src/main/java/fr/xephi/authme/security/RandomString.java b/src/main/java/fr/xephi/authme/security/RandomString.java index fec71b71..d9f58bfc 100644 --- a/src/main/java/fr/xephi/authme/security/RandomString.java +++ b/src/main/java/fr/xephi/authme/security/RandomString.java @@ -4,7 +4,6 @@ import java.util.Calendar; import java.util.Random; /** - * * @author Xephi59 * @version $Revision: 1.0 $ */ @@ -25,6 +24,7 @@ public class RandomString { /** * Constructor for RandomString. + * * @param length int */ public RandomString(int length) { @@ -36,8 +36,9 @@ public class RandomString { /** * Method nextString. - - * @return String */ + * + * @return String + */ public String nextString() { for (int idx = 0; idx < buf.length; ++idx) buf[idx] = chars[random.nextInt(chars.length)]; diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java index 0eb79b33..b0c1adbe 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -92,8 +92,6 @@ public class BCRYPT implements EncryptionMethod { * * @param d the byte array to encode * @param len the number of bytes to encode - - * @return base64-encoded string * @throws IllegalArgumentException if the length is invalid * @throws IllegalArgumentException */ private static String encode_base64(byte d[], int len) @@ -134,8 +132,8 @@ public class BCRYPT implements EncryptionMethod { * range-checking againt conversion table * * @param x the base64-encoded value - - * @return the decoded value of x */ + * @return the decoded value of x + */ private static byte char64(char x) { if ((int) x > index_64.length) return -1; @@ -149,8 +147,6 @@ public class BCRYPT implements EncryptionMethod { * * @param s the string to decode * @param maxolen the maximum number of bytes to decode - - * @return an array containing the decoded bytes * @throws IllegalArgumentException if maxolen is invalid * @throws IllegalArgumentException */ private static byte[] decode_base64(String s, int maxolen) @@ -194,6 +190,178 @@ public class BCRYPT implements EncryptionMethod { return ret; } + /** + * Cycically extract a word of key material + * + * @param data the string to extract the data from + * @param offp a "pointer" (as a one-entry array) to the current offset into + * data + * @return the next word of material from data + */ + private static int streamtoword(byte data[], int offp[]) { + int i; + int word = 0; + int off = offp[0]; + + for (i = 0; i < 4; i++) { + word = (word << 8) | (data[off] & 0xff); + off = (off + 1) % data.length; + } + + offp[0] = off; + return word; + } + + /** + * Hash a password using the OpenBSD bcrypt scheme + * + * @param password the password to hash + * @param salt the salt to hash with (perhaps generated using BCrypt.gensalt) + * @return the hashed password + */ + public static String hashpw(String password, String salt) { + BCRYPT B; + String real_salt; + byte passwordb[], saltb[], hashed[]; + char minor = (char) 0; + int rounds, off = 0; + StringBuffer rs = new StringBuffer(); + + if (salt.charAt(0) != '$' || salt.charAt(1) != '2') + throw new IllegalArgumentException("Invalid salt version"); + if (salt.charAt(2) == '$') + off = 3; + else { + minor = salt.charAt(2); + if (minor < 'a' || minor > 'z' || salt.charAt(3) != '$') + throw new IllegalArgumentException("Invalid salt revision"); + off = 4; + } + + // Extract number of rounds + if (salt.charAt(off + 2) > '$') + throw new IllegalArgumentException("Missing salt rounds"); + rounds = Integer.parseInt(salt.substring(off, off + 2)); + + real_salt = salt.substring(off + 3, off + 25); + try { + passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8"); + } catch (UnsupportedEncodingException uee) { + throw new AssertionError("UTF-8 is not supported"); + } + + saltb = decode_base64(real_salt, BCRYPT_SALT_LEN); + + B = new BCRYPT(); + hashed = B.crypt_raw(passwordb, saltb, rounds); + + rs.append("$2"); + if (minor >= 'a') + rs.append(minor); + rs.append('$'); + if (rounds < 10) + rs.append('0'); + rs.append(Integer.toString(rounds)); + rs.append('$'); + rs.append(encode_base64(saltb, saltb.length)); + rs.append(encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1)); + return rs.toString(); + } + + /** + * Generate a salt for use with the BCrypt.hashpw() method + * + * @param log_rounds the log2 of the number of rounds of hashing to apply - the + * work factor therefore increases as 2**log_rounds. + * @param random an instance of SecureRandom to use + * @return an encoded salt value + */ + public static String gensalt(int log_rounds, SecureRandom random) { + StringBuffer rs = new StringBuffer(); + byte rnd[] = new byte[BCRYPT_SALT_LEN]; + + random.nextBytes(rnd); + + rs.append("$2a$"); + if (log_rounds < 10) + rs.append('0'); + rs.append(Integer.toString(log_rounds)); + rs.append('$'); + rs.append(encode_base64(rnd, rnd.length)); + return rs.toString(); + } + + /** + * Generate a salt for use with the BCrypt.hashpw() method + * + * @param log_rounds the log2 of the number of rounds of hashing to apply - the + * work factor therefore increases as 2**log_rounds. + * @return an encoded salt value + */ + public static String gensalt(int log_rounds) { + return gensalt(log_rounds, new SecureRandom()); + } + + /** + * Generate a salt for use with the BCrypt.hashpw() method, selecting a + * reasonable default for the number of hashing rounds to apply + * + * @return an encoded salt value + */ + public static String gensalt() { + return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS); + } + + /** + * Check that a plaintext password matches a previously hashed one + * + * @param plaintext the plaintext password to verify + * @param hashed the previously-hashed password + * @return true if the passwords match, false otherwise + */ + public static boolean checkpw(String plaintext, String hashed) { + return (hashed.compareTo(hashpw(plaintext, hashed)) == 0); + } + + /** + * Check that a text password matches a previously hashed one with the + * specified number of rounds using recursion + * + * @param text plaintext or hashed text + * @param hashed the previously-hashed password + * @param rounds number of rounds to hash the password + * @return boolean + */ + public static boolean checkpw(String text, String hashed, int rounds) { + boolean matched = false; + + if (rounds > 0) { + String hash = hashpw(text, hashed); + + if (rounds > 1) { + matched = checkpw(hash, hashed, rounds - 1); + } else { + matched = hash.compareTo(hashed) == 0; + } + } else { + matched = text.compareTo(hashed) == 0; + } + + return matched; + } + + /** + * Method getDoubleHash. + * + * @param text String + * @param salt String + * @return String + */ + public static String getDoubleHash(String text, String salt) { + String hash = hashpw(text, salt); + return hashpw(text, hash); + } + /** * Blowfish encipher a single 64-bit block encoded as two 32-bit halves * @@ -223,28 +391,6 @@ public class BCRYPT implements EncryptionMethod { lr[off + 1] = l; } - /** - * Cycically extract a word of key material - * - * @param data the string to extract the data from - * @param offp a "pointer" (as a one-entry array) to the current offset into - * data - - * @return the next word of material from data */ - private static int streamtoword(byte data[], int offp[]) { - int i; - int word = 0; - int off = offp[0]; - - for (i = 0; i < 4; i++) { - word = (word << 8) | (data[off] & 0xff); - off = (off + 1) % data.length; - } - - offp[0] = off; - return word; - } - /** * Initialise the Blowfish key schedule */ @@ -321,8 +467,8 @@ public class BCRYPT implements EncryptionMethod { * @param salt the binary salt to hash with the password * @param log_rounds the binary logarithm of the number of rounds of hashing to * apply - - * @return an array containing the binary hashed password */ + * @return an array containing the binary hashed password + */ private byte[] crypt_raw(byte password[], byte salt[], int log_rounds) { int rounds, i, j; int cdata[] = bf_crypt_ciphertext.clone(); @@ -357,154 +503,14 @@ public class BCRYPT implements EncryptionMethod { return ret; } - /** - * Hash a password using the OpenBSD bcrypt scheme - * - * @param password the password to hash - * @param salt the salt to hash with (perhaps generated using BCrypt.gensalt) - - * @return the hashed password */ - public static String hashpw(String password, String salt) { - BCRYPT B; - String real_salt; - byte passwordb[], saltb[], hashed[]; - char minor = (char) 0; - int rounds, off = 0; - StringBuffer rs = new StringBuffer(); - - if (salt.charAt(0) != '$' || salt.charAt(1) != '2') - throw new IllegalArgumentException("Invalid salt version"); - if (salt.charAt(2) == '$') - off = 3; - else { - minor = salt.charAt(2); - if (minor < 'a' || minor > 'z' || salt.charAt(3) != '$') - throw new IllegalArgumentException("Invalid salt revision"); - off = 4; - } - - // Extract number of rounds - if (salt.charAt(off + 2) > '$') - throw new IllegalArgumentException("Missing salt rounds"); - rounds = Integer.parseInt(salt.substring(off, off + 2)); - - real_salt = salt.substring(off + 3, off + 25); - try { - passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8"); - } catch (UnsupportedEncodingException uee) { - throw new AssertionError("UTF-8 is not supported"); - } - - saltb = decode_base64(real_salt, BCRYPT_SALT_LEN); - - B = new BCRYPT(); - hashed = B.crypt_raw(passwordb, saltb, rounds); - - rs.append("$2"); - if (minor >= 'a') - rs.append(minor); - rs.append('$'); - if (rounds < 10) - rs.append('0'); - rs.append(Integer.toString(rounds)); - rs.append('$'); - rs.append(encode_base64(saltb, saltb.length)); - rs.append(encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1)); - return rs.toString(); - } - - /** - * Generate a salt for use with the BCrypt.hashpw() method - * - * @param log_rounds the log2 of the number of rounds of hashing to apply - the - * work factor therefore increases as 2**log_rounds. - * @param random an instance of SecureRandom to use - - * @return an encoded salt value */ - public static String gensalt(int log_rounds, SecureRandom random) { - StringBuffer rs = new StringBuffer(); - byte rnd[] = new byte[BCRYPT_SALT_LEN]; - - random.nextBytes(rnd); - - rs.append("$2a$"); - if (log_rounds < 10) - rs.append('0'); - rs.append(Integer.toString(log_rounds)); - rs.append('$'); - rs.append(encode_base64(rnd, rnd.length)); - return rs.toString(); - } - - /** - * Generate a salt for use with the BCrypt.hashpw() method - * - * @param log_rounds the log2 of the number of rounds of hashing to apply - the - * work factor therefore increases as 2**log_rounds. - - * @return an encoded salt value */ - public static String gensalt(int log_rounds) { - return gensalt(log_rounds, new SecureRandom()); - } - - /** - * Generate a salt for use with the BCrypt.hashpw() method, selecting a - * reasonable default for the number of hashing rounds to apply - * - - * @return an encoded salt value */ - public static String gensalt() { - return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS); - } - - /** - * Check that a plaintext password matches a previously hashed one - * - * @param plaintext the plaintext password to verify - * @param hashed the previously-hashed password - - * @return true if the passwords match, false otherwise */ - public static boolean checkpw(String plaintext, String hashed) { - return (hashed.compareTo(hashpw(plaintext, hashed)) == 0); - } - - /** - * Check that a text password matches a previously hashed one with the - * specified number of rounds using recursion - * - * @param text plaintext or hashed text - * @param hashed the previously-hashed password - * @param rounds number of rounds to hash the password - - - * @return boolean */ - public static boolean checkpw(String text, String hashed, int rounds) { - boolean matched = false; - - if (rounds > 0) { - String hash = hashpw(text, hashed); - - if (rounds > 1) { - matched = checkpw(hash, hashed, rounds - 1); - } else { - matched = hash.compareTo(hashed) == 0; - } - } else { - matched = text.compareTo(hashed) == 0; - } - - return matched; - } - /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -513,27 +519,15 @@ public class BCRYPT implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { return checkpw(password, hash); } - - /** - * Method getDoubleHash. - * @param text String - * @param salt String - - * @return String */ - public static String getDoubleHash(String text, String salt) { - String hash = hashpw(text, salt); - return hashpw(text, hash); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java index ddee2cd8..4db8d8c6 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java @@ -8,13 +8,12 @@ public class BCRYPT2Y implements EncryptionMethod { /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -25,16 +24,15 @@ public class BCRYPT2Y implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + String playerName) throws NoSuchAlgorithmException { String ok = hash.substring(0, 29); if (ok.length() != 29) return false; diff --git a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java index 86e640c6..ac890a88 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java @@ -8,18 +8,32 @@ import java.security.NoSuchAlgorithmException; */ public class CRAZYCRYPT1 implements EncryptionMethod { + private static final char[] CRYPTCHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; protected final Charset charset = Charset.forName("UTF-8"); - private static final char[] CRYPTCHARS = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + /** + * Method byteArrayToHexString. + * + * @param args byte[]String * @return String + */ + + public static String byteArrayToHexString(final byte... args) { + final char[] chars = new char[args.length * 2]; + for (int i = 0; i < args.length; i++) { + chars[i * 2] = CRYPTCHARS[(args[i] >> 4) & 0xF]; + chars[i * 2 + 1] = CRYPTCHARS[(args[i]) & 0xF]; + } + return new String(chars); + } /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -32,29 +46,18 @@ public class CRAZYCRYPT1 implements EncryptionMethod { return null; } } -/** + + /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName Stringooleaneptiontring) * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - + @Override public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + String playerName) throws NoSuchAlgorithmException { return hash.equals(getHash(password, null, playerName)); } -/** - * Method byteArrayToHexString. - * @param args byte[]String * @return String - */ - - public static String byteArrayToHexString(final byte... args) { - final char[] chars = new char[args.length * 2]; - for (int i = 0; i < args.length; i++) { - chars[i * 2] = CRYPTCHARS[(args[i] >> 4) & 0xF]; - chars[i * 2 + 1] = CRYPTCHARS[(args[i]) & 0xF]; - } - return new String(chars); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java index 9b1bc779..e6bc7c03 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java @@ -1,23 +1,22 @@ package fr.xephi.authme.security.crypts; -import java.security.NoSuchAlgorithmException; - import fr.xephi.authme.security.pbkdf2.PBKDF2Engine; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; +import java.security.NoSuchAlgorithmException; + /** */ public class CryptPBKDF2 implements EncryptionMethod { /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -30,16 +29,15 @@ public class CryptPBKDF2 implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + String playerName) throws NoSuchAlgorithmException { String[] line = hash.split("\\$"); String salt = line[2]; String derivedKey = line[3]; diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java index 7c70909d..13aaf49f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java @@ -1,25 +1,23 @@ package fr.xephi.authme.security.crypts; -import java.security.NoSuchAlgorithmException; - -import javax.xml.bind.DatatypeConverter; - import fr.xephi.authme.security.pbkdf2.PBKDF2Engine; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; +import javax.xml.bind.DatatypeConverter; +import java.security.NoSuchAlgorithmException; + /** */ public class CryptPBKDF2Django implements EncryptionMethod { /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -32,13 +30,12 @@ public class CryptPBKDF2Django implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java index 7f089be8..486ffbae 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java @@ -8,42 +8,12 @@ import java.security.NoSuchAlgorithmException; */ public class DOUBLEMD5 implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(getMD5(password)); - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(getHash(password, "", "")); - } - /** * Method getMD5. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); @@ -53,4 +23,32 @@ public class DOUBLEMD5 implements EncryptionMethod { return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return getMD5(getMD5(password)); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + return hash.equals(getHash(password, "", "")); + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java index 829c786e..e56e5ecd 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java @@ -13,6 +13,7 @@ import java.security.NoSuchAlgorithmException; *

* The comparePassword is called when we need to match password (/login usually) *

+ * * @author Gabriele * @version $Revision: 1.0 $ */ @@ -20,12 +21,9 @@ public interface EncryptionMethod { /** * @param password - * @param salt - * (can be an other data like playerName;salt , playerName, - * etc... for customs methods) - - - * @param name String + * @param salt (can be an other data like playerName;salt , playerName, + * etc... for customs methods) + * @param name String * @return Hashing password * @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException */ String getHash(String password, String salt, String name) @@ -35,8 +33,6 @@ public interface EncryptionMethod { * @param hash * @param password * @param playerName - - * @return true if password match, false else * @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException */ boolean comparePassword(String hash, String password, String playerName) diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java index 7d8ca944..857c3fef 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java @@ -1,52 +1,21 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.AuthMe; + import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import fr.xephi.authme.AuthMe; - /** */ public class IPB3 implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(getMD5(salt) + getMD5(password)); - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(getHash(password, salt, playerName)); - } - /** * Method getMD5. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); @@ -55,4 +24,33 @@ public class IPB3 implements EncryptionMethod { byte[] digest = md5.digest(); return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return getMD5(getMD5(salt) + getMD5(password)); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); + return hash.equals(getHash(password, salt, playerName)); + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java index f43d15f2..73fbd3bd 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java +++ b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java @@ -8,43 +8,12 @@ import java.security.NoSuchAlgorithmException; */ public class JOOMLA implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(password + salt) + ":" + salt; - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = hash.split(":")[1]; - return hash.equals(getMD5(password + salt) + ":" + salt); - } - /** * Method getMD5. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); @@ -53,4 +22,33 @@ public class JOOMLA implements EncryptionMethod { byte[] digest = md5.digest(); return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return getMD5(password + salt) + ":" + salt; + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + String salt = hash.split(":")[1]; + return hash.equals(getMD5(password + salt) + ":" + salt); + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5.java b/src/main/java/fr/xephi/authme/security/crypts/MD5.java index 7caf5ba3..af94098b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5.java @@ -8,42 +8,12 @@ import java.security.NoSuchAlgorithmException; */ public class MD5 implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(password); - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(getHash(password, "", "")); - } - /** * Method getMD5. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); @@ -52,4 +22,32 @@ public class MD5 implements EncryptionMethod { byte[] digest = md5.digest(); return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return getMD5(password); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + return hash.equals(getHash(password, "", "")); + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java index ecb8a78c..ab2878f4 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java @@ -8,43 +8,12 @@ import java.security.NoSuchAlgorithmException; */ public class MD5VB implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return "$MD5vb$" + salt + "$" + getMD5(getMD5(password) + salt); - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String[] line = hash.split("\\$"); - return hash.equals(getHash(password, line[2], "")); - } - /** * Method getMD5. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); @@ -54,4 +23,33 @@ public class MD5VB implements EncryptionMethod { return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return "$MD5vb$" + salt + "$" + getMD5(getMD5(password) + salt); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + String[] line = hash.split("\\$"); + return hash.equals(getHash(password, line[2], "")); + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java index 02a362c3..0cae271f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java @@ -1,52 +1,21 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.AuthMe; + import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import fr.xephi.authme.AuthMe; - /** */ public class MYBB implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(getMD5(salt) + getMD5(password)); - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(getHash(password, salt, playerName)); - } - /** * Method getMD5. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); @@ -55,4 +24,33 @@ public class MYBB implements EncryptionMethod { byte[] digest = md5.digest(); return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return getMD5(getMD5(salt) + getMD5(password)); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); + return hash.equals(getHash(password, salt, playerName)); + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java index ac06e32e..58ebdc74 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java @@ -17,12 +17,79 @@ public class PHPBB implements EncryptionMethod { private String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + /** + * Method md5. + * + * @param data String + * @return String + */ + public static String md5(String data) { + try { + byte[] bytes = data.getBytes("ISO-8859-1"); + MessageDigest md5er = MessageDigest.getInstance("MD5"); + byte[] hash = md5er.digest(bytes); + return bytes2hex(hash); + } catch (GeneralSecurityException | UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + /** + * Method hexToInt. + * + * @param ch char + * @return int + */ + static int hexToInt(char ch) { + if (ch >= '0' && ch <= '9') + return ch - '0'; + ch = Character.toUpperCase(ch); + if (ch >= 'A' && ch <= 'F') + return ch - 'A' + 0xA; + throw new IllegalArgumentException("Not a hex character: " + ch); + } + + /** + * Method bytes2hex. + * + * @param bytes byte[] + * @return String + */ + private static String bytes2hex(byte[] bytes) { + StringBuilder r = new StringBuilder(32); + for (byte b : bytes) { + String x = Integer.toHexString(b & 0xff); + if (x.length() < 2) + r.append('0'); + r.append(x); + } + return r.toString(); + } + + /** + * Method pack. + * + * @param hex String + * @return String + */ + static String pack(String hex) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < hex.length(); i += 2) { + char c1 = hex.charAt(i); + char c2 = hex.charAt(i + 1); + char packed = (char) (hexToInt(c1) * 16 + hexToInt(c2)); + buf.append(packed); + } + return buf.toString(); + } + /** * Method phpbb_hash. + * * @param password String - * @param salt String - - * @return String */ + * @param salt String + * @return String + */ public String phpbb_hash(String password, String salt) { String random_state = salt; StringBuilder random = new StringBuilder(); @@ -40,21 +107,23 @@ public class PHPBB implements EncryptionMethod { /** * Method _hash_gensalt_private. - * @param input String + * + * @param input String * @param itoa64 String - - * @return String */ + * @return String + */ private String _hash_gensalt_private(String input, String itoa64) { return _hash_gensalt_private(input, itoa64, 6); } /** * Method _hash_gensalt_private. - * @param input String - * @param itoa64 String + * + * @param input String + * @param itoa64 String * @param iteration_count_log2 int - - * @return String */ + * @return String + */ private String _hash_gensalt_private(String input, String itoa64, int iteration_count_log2) { if (iteration_count_log2 < 4 || iteration_count_log2 > 31) { @@ -68,10 +137,11 @@ public class PHPBB implements EncryptionMethod { /** * Encode hash + * * @param input String * @param count int - - * @return String */ + * @return String + */ private String _hash_encode64(String input, int count) { StringBuilder output = new StringBuilder(); int i = 0; @@ -95,10 +165,11 @@ public class PHPBB implements EncryptionMethod { /** * Method _hash_crypt_private. + * * @param password String - * @param setting String - - * @return String */ + * @param setting String + * @return String + */ String _hash_crypt_private(String password, String setting) { String output = "*"; if (!setting.substring(0, 3).equals("$H$")) @@ -122,87 +193,25 @@ public class PHPBB implements EncryptionMethod { /** * Method phpbb_check_hash. + * * @param password String - * @param hash String - - * @return boolean */ + * @param hash String + * @return boolean + */ public boolean phpbb_check_hash(String password, String hash) { if (hash.length() == 34) return _hash_crypt_private(password, hash).equals(hash); else return md5(password).equals(hash); } - /** - * Method md5. - * @param data String - - * @return String */ - public static String md5(String data) { - try { - byte[] bytes = data.getBytes("ISO-8859-1"); - MessageDigest md5er = MessageDigest.getInstance("MD5"); - byte[] hash = md5er.digest(bytes); - return bytes2hex(hash); - } catch (GeneralSecurityException | UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - /** - * Method hexToInt. - * @param ch char - - * @return int */ - static int hexToInt(char ch) { - if (ch >= '0' && ch <= '9') - return ch - '0'; - ch = Character.toUpperCase(ch); - if (ch >= 'A' && ch <= 'F') - return ch - 'A' + 0xA; - throw new IllegalArgumentException("Not a hex character: " + ch); - } - - /** - * Method bytes2hex. - * @param bytes byte[] - - * @return String */ - private static String bytes2hex(byte[] bytes) { - StringBuilder r = new StringBuilder(32); - for (byte b : bytes) { - String x = Integer.toHexString(b & 0xff); - if (x.length() < 2) - r.append('0'); - r.append(x); - } - return r.toString(); - } - - /** - * Method pack. - * @param hex String - - * @return String */ - static String pack(String hex) { - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < hex.length(); i += 2) { - char c1 = hex.charAt(i); - char c2 = hex.charAt(i + 1); - char packed = (char) (hexToInt(c1) * 16 + hexToInt(c2)); - buf.append(packed); - } - return buf.toString(); - } - /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -211,13 +220,12 @@ public class PHPBB implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java index b9d938a8..54e6cd66 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java @@ -1,29 +1,42 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.AuthMe; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import fr.xephi.authme.AuthMe; - /** */ public class PHPFUSION implements EncryptionMethod { + /** + * Method getSHA1. + * + * @param message String + * @return String * @throws NoSuchAlgorithmException + */ + private static String getSHA1(String message) + throws NoSuchAlgorithmException { + MessageDigest sha1 = MessageDigest.getInstance("SHA1"); + sha1.reset(); + sha1.update(message.getBytes()); + byte[] digest = sha1.digest(); + return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + } + /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -53,33 +66,17 @@ public class PHPFUSION implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + String playerName) throws NoSuchAlgorithmException { String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); return hash.equals(getHash(password, salt, "")); } - /** - * Method getSHA1. - * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ - private static String getSHA1(String message) - throws NoSuchAlgorithmException { - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - sha1.reset(); - sha1.update(message.getBytes()); - byte[] digest = sha1.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); - } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java index 94f4fe5f..afb6dde9 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java @@ -8,13 +8,12 @@ public class PLAINTEXT implements EncryptionMethod { /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -23,16 +22,15 @@ public class PLAINTEXT implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + String playerName) throws NoSuchAlgorithmException { return hash.equals(password); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java index e9b9e1e9..ad3901e4 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java @@ -9,13 +9,12 @@ public class ROYALAUTH implements EncryptionMethod { /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -26,11 +25,11 @@ public class ROYALAUTH implements EncryptionMethod { /** * Method hash. + * * @param password String - * @param salt String - - - * @return String * @throws NoSuchAlgorithmException */ + * @param salt String + * @return String * @throws NoSuchAlgorithmException + */ public String hash(String password, String salt) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-512"); @@ -44,16 +43,15 @@ public class ROYALAUTH implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + String playerName) throws NoSuchAlgorithmException { return hash.equalsIgnoreCase(getHash(password, "", "")); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java index f90f6907..f71018ac 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java @@ -1,52 +1,21 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.AuthMe; + import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import fr.xephi.authme.AuthMe; - /** */ public class SALTED2MD5 implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(getMD5(password) + salt); - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(getMD5(getMD5(password) + salt)); - } - /** * Method getMD5. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getMD5(String message) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); @@ -55,4 +24,33 @@ public class SALTED2MD5 implements EncryptionMethod { byte[] digest = md5.digest(); return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return getMD5(getMD5(password) + salt); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); + return hash.equals(getMD5(getMD5(password) + salt)); + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java index cd2b4aac..dcacb63c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java @@ -1,52 +1,21 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.AuthMe; + import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import fr.xephi.authme.AuthMe; - /** */ public class SALTEDSHA512 implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA512(password + salt); - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(getHash(password, salt, "")); - } - /** * Method getSHA512. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getSHA512(String message) throws NoSuchAlgorithmException { MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); @@ -55,4 +24,33 @@ public class SALTEDSHA512 implements EncryptionMethod { byte[] digest = sha512.digest(); return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return getSHA512(password + salt); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); + return hash.equals(getHash(password, salt, "")); + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java index ae1fab0a..7d1a48a6 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java @@ -8,42 +8,12 @@ import java.security.NoSuchAlgorithmException; */ public class SHA1 implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA1(password); - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(getHash(password, "", "")); - } - /** * Method getSHA1. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getSHA1(String message) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); @@ -53,4 +23,32 @@ public class SHA1 implements EncryptionMethod { return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return getSHA1(password); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + return hash.equals(getHash(password, "", "")); + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java index 11d53e16..b0ddfa56 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java @@ -8,43 +8,12 @@ import java.security.NoSuchAlgorithmException; */ public class SHA256 implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return "$SHA$" + salt + "$" + getSHA256(getSHA256(password) + salt); - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String[] line = hash.split("\\$"); - return hash.equals(getHash(password, line[2], "")); - } - /** * Method getSHA256. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getSHA256(String message) throws NoSuchAlgorithmException { MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); @@ -54,4 +23,33 @@ public class SHA256 implements EncryptionMethod { return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return "$SHA$" + salt + "$" + getSHA256(getSHA256(password) + salt); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + String[] line = hash.split("\\$"); + return hash.equals(getHash(password, line[2], "")); + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java index a44d9367..a6ccd615 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java @@ -8,42 +8,12 @@ import java.security.NoSuchAlgorithmException; */ public class SHA512 implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA512(password); - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(getHash(password, "", "")); - } - /** * Method getSHA512. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getSHA512(String message) throws NoSuchAlgorithmException { MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); @@ -52,4 +22,32 @@ public class SHA512 implements EncryptionMethod { byte[] digest = sha512.digest(); return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return getSHA512(password); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + return hash.equals(getHash(password, "", "")); + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/SMF.java index 8ee0cbe0..ce15e030 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SMF.java @@ -8,42 +8,12 @@ import java.security.NoSuchAlgorithmException; */ public class SMF implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA1(name.toLowerCase() + password); - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(getHash(password, null, playerName)); - } - /** * Method getSHA1. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getSHA1(String message) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); @@ -52,4 +22,32 @@ public class SMF implements EncryptionMethod { byte[] digest = sha1.digest(); return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return getSHA1(name.toLowerCase() + password); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + return hash.equals(getHash(password, null, playerName)); + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java index 4a5cdef2..1d966423 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java @@ -1,52 +1,21 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.AuthMe; + import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import fr.xephi.authme.AuthMe; - /** */ public class WBB3 implements EncryptionMethod { - /** - * Method getHash. - * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA1(salt.concat(getSHA1(salt.concat(getSHA1(password))))); - } - - /** - * Method comparePassword. - * @param hash String - * @param password String - * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(getHash(password, salt, "")); - } - /** * Method getSHA1. + * * @param message String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ private static String getSHA1(String message) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); @@ -55,4 +24,33 @@ public class WBB3 implements EncryptionMethod { byte[] digest = sha1.digest(); return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + return getSHA1(salt.concat(getSHA1(salt.concat(getSHA1(password))))); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); + return hash.equals(getHash(password, salt, "")); + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java index ab43c65a..ebe79438 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java @@ -8,13 +8,12 @@ public class WBB4 implements EncryptionMethod { /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -23,16 +22,15 @@ public class WBB4 implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + String playerName) throws NoSuchAlgorithmException { return BCRYPT.checkpw(password, hash, 2); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java index f8cf09ee..596aafe8 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java @@ -2,63 +2,61 @@ package fr.xephi.authme.security.crypts; /** * The Whirlpool hashing function. - * - *

+ *

+ *

* References - * - *

+ *

+ *

* The Whirlpool algorithm was developed by Paulo S. L. M. Barreto and Vincent Rijmen. - * + *

* See P.S.L.M. Barreto, V. Rijmen, ``The Whirlpool hashing function,'' First * NESSIE workshop, 2000 (tweaked version, 2003), * - * + * * @author Paulo S.L.M. Barreto * @author Vincent Rijmen. - * * @version 3.0 (2003.03.12) - * - * ==================================================================== - * ========= - * - * Differences from version 2.1: - * - * - Suboptimal diffusion matrix replaced by cir(1, 1, 4, 1, 8, 5, 2, - * 9). - * - * ==================================================================== - * ========= - * - * Differences from version 2.0: - * - * - Generation of ISO/IEC 10118-3 test vectors. - Bug fix: nonzero - * carry was ignored when tallying the data length (this bug apparently - * only manifested itself when feeding data in pieces rather than in a - * single chunk at once). - * - * Differences from version 1.0: - * - * - Original S-box replaced by the tweaked, hardware-efficient - * version. - * - * ==================================================================== - * ========= - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + *

+ * ==================================================================== + * ========= + *

+ * Differences from version 2.1: + *

+ * - Suboptimal diffusion matrix replaced by cir(1, 1, 4, 1, 8, 5, 2, + * 9). + *

+ * ==================================================================== + * ========= + *

+ * Differences from version 2.0: + *

+ * - Generation of ISO/IEC 10118-3 test vectors. - Bug fix: nonzero + * carry was ignored when tallying the data length (this bug apparently + * only manifested itself when feeding data in pieces rather than in a + * single chunk at once). + *

+ * Differences from version 1.0: + *

+ * - Original S-box replaced by the tweaked, hardware-efficient + * version. + *

+ * ==================================================================== + * ========= + *

+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import java.security.NoSuchAlgorithmException; @@ -166,6 +164,22 @@ public class WHIRLPOOL implements EncryptionMethod { public WHIRLPOOL() { } + /** + * Method display. + * @param array byte[] + + * @return String */ + protected static String display(byte[] array) { + char[] val = new char[2 * array.length]; + String hex = "0123456789ABCDEF"; + for (int i = 0; i < array.length; i++) { + int b = array[i] & 0xff; + val[2 * i] = hex.charAt(b >>> 4); + val[2 * i + 1] = hex.charAt(b & 15); + } + return String.valueOf(val); + } + /** * The core Whirlpool transform. */ @@ -232,12 +246,12 @@ public class WHIRLPOOL implements EncryptionMethod { /** * Delivers input data to the hashing algorithm. - * + * * @param source * plaintext data to hash. * @param sourceBits * how many bits of plaintext to process. - * + * * This method maintains the invariant: bufferBits < 512 */ public void NESSIEadd(byte[] source, long sourceBits) { @@ -249,9 +263,9 @@ public class WHIRLPOOL implements EncryptionMethod { * +-------+-------+-------+-------+-------+------- | bufferPos */ int sourcePos = 0; // index of leftmost source byte containing data (1 - // to 8 bits). + // to 8 bits). int sourceGap = (8 - ((int) sourceBits & 7)) & 7; // space on - // source[sourcePos]. + // source[sourcePos]. int bufferRem = bufferBits & 7; // occupied bits on buffer[bufferPos]. int b; // tally the length of the added data: @@ -264,7 +278,7 @@ public class WHIRLPOOL implements EncryptionMethod { } // process data in chunks of 8 bits: while (sourceBits > 8) { // at least source[sourcePos] and - // source[sourcePos+1] contain data. + // source[sourcePos+1] contain data. // take a byte from the source: b = ((source[sourcePos] << sourceGap) & 0xff) | ((source[sourcePos + 1] & 0xff) >>> (8 - sourceGap)); if (b < 0 || b >= 256) { @@ -289,7 +303,7 @@ public class WHIRLPOOL implements EncryptionMethod { // furthermore, all data (if any is left) is in source[sourcePos]. if (sourceBits > 0) { b = (source[sourcePos] << sourceGap) & 0xff; // bits are - // left-justified on b. + // left-justified on b. // process the remaining bits: buffer[bufferPos] |= b >>> bufferRem; } else { @@ -319,7 +333,7 @@ public class WHIRLPOOL implements EncryptionMethod { /** * Get the hash value from the hashing state. - * + * * This method uses the invariant: bufferBits < 512 * @param digest byte[] */ @@ -360,10 +374,10 @@ public class WHIRLPOOL implements EncryptionMethod { /** * Delivers string input data to the hashing algorithm. - * + * * @param source * plaintext data to hash (ASCII text string). - * + * * This method maintains the invariant: bufferBits < 512 */ public void NESSIEadd(String source) { @@ -376,30 +390,14 @@ public class WHIRLPOOL implements EncryptionMethod { } } - /** - * Method display. - * @param array byte[] - - * @return String */ - protected static String display(byte[] array) { - char[] val = new char[2 * array.length]; - String hex = "0123456789ABCDEF"; - for (int i = 0; i < array.length; i++) { - int b = array[i] & 0xff; - val[2 * i] = hex.charAt(b >>> 4); - val[2 * i + 1] = hex.charAt(b & 15); - } - return String.valueOf(val); - } - /** * Method getHash. * @param password String * @param salt String * @param name String - - - + + + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) @@ -416,13 +414,13 @@ public class WHIRLPOOL implements EncryptionMethod { * @param hash String * @param password String * @param playerName String - - - + + + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ @Override public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + String playerName) throws NoSuchAlgorithmException { return hash.equals(getHash(password, "", "")); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java index 6c74b0b9..557c4bdd 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java @@ -15,10 +15,11 @@ public class WORDPRESS implements EncryptionMethod { /** * Method encode64. - * @param src byte[] + * + * @param src byte[] * @param count int - - * @return String */ + * @return String + */ private String encode64(byte[] src, int count) { int i, value; StringBuilder output = new StringBuilder(); @@ -55,10 +56,11 @@ public class WORDPRESS implements EncryptionMethod { /** * Method crypt. + * * @param password String - * @param setting String - - * @return String */ + * @param setting String + * @return String + */ private String crypt(String password, String setting) { String output = "*0"; if (((setting.length() < 2) ? setting : setting.substring(0, 2)).equalsIgnoreCase(output)) { @@ -99,9 +101,10 @@ public class WORDPRESS implements EncryptionMethod { /** * Method gensaltPrivate. + * * @param input byte[] - - * @return String */ + * @return String + */ private String gensaltPrivate(byte[] input) { String output = "$P$"; int iterationCountLog2 = 8; @@ -112,9 +115,10 @@ public class WORDPRESS implements EncryptionMethod { /** * Method stringToUtf8. + * * @param string String - - * @return byte[] */ + * @return byte[] + */ private byte[] stringToUtf8(String string) { try { return string.getBytes("UTF-8"); @@ -125,13 +129,12 @@ public class WORDPRESS implements EncryptionMethod { /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -142,16 +145,15 @@ public class WORDPRESS implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + String playerName) throws NoSuchAlgorithmException { String comparedHash = crypt(password, hash); return comparedHash.equals(hash); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java index 2254700f..6848d2ea 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java @@ -6,15 +6,29 @@ import java.security.NoSuchAlgorithmException; */ public class XAUTH implements EncryptionMethod { + /** + * Method getWhirlpool. + * + * @param message String + * @return String + */ + public static String getWhirlpool(String message) { + WHIRLPOOL w = new WHIRLPOOL(); + byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES]; + w.NESSIEinit(); + w.NESSIEadd(message); + w.NESSIEfinalize(digest); + return WHIRLPOOL.display(digest); + } + /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -25,33 +39,18 @@ public class XAUTH implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + String playerName) throws NoSuchAlgorithmException { int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length()); String salt = hash.substring(saltPos, saltPos + 12); return hash.equals(getHash(password, salt, "")); } - /** - * Method getWhirlpool. - * @param message String - - * @return String */ - public static String getWhirlpool(String message) { - WHIRLPOOL w = new WHIRLPOOL(); - byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES]; - w.NESSIEinit(); - w.NESSIEadd(message); - w.NESSIEfinalize(digest); - return WHIRLPOOL.display(digest); - } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/XF.java b/src/main/java/fr/xephi/authme/security/crypts/XF.java index c8f778db..41d665ac 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XF.java @@ -1,5 +1,7 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.AuthMe; + import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -7,21 +9,18 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import fr.xephi.authme.AuthMe; - /** */ public class XF implements EncryptionMethod { /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -30,26 +29,25 @@ public class XF implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + String playerName) throws NoSuchAlgorithmException { String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); return hash.equals(regmatch("\"hash\";.:..:\"(.*)\";.:.:\"salt\"", salt)); } /** * Method getSHA256. + * * @param password String - - - * @return String * @throws NoSuchAlgorithmException */ + * @return String * @throws NoSuchAlgorithmException + */ public String getSHA256(String password) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(password.getBytes()); @@ -71,10 +69,11 @@ public class XF implements EncryptionMethod { /** * Method regmatch. + * * @param pattern String - * @param line String - - * @return String */ + * @param line String + * @return String + */ public String regmatch(String pattern, String line) { List allMatches = new ArrayList<>(); Matcher m = Pattern.compile(pattern).matcher(line); diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/BinTools.java b/src/main/java/fr/xephi/authme/security/pbkdf2/BinTools.java index b8c5a198..d465bbc6 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/BinTools.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/BinTools.java @@ -26,7 +26,7 @@ package fr.xephi.authme.security.pbkdf2; * href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" * >http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. *

- * + * * @author Matthias Gärtner * @version 1.0 */ @@ -36,12 +36,11 @@ public class BinTools { /** * Simple binary-to-hexadecimal conversion. - * - * @param b - * Input bytes. May be null. - + * + * @param b Input bytes. May be null. * @return Hexadecimal representation of b. Uppercase A-F, two characters - * per byte. Empty string on null input. */ + * per byte. Empty string on null input. + */ public static String bin2hex(final byte[] b) { if (b == null) { return ""; @@ -57,14 +56,12 @@ public class BinTools { /** * Convert hex string to array of bytes. - * - * @param s - * String containing hexadecimal digits. May be null - * . On odd length leading zero will be assumed. - - + * + * @param s String containing hexadecimal digits. May be null + * . On odd length leading zero will be assumed. * @return Array on bytes, non-null. * @throws IllegalArgumentException - * when string contains non-hex character */ + * when string contains non-hex character + */ public static byte[] hex2bin(final String s) { String m = s; if (s == null) { @@ -85,13 +82,11 @@ public class BinTools { /** * Convert hex digit to numerical value. - * - * @param c - * 0-9, a-f, A-F allowd. - - + * + * @param c 0-9, a-f, A-F allowd. * @return 0-15 * @throws IllegalArgumentException - * on non-hex character */ + * on non-hex character + */ public static int hex2bin(char c) { if (c >= '0' && c <= '9') { return (c - '0'); @@ -107,6 +102,7 @@ public class BinTools { /** * Method main. + * * @param args String[] */ public static void main(String[] args) { diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/MacBasedPRF.java b/src/main/java/fr/xephi/authme/security/pbkdf2/MacBasedPRF.java index 9a7cc0bc..0332003b 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/MacBasedPRF.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/MacBasedPRF.java @@ -1,15 +1,14 @@ package fr.xephi.authme.security.pbkdf2; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - /** * Default PRF implementation based on standard javax.crypt.Mac mechanisms. - * + *

*


*

* A free Java implementation of Password Based Key Derivation Function 2 as @@ -52,8 +51,7 @@ public class MacBasedPRF implements PRF { /** * Create Mac-based Pseudo Random Function. * - * @param macAlgorithm - * Mac algorithm to use, i.e. HMacSHA1 or HMacMD5. + * @param macAlgorithm Mac algorithm to use, i.e. HMacSHA1 or HMacMD5. */ public MacBasedPRF(String macAlgorithm) { this.macAlgorithm = macAlgorithm; @@ -67,8 +65,9 @@ public class MacBasedPRF implements PRF { /** * Constructor for MacBasedPRF. + * * @param macAlgorithm String - * @param provider String + * @param provider String */ public MacBasedPRF(String macAlgorithm, String provider) { this.macAlgorithm = macAlgorithm; @@ -82,10 +81,10 @@ public class MacBasedPRF implements PRF { /** * Method doFinal. + * * @param M byte[] - - - * @return byte[] * @see fr.xephi.authme.security.pbkdf2.PRF#doFinal(byte[]) */ + * @return byte[] * @see fr.xephi.authme.security.pbkdf2.PRF#doFinal(byte[]) + */ public byte[] doFinal(byte[] M) { byte[] r = mac.doFinal(M); return r; @@ -93,18 +92,19 @@ public class MacBasedPRF implements PRF { /** * Method getHLen. - - - * @return int * @see fr.xephi.authme.security.pbkdf2.PRF#getHLen() */ + * + * @return int * @see fr.xephi.authme.security.pbkdf2.PRF#getHLen() + */ public int getHLen() { return hLen; } /** * Method init. + * * @param P byte[] - - * @see fr.xephi.authme.security.pbkdf2.PRF#init(byte[]) */ + * @see fr.xephi.authme.security.pbkdf2.PRF#init(byte[]) + */ public void init(byte[] P) { try { mac.init(new SecretKeySpec(P, macAlgorithm)); diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2.java index 7a623e06..f5bd3445 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2.java @@ -27,7 +27,7 @@ package fr.xephi.authme.security.pbkdf2; * href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" * >http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. *

- * + * * @author Matthias Gärtner * @version 1.0 */ @@ -36,64 +36,59 @@ public interface PBKDF2 { /** * Convert String-based input to internal byte array, then invoke PBKDF2. * Desired key length defaults to Pseudo Random Function block size. - * - * @param inputPassword - * Candidate password to compute the derived key for. - - * @return internal byte array */ + * + * @param inputPassword Candidate password to compute the derived key for. + * @return internal byte array + */ public abstract byte[] deriveKey(String inputPassword); /** * Convert String-based input to internal byte array, then invoke PBKDF2. - * - * @param inputPassword - * Candidate password to compute the derived key for. - * @param dkLen - * Specify desired key length - - * @return internal byte array */ + * + * @param inputPassword Candidate password to compute the derived key for. + * @param dkLen Specify desired key length + * @return internal byte array + */ public abstract byte[] deriveKey(String inputPassword, int dkLen); /** * Convert String-based input to internal byte arrays, then invoke PBKDF2 * and verify result against the reference data that is supplied in the * PBKDF2Parameters. - * - * @param inputPassword - * Candidate password to compute the derived key for. - + * + * @param inputPassword Candidate password to compute the derived key for. * @return true password match; false incorrect - * password */ + * password + */ public abstract boolean verifyKey(String inputPassword); /** * Allow reading of configured parameters. - * - - * @return Currently set parameters. */ + * + * @return Currently set parameters. + */ public abstract PBKDF2Parameters getParameters(); /** * Allow setting of configured parameters. - * + * * @param parameters */ public abstract void setParameters(PBKDF2Parameters parameters); /** * Get currently set Pseudo Random Function. - * - - * @return Currently set Pseudo Random Function */ + * + * @return Currently set Pseudo Random Function + */ public abstract PRF getPseudoRandomFunction(); /** * Set the Pseudo Random Function to use. Note that deriveKeys/getPRF does * init this object using the supplied candidate password. If this is * undesired, one has to override getPRF. - * - * @param prf - * Pseudo Random Function to set. + * + * @param prf Pseudo Random Function to set. */ public abstract void setPseudoRandomFunction(PRF prf); } diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java index 3e124da1..fb8252ba 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java @@ -10,17 +10,17 @@ import java.security.SecureRandom; * Request for Comments: 2898 PKCS #5: Password-Based Cryptography Specification *

* Version 2.0 - * + *

*

* PBKDF2 (P, S, c, dkLen) - * + *

*

* Options: *

    *
  • PRF underlying pseudorandom function (hLen denotes the length in octets * of the pseudorandom function output). PRF is pluggable.
  • *
- * + *

*

* Input: *

    @@ -30,13 +30,13 @@ import java.security.SecureRandom; *
  • dkLen intended length in octets of the derived key, a positive integer, * at most (2^32 - 1) * hLen
  • *
- * + *

*

* Output: *

    *
  • DK derived key, a dkLen-octet string
  • *
- * + *

*


*

* A free Java implementation of Password Based Key Derivation Function 2 as @@ -64,10 +64,10 @@ import java.security.SecureRandom; * http://www. * gnu.org/licenses/old-licenses/lgpl-2.1.html. *

- * - * @see RFC 2898 + * * @author Matthias Gärtner * @version 1.0 + * @see RFC 2898 */ public class PBKDF2Engine implements PBKDF2 { @@ -88,9 +88,8 @@ public class PBKDF2Engine implements PBKDF2 { * Constructor for PBKDF2 implementation object. PBKDF2 parameters are * passed so that this implementation knows iteration count, method to use * and String encoding. - * - * @param parameters - * Data holder for iteration count, method to use et cetera. + * + * @param parameters Data holder for iteration count, method to use et cetera. */ public PBKDF2Engine(PBKDF2Parameters parameters) { this.parameters = parameters; @@ -101,262 +100,26 @@ public class PBKDF2Engine implements PBKDF2 { * Constructor for PBKDF2 implementation object. PBKDF2 parameters are * passed so that this implementation knows iteration count, method to use * and String encoding. - * - * @param parameters - * Data holder for iteration count, method to use et cetera. - * @param prf - * Supply customer Pseudo Random Function. + * + * @param parameters Data holder for iteration count, method to use et cetera. + * @param prf Supply customer Pseudo Random Function. */ public PBKDF2Engine(PBKDF2Parameters parameters, PRF prf) { this.parameters = parameters; this.prf = prf; } - /** - * Method deriveKey. - * @param inputPassword String - - - * @return byte[] * @see fr.xephi.authme.security.pbkdf2.PBKDF2#deriveKey(String) */ - public byte[] deriveKey(String inputPassword) { - return deriveKey(inputPassword, 0); - } - - /** - * Method deriveKey. - * @param inputPassword String - * @param dkLen int - - - * @return byte[] * @see fr.xephi.authme.security.pbkdf2.PBKDF2#deriveKey(String, int) */ - public byte[] deriveKey(String inputPassword, int dkLen) { - byte[] r = null; - byte P[] = null; - String charset = parameters.getHashCharset(); - if (inputPassword == null) { - inputPassword = ""; - } - try { - if (charset == null) { - P = inputPassword.getBytes(); - } else { - P = inputPassword.getBytes(charset); - } - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - assertPRF(P); - if (dkLen == 0) { - dkLen = prf.getHLen(); - } - r = PBKDF2(prf, parameters.getSalt(), parameters.getIterationCount(), dkLen); - return r; - } - - /** - * Method verifyKey. - * @param inputPassword String - - - * @return boolean * @see fr.xephi.authme.security.pbkdf2.PBKDF2#verifyKey(String) */ - public boolean verifyKey(String inputPassword) { - byte[] referenceKey = getParameters().getDerivedKey(); - if (referenceKey == null || referenceKey.length == 0) { - return false; - } - byte[] inputKey = deriveKey(inputPassword, referenceKey.length); - - if (inputKey == null || inputKey.length != referenceKey.length) { - return false; - } - for (int i = 0; i < inputKey.length; i++) { - if (inputKey[i] != referenceKey[i]) { - return false; - } - } - return true; - } - - /** - * Factory method. Default implementation is (H)MAC-based. To be overridden - * in derived classes. - * - * @param P - * User-supplied candidate password as array of bytes. - */ - protected void assertPRF(byte[] P) { - if (prf == null) { - prf = new MacBasedPRF(parameters.getHashAlgorithm()); - } - prf.init(P); - } - - /** - * Method getPseudoRandomFunction. - - - * @return PRF * @see fr.xephi.authme.security.pbkdf2.PBKDF2#getPseudoRandomFunction() */ - public PRF getPseudoRandomFunction() { - return prf; - } - - /** - * Core Password Based Key Derivation Function 2. - * - - * @param prf - * Pseudo Random Function (i.e. HmacSHA1) - * @param S - * Salt as array of bytes. null means no salt. - * @param c - * Iteration count (see RFC 2898 4.2) - * @param dkLen - * desired length of derived key. - - * @return internal byte array * @see RFC 2898 5.2 */ - protected byte[] PBKDF2(PRF prf, byte[] S, int c, int dkLen) { - if (S == null) { - S = new byte[0]; - } - int hLen = prf.getHLen(); - int l = ceil(dkLen, hLen); - int r = dkLen - (l - 1) * hLen; - byte T[] = new byte[l * hLen]; - int ti_offset = 0; - for (int i = 1; i <= l; i++) { - _F(T, ti_offset, prf, S, c, i); - ti_offset += hLen; - } - if (r < hLen) { - // Incomplete last block - byte DK[] = new byte[dkLen]; - System.arraycopy(T, 0, DK, 0, dkLen); - return DK; - } - return T; - } - - /** - * Integer division with ceiling function. - * - - * @param a - * @param b - - * @return ceil(a/b) * @see RFC 2898 5.2 Step - * 2. */ - protected int ceil(int a, int b) { - int m = 0; - if (a % b > 0) { - m = 1; - } - return a / b + m; - } - - /** - * Function F. - * - - * @param dest - * Destination byte buffer - * @param offset - * Offset into destination byte buffer - * @param prf - * Pseudo Random Function - * @param S - * Salt as array of bytes - * @param c - * Iteration count - * @param blockIndex - * @see RFC 2898 5.2 Step - * 3. */ - protected void _F(byte[] dest, int offset, PRF prf, byte[] S, int c, - int blockIndex) { - int hLen = prf.getHLen(); - byte U_r[] = new byte[hLen]; - - // U0 = S || INT (i); - byte U_i[] = new byte[S.length + 4]; - System.arraycopy(S, 0, U_i, 0, S.length); - INT(U_i, S.length, blockIndex); - - for (int i = 0; i < c; i++) { - U_i = prf.doFinal(U_i); - xor(U_r, U_i); - } - System.arraycopy(U_r, 0, dest, offset, hLen); - } - - /** - * Block-Xor. Xor source bytes into destination byte buffer. Destination - * buffer must be same length or less than source buffer. - * - * @param dest - * @param src - */ - protected void xor(byte[] dest, byte[] src) { - for (int i = 0; i < dest.length; i++) { - dest[i] ^= src[i]; - } - } - - /** - * Four-octet encoding of the integer i, most significant octet first. - * - - * @param dest - * @param offset - * @param i - * @see RFC 2898 5.2 Step - * 3. */ - protected void INT(byte[] dest, int offset, int i) { - dest[offset + 0] = (byte) (i / (256 * 256 * 256)); - dest[offset + 1] = (byte) (i / (256 * 256)); - dest[offset + 2] = (byte) (i / (256)); - dest[offset + 3] = (byte) (i); - } - - /** - * Method getParameters. - - - * @return PBKDF2Parameters * @see fr.xephi.authme.security.pbkdf2.PBKDF2#getParameters() */ - public PBKDF2Parameters getParameters() { - return parameters; - } - - /** - * Method setParameters. - * @param parameters PBKDF2Parameters - - * @see fr.xephi.authme.security.pbkdf2.PBKDF2#setParameters(PBKDF2Parameters) */ - public void setParameters(PBKDF2Parameters parameters) { - this.parameters = parameters; - } - - /** - * Method setPseudoRandomFunction. - * @param prf PRF - - * @see fr.xephi.authme.security.pbkdf2.PBKDF2#setPseudoRandomFunction(PRF) */ - public void setPseudoRandomFunction(PRF prf) { - this.prf = prf; - } - /** * Convenience client function. Convert supplied password with random 8-byte * salt and 1000 iterations using HMacSHA1. Assume that password is in * ISO-8559-1 encoding. Output result as * "Salt:iteration-count:PBKDF2" with binary data in hexadecimal * encoding. - * + *

* Example: Password "password" (without the quotes) leads to * 48290A0B96C426C3:1000:973899B1D4AFEB3ED371060D0797E0EE0142BD04 - * - * @param args - * Supply the password as argument. - - + * + * @param args Supply the password as argument. * @throws IOException * @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException */ public static void main(String[] args) @@ -394,4 +157,225 @@ public class PBKDF2Engine implements PBKDF2 { System.exit(verifyOK ? 0 : 1); } } + + /** + * Method deriveKey. + * + * @param inputPassword String + * @return byte[] * @see fr.xephi.authme.security.pbkdf2.PBKDF2#deriveKey(String) + */ + public byte[] deriveKey(String inputPassword) { + return deriveKey(inputPassword, 0); + } + + /** + * Method deriveKey. + * + * @param inputPassword String + * @param dkLen int + * @return byte[] * @see fr.xephi.authme.security.pbkdf2.PBKDF2#deriveKey(String, int) + */ + public byte[] deriveKey(String inputPassword, int dkLen) { + byte[] r = null; + byte P[] = null; + String charset = parameters.getHashCharset(); + if (inputPassword == null) { + inputPassword = ""; + } + try { + if (charset == null) { + P = inputPassword.getBytes(); + } else { + P = inputPassword.getBytes(charset); + } + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + assertPRF(P); + if (dkLen == 0) { + dkLen = prf.getHLen(); + } + r = PBKDF2(prf, parameters.getSalt(), parameters.getIterationCount(), dkLen); + return r; + } + + /** + * Method verifyKey. + * + * @param inputPassword String + * @return boolean * @see fr.xephi.authme.security.pbkdf2.PBKDF2#verifyKey(String) + */ + public boolean verifyKey(String inputPassword) { + byte[] referenceKey = getParameters().getDerivedKey(); + if (referenceKey == null || referenceKey.length == 0) { + return false; + } + byte[] inputKey = deriveKey(inputPassword, referenceKey.length); + + if (inputKey == null || inputKey.length != referenceKey.length) { + return false; + } + for (int i = 0; i < inputKey.length; i++) { + if (inputKey[i] != referenceKey[i]) { + return false; + } + } + return true; + } + + /** + * Factory method. Default implementation is (H)MAC-based. To be overridden + * in derived classes. + * + * @param P User-supplied candidate password as array of bytes. + */ + protected void assertPRF(byte[] P) { + if (prf == null) { + prf = new MacBasedPRF(parameters.getHashAlgorithm()); + } + prf.init(P); + } + + /** + * Method getPseudoRandomFunction. + * + * @return PRF * @see fr.xephi.authme.security.pbkdf2.PBKDF2#getPseudoRandomFunction() + */ + public PRF getPseudoRandomFunction() { + return prf; + } + + /** + * Method setPseudoRandomFunction. + * + * @param prf PRF + * @see fr.xephi.authme.security.pbkdf2.PBKDF2#setPseudoRandomFunction(PRF) + */ + public void setPseudoRandomFunction(PRF prf) { + this.prf = prf; + } + + /** + * Core Password Based Key Derivation Function 2. + * + * @param prf Pseudo Random Function (i.e. HmacSHA1) + * @param S Salt as array of bytes. null means no salt. + * @param c Iteration count (see RFC 2898 4.2) + * @param dkLen desired length of derived key. + * @return internal byte array * @see RFC 2898 5.2 + */ + protected byte[] PBKDF2(PRF prf, byte[] S, int c, int dkLen) { + if (S == null) { + S = new byte[0]; + } + int hLen = prf.getHLen(); + int l = ceil(dkLen, hLen); + int r = dkLen - (l - 1) * hLen; + byte T[] = new byte[l * hLen]; + int ti_offset = 0; + for (int i = 1; i <= l; i++) { + _F(T, ti_offset, prf, S, c, i); + ti_offset += hLen; + } + if (r < hLen) { + // Incomplete last block + byte DK[] = new byte[dkLen]; + System.arraycopy(T, 0, DK, 0, dkLen); + return DK; + } + return T; + } + + /** + * Integer division with ceiling function. + * + * @param a + * @param b + * @return ceil(a/b) * @see RFC 2898 5.2 Step + * 2. + */ + protected int ceil(int a, int b) { + int m = 0; + if (a % b > 0) { + m = 1; + } + return a / b + m; + } + + /** + * Function F. + * + * @param dest Destination byte buffer + * @param offset Offset into destination byte buffer + * @param prf Pseudo Random Function + * @param S Salt as array of bytes + * @param c Iteration count + * @param blockIndex + * @see RFC 2898 5.2 Step + * 3. + */ + protected void _F(byte[] dest, int offset, PRF prf, byte[] S, int c, + int blockIndex) { + int hLen = prf.getHLen(); + byte U_r[] = new byte[hLen]; + + // U0 = S || INT (i); + byte U_i[] = new byte[S.length + 4]; + System.arraycopy(S, 0, U_i, 0, S.length); + INT(U_i, S.length, blockIndex); + + for (int i = 0; i < c; i++) { + U_i = prf.doFinal(U_i); + xor(U_r, U_i); + } + System.arraycopy(U_r, 0, dest, offset, hLen); + } + + /** + * Block-Xor. Xor source bytes into destination byte buffer. Destination + * buffer must be same length or less than source buffer. + * + * @param dest + * @param src + */ + protected void xor(byte[] dest, byte[] src) { + for (int i = 0; i < dest.length; i++) { + dest[i] ^= src[i]; + } + } + + /** + * Four-octet encoding of the integer i, most significant octet first. + * + * @param dest + * @param offset + * @param i + * @see RFC 2898 5.2 Step + * 3. + */ + protected void INT(byte[] dest, int offset, int i) { + dest[offset + 0] = (byte) (i / (256 * 256 * 256)); + dest[offset + 1] = (byte) (i / (256 * 256)); + dest[offset + 2] = (byte) (i / (256)); + dest[offset + 3] = (byte) (i); + } + + /** + * Method getParameters. + * + * @return PBKDF2Parameters * @see fr.xephi.authme.security.pbkdf2.PBKDF2#getParameters() + */ + public PBKDF2Parameters getParameters() { + return parameters; + } + + /** + * Method setParameters. + * + * @param parameters PBKDF2Parameters + * @see fr.xephi.authme.security.pbkdf2.PBKDF2#setParameters(PBKDF2Parameters) + */ + public void setParameters(PBKDF2Parameters parameters) { + this.parameters = parameters; + } } diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Formatter.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Formatter.java index 68f3fa6c..52e1c85c 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Formatter.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Formatter.java @@ -27,7 +27,7 @@ package fr.xephi.authme.security.pbkdf2; * href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" * >http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. *

- * + * * @author Matthias Gärtner * @version 1.0 */ @@ -35,22 +35,20 @@ public interface PBKDF2Formatter { /** * Convert parameters to String. - * - * @param p - * Parameters object to output. - - * @return String representation */ + * + * @param p Parameters object to output. + * @return String representation + */ public abstract String toString(PBKDF2Parameters p); /** * Convert String to parameters. Depending on actual implementation, it may * be required to set further fields externally. - * - * @param s - * String representation of parameters to decode. - + * + * @param s String representation of parameters to decode. * @param p PBKDF2Parameters * @return false syntax OK, true some syntax - * issue. */ + * issue. + */ public abstract boolean fromString(PBKDF2Parameters p, String s); } diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2HexFormatter.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2HexFormatter.java index 8ae057fd..32f393bd 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2HexFormatter.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2HexFormatter.java @@ -27,7 +27,7 @@ package fr.xephi.authme.security.pbkdf2; * href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" * >http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. *

- * + * * @author Matthias Gärtner * @version 1.0 */ @@ -35,11 +35,11 @@ public class PBKDF2HexFormatter implements PBKDF2Formatter { /** * Method fromString. + * * @param p PBKDF2Parameters * @param s String - - - * @return boolean * @see fr.xephi.authme.security.pbkdf2.PBKDF2Formatter#fromString(PBKDF2Parameters, String) */ + * @return boolean * @see fr.xephi.authme.security.pbkdf2.PBKDF2Formatter#fromString(PBKDF2Parameters, String) + */ public boolean fromString(PBKDF2Parameters p, String s) { if (p == null || s == null) { return true; @@ -62,10 +62,10 @@ public class PBKDF2HexFormatter implements PBKDF2Formatter { /** * Method toString. + * * @param p PBKDF2Parameters - - - * @return String * @see fr.xephi.authme.security.pbkdf2.PBKDF2Formatter#toString(PBKDF2Parameters) */ + * @return String * @see fr.xephi.authme.security.pbkdf2.PBKDF2Formatter#toString(PBKDF2Parameters) + */ public String toString(PBKDF2Parameters p) { String s = BinTools.bin2hex(p.getSalt()) + ":" + String.valueOf(p.getIterationCount()) + ":" + BinTools.bin2hex(p.getDerivedKey()); return s; diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Parameters.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Parameters.java index 5dde7a99..d962648b 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Parameters.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Parameters.java @@ -4,7 +4,7 @@ package fr.xephi.authme.security.pbkdf2; *

* Parameter data holder for PBKDF2 configuration. *

- * + *

*


*

* A free Java implementation of Password Based Key Derivation Function 2 as @@ -32,7 +32,7 @@ package fr.xephi.authme.security.pbkdf2; * href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" * >http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. *

- * + * * @author Matthias Gärtner * @version 1.0 */ @@ -55,7 +55,6 @@ public class PBKDF2Parameters { /** * Constructor. Defaults to null for byte arrays, UTF-8 as * character set and 1000 for iteration count. - * */ public PBKDF2Parameters() { this.hashAlgorithm = null; @@ -67,18 +66,14 @@ public class PBKDF2Parameters { /** * Constructor. - * - * @param hashAlgorithm - * for example HMacSHA1 or HMacMD5 - * @param hashCharset - * for example UTF-8 - * @param salt - * Salt as byte array, may be null (not recommended) - * @param iterationCount - * Number of iterations to execute. Recommended value 1000. + * + * @param hashAlgorithm for example HMacSHA1 or HMacMD5 + * @param hashCharset for example UTF-8 + * @param salt Salt as byte array, may be null (not recommended) + * @param iterationCount Number of iterations to execute. Recommended value 1000. */ public PBKDF2Parameters(String hashAlgorithm, String hashCharset, - byte[] salt, int iterationCount) { + byte[] salt, int iterationCount) { this.hashAlgorithm = hashAlgorithm; this.hashCharset = hashCharset; this.salt = salt; @@ -88,20 +83,15 @@ public class PBKDF2Parameters { /** * Constructor. - * - * @param hashAlgorithm - * for example HMacSHA1 or HMacMD5 - * @param hashCharset - * for example UTF-8 - * @param salt - * Salt as byte array, may be null (not recommended) - * @param iterationCount - * Number of iterations to execute. Recommended value 1000. - * @param derivedKey - * Convenience data holder, not used during computation. + * + * @param hashAlgorithm for example HMacSHA1 or HMacMD5 + * @param hashCharset for example UTF-8 + * @param salt Salt as byte array, may be null (not recommended) + * @param iterationCount Number of iterations to execute. Recommended value 1000. + * @param derivedKey Convenience data holder, not used during computation. */ public PBKDF2Parameters(String hashAlgorithm, String hashCharset, - byte[] salt, int iterationCount, byte[] derivedKey) { + byte[] salt, int iterationCount, byte[] derivedKey) { this.hashAlgorithm = hashAlgorithm; this.hashCharset = hashCharset; this.salt = salt; @@ -111,14 +101,16 @@ public class PBKDF2Parameters { /** * Method getIterationCount. - - * @return int */ + * + * @return int + */ public int getIterationCount() { return iterationCount; } /** * Method setIterationCount. + * * @param iterationCount int */ public void setIterationCount(int iterationCount) { @@ -127,14 +119,16 @@ public class PBKDF2Parameters { /** * Method getSalt. - - * @return byte[] */ + * + * @return byte[] + */ public byte[] getSalt() { return salt; } /** * Method setSalt. + * * @param salt byte[] */ public void setSalt(byte[] salt) { @@ -143,14 +137,16 @@ public class PBKDF2Parameters { /** * Method getDerivedKey. - - * @return byte[] */ + * + * @return byte[] + */ public byte[] getDerivedKey() { return derivedKey; } /** * Method setDerivedKey. + * * @param derivedKey byte[] */ public void setDerivedKey(byte[] derivedKey) { @@ -159,14 +155,16 @@ public class PBKDF2Parameters { /** * Method getHashAlgorithm. - - * @return String */ + * + * @return String + */ public String getHashAlgorithm() { return hashAlgorithm; } /** * Method setHashAlgorithm. + * * @param hashAlgorithm String */ public void setHashAlgorithm(String hashAlgorithm) { @@ -175,14 +173,16 @@ public class PBKDF2Parameters { /** * Method getHashCharset. - - * @return String */ + * + * @return String + */ public String getHashCharset() { return hashCharset; } /** * Method setHashCharset. + * * @param hashCharset String */ public void setHashCharset(String hashCharset) { diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PRF.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PRF.java index c7dbf547..42c64600 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PRF.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PRF.java @@ -27,7 +27,7 @@ package fr.xephi.authme.security.pbkdf2; * href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" * >http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. *

- * + * * @author Matthias Gärtner * @version 1.0 */ @@ -35,27 +35,25 @@ public interface PRF { /** * Initialize this instance with the user-supplied password. - * - * @param P - * The password supplied as array of bytes. It is the caller's - * task to convert String passwords to bytes as appropriate. + * + * @param P The password supplied as array of bytes. It is the caller's + * task to convert String passwords to bytes as appropriate. */ public void init(byte[] P); /** * Pseudo Random Function - * - * @param M - * Input data/message etc. Together with any data supplied during - * initilization. - - * @return Random bytes of hLen length. */ + * + * @param M Input data/message etc. Together with any data supplied during + * initilization. + * @return Random bytes of hLen length. + */ public byte[] doFinal(byte[] M); /** * Query block size of underlying algorithm/mechanism. - * - - * @return block size */ + * + * @return block size + */ public int getHLen(); } diff --git a/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java b/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java index 15e651aa..757bfe33 100644 --- a/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java +++ b/src/main/java/fr/xephi/authme/settings/CustomConfiguration.java @@ -1,5 +1,10 @@ package fr.xephi.authme.settings; +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.ConsoleLogger; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -7,12 +12,6 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.StandardCopyOption; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; - -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.ConsoleLogger; - /** */ public abstract class CustomConfiguration extends YamlConfiguration { @@ -21,6 +20,7 @@ public abstract class CustomConfiguration extends YamlConfiguration { /** * Constructor for CustomConfiguration. + * * @param file the config file */ public CustomConfiguration(File file) { diff --git a/src/main/java/fr/xephi/authme/settings/Messages.java b/src/main/java/fr/xephi/authme/settings/Messages.java index 15b10c4d..32a5192b 100644 --- a/src/main/java/fr/xephi/authme/settings/Messages.java +++ b/src/main/java/fr/xephi/authme/settings/Messages.java @@ -1,10 +1,9 @@ package fr.xephi.authme.settings; -import java.io.File; - +import fr.xephi.authme.ConsoleLogger; import org.bukkit.command.CommandSender; -import fr.xephi.authme.ConsoleLogger; +import java.io.File; /** */ @@ -15,6 +14,7 @@ public class Messages extends CustomConfiguration { /** * Constructor for Messages. + * * @param file the configuration file * @param lang the code of the language to use */ @@ -25,6 +25,13 @@ public class Messages extends CustomConfiguration { this.lang = lang; } + public static Messages getInstance() { + if (singleton == null) { + singleton = new Messages(Settings.messageFile, Settings.messagesLanguage); + } + return singleton; + } + public void send(CommandSender sender, String msg) { if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.lang)) { singleton.reloadMessages(); @@ -62,13 +69,6 @@ public class Messages extends CustomConfiguration { return loc; } - public static Messages getInstance() { - if (singleton == null) { - singleton = new Messages(Settings.messageFile, Settings.messagesLanguage); - } - return singleton; - } - public void reloadMessages() { singleton = new Messages(Settings.messageFile, Settings.messagesLanguage); } diff --git a/src/main/java/fr/xephi/authme/settings/OtherAccounts.java b/src/main/java/fr/xephi/authme/settings/OtherAccounts.java index dcaf3c00..9dee23d1 100644 --- a/src/main/java/fr/xephi/authme/settings/OtherAccounts.java +++ b/src/main/java/fr/xephi/authme/settings/OtherAccounts.java @@ -1,15 +1,14 @@ package fr.xephi.authme.settings; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - /** - * * @author Xephi59 * @version $Revision: 1.0 $ */ @@ -24,19 +23,11 @@ public class OtherAccounts extends CustomConfiguration { save(); } - /** - * Method clear. - * @param uuid UUID - */ - public void clear(UUID uuid) { - set(uuid.toString(), new ArrayList()); - save(); - } - /** * Method getInstance. - - * @return OtherAccounts */ + * + * @return OtherAccounts + */ public static OtherAccounts getInstance() { if (others == null) { others = new OtherAccounts(); @@ -44,8 +35,19 @@ public class OtherAccounts extends CustomConfiguration { return others; } + /** + * Method clear. + * + * @param uuid UUID + */ + public void clear(UUID uuid) { + set(uuid.toString(), new ArrayList()); + save(); + } + /** * Method addPlayer. + * * @param uuid UUID */ public void addPlayer(UUID uuid) { @@ -64,6 +66,7 @@ public class OtherAccounts extends CustomConfiguration { /** * Method removePlayer. + * * @param uuid UUID */ public void removePlayer(UUID uuid) { @@ -82,9 +85,10 @@ public class OtherAccounts extends CustomConfiguration { /** * Method getAllPlayersByUUID. + * * @param uuid UUID - - * @return List */ + * @return List + */ public List getAllPlayersByUUID(UUID uuid) { return this.getStringList(uuid.toString()); } diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index 94f032d8..49a78db0 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -1,40 +1,29 @@ package fr.xephi.authme.settings; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.bukkit.configuration.file.YamlConfiguration; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource.DataSourceType; import fr.xephi.authme.security.HashAlgorithm; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; /** */ public final class Settings extends YamlConfiguration { - private static AuthMe plugin; - private static Settings instance; - - // This is not an option! - public static boolean antiBotInAction = false; - public static final File PLUGIN_FOLDER = AuthMe.getInstance().getDataFolder(); public static final File MODULE_FOLDER = new File(PLUGIN_FOLDER, "modules"); public static final File CACHE_FOLDER = new File(PLUGIN_FOLDER, "cache"); public static final File AUTH_FILE = new File(PLUGIN_FOLDER, "auths.db"); public static final File SETTINGS_FILE = new File(PLUGIN_FOLDER, "config.yml"); public static final File LOG_FILE = new File(PLUGIN_FOLDER, "authme.log"); - + // This is not an option! + public static boolean antiBotInAction = false; public static File messageFile; public static List allowCommands; public static List getJoinPermissions; @@ -56,10 +45,8 @@ public final class Settings extends YamlConfiguration { public static HashAlgorithm getPasswordHash; public static boolean useLogging = false; public static int purgeDelay = 60; - // Due to compatibility issues with plugins like FactionsChat public static Boolean isChatAllowed; - public static boolean isPermissionCheckEnabled, isRegistrationEnabled, isForcedRegistrationEnabled, isTeleportToSpawnEnabled, isSessionsEnabled, isAllowRestrictedIp, @@ -80,7 +67,6 @@ public final class Settings extends YamlConfiguration { broadcastWelcomeMessage, forceRegKick, forceRegLogin, checkVeryGames, delayJoinLeaveMessages, noTeleport, applyBlindEffect, customAttributes, generateImage, isRemoveSpeedEnabled, isMySQLWebsite; - public static String getNickRegex, getUnloggedinGroup, getMySQLHost, getMySQLPort, getMySQLUsername, getMySQLPassword, getMySQLDatabase, getMySQLTablename, getMySQLColumnName, getMySQLColumnPassword, @@ -94,7 +80,6 @@ public final class Settings extends YamlConfiguration { getPhpbbPrefix, getWordPressPrefix, getMySQLColumnLogged, spawnPriority, crazyloginFileName, getPassRegex, getMySQLColumnRealName; - public static int getWarnMessageInterval, getSessionTimeout, getRegistrationTimeout, getMaxNickLength, getMinNickLength, getPasswordMinLen, getMovementRadius, getmaxRegPerIp, @@ -103,11 +88,13 @@ public final class Settings extends YamlConfiguration { getmaxRegPerEmail, bCryptLog2Rounds, getPhpbbGroup, antiBotSensibility, antiBotDuration, delayRecall, getMaxLoginPerIp, getMaxJoinPerIp, getMySQLMaxConnections; - protected static YamlConfiguration configFile; + private static AuthMe plugin; + private static Settings instance; /** * Constructor for Settings. + * * @param pl AuthMe */ public Settings(AuthMe pl) { @@ -118,8 +105,9 @@ public final class Settings extends YamlConfiguration { /** * Method reload. - - * @throws Exception */ + * + * @throws Exception + */ public static void reload() throws Exception { plugin.getLogger().info("Loading Configuration File..."); boolean exist = SETTINGS_FILE.exists(); @@ -302,6 +290,189 @@ public final class Settings extends YamlConfiguration { } + /** + * Method setValue. + * + * @param key String + * @param value Object + */ + public static void setValue(String key, Object value) { + instance.set(key, value); + save(); + } + + /** + * Method getPasswordHash. + * + * @return HashAlgorithm + */ + private static HashAlgorithm getPasswordHash() { + String key = "settings.security.passwordHash"; + try { + return HashAlgorithm.valueOf(configFile.getString(key, "SHA256").toUpperCase()); + } catch (IllegalArgumentException ex) { + ConsoleLogger.showError("Unknown Hash Algorithm; defaulting to SHA256"); + return HashAlgorithm.SHA256; + } + } + + /** + * Method getDataSource. + * + * @return DataSourceType + */ + private static DataSourceType getDataSource() { + String key = "DataSource.backend"; + try { + return DataSource.DataSourceType.valueOf(configFile.getString(key, "sqlite").toUpperCase()); + } catch (IllegalArgumentException ex) { + ConsoleLogger.showError("Unknown database backend; defaulting to SQLite database"); + return DataSource.DataSourceType.SQLITE; + } + } + + /** + * Config option for setting and check restricted user by username;ip , + * return false if ip and name doesn't match with player that join the + * server, so player has a restricted access + * + * @param name String + * @param ip String + * @return boolean + */ + public static boolean getRestrictedIp(String name, String ip) { + + Iterator iterator = getRestrictedIp.iterator(); + boolean trueOnce = false; + boolean nameFound = false; + while (iterator.hasNext()) { + String[] args = iterator.next().split(";"); + String testName = args[0]; + String testIp = args[1]; + if (testName.equalsIgnoreCase(name)) { + nameFound = true; + if (testIp.equalsIgnoreCase(ip)) { + trueOnce = true; + } + } + } + return !nameFound || trueOnce; + } + + /** + * Saves the configuration to disk + * + * @return True if saved successfully + */ + public static boolean save() { + try { + instance.save(SETTINGS_FILE); + return true; + } catch (Exception ex) { + return false; + } + } + + /** + * Method checkLang. + * + * @param lang String + * @return String + */ + public static String checkLang(String lang) { + if (new File(PLUGIN_FOLDER, "messages" + File.separator + "messages_" + lang + ".yml").exists()) { + ConsoleLogger.info("Set Language to: " + lang); + return lang; + } + if (AuthMe.class.getResourceAsStream("/messages/messages_" + lang + ".yml") != null) { + ConsoleLogger.info("Set Language to: " + lang); + return lang; + } + ConsoleLogger.info("Language file not found for " + lang + ", set to default language: en !"); + return "en"; + } + + /** + * Method switchAntiBotMod. + * + * @param mode boolean + */ + public static void switchAntiBotMod(boolean mode) { + if (mode) { + isKickNonRegisteredEnabled = true; + antiBotInAction = true; + } else { + isKickNonRegisteredEnabled = configFile.getBoolean("settings.restrictions.kickNonRegistered", false); + antiBotInAction = false; + } + } + + private static void getWelcomeMessage() { + AuthMe plugin = AuthMe.getInstance(); + welcomeMsg = new ArrayList<>(); + if (!useWelcomeMessage) { + return; + } + if (!(new File(plugin.getDataFolder() + File.separator + "welcome.txt").exists())) { + try { + FileWriter fw = new FileWriter(plugin.getDataFolder() + File.separator + "welcome.txt", true); + BufferedWriter w = new BufferedWriter(fw); + w.write("Welcome {PLAYER} on {SERVER} server"); + w.newLine(); + w.write("This server uses " + AuthMe.getPluginName() + " protection!"); + w.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + try { + FileReader fr = new FileReader(plugin.getDataFolder() + File.separator + "welcome.txt"); + BufferedReader br = new BufferedReader(fr); + String line; + while ((line = br.readLine()) != null) { + welcomeMsg.add(line); + } + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Method isEmailCorrect. + * + * @param email String + * @return boolean + */ + public static boolean isEmailCorrect(String email) { + if (!email.contains("@")) + return false; + if (email.equalsIgnoreCase("your@email.com")) + return false; + String emailDomain = email.split("@")[1]; + boolean correct = true; + if (emailWhitelist != null && !emailWhitelist.isEmpty()) { + for (String domain : emailWhitelist) { + if (!domain.equalsIgnoreCase(emailDomain)) { + correct = false; + } else { + correct = true; + break; + } + } + return correct; + } + if (emailBlacklist != null && !emailBlacklist.isEmpty()) { + for (String domain : emailBlacklist) { + if (domain.equalsIgnoreCase(emailDomain)) { + correct = false; + break; + } + } + } + return correct; + } + public void mergeConfig() { boolean changes = false; if (contains("Xenoforo.predefinedSalt")) { @@ -512,92 +683,13 @@ public final class Settings extends YamlConfiguration { } } - /** - * Method setValue. - * @param key String - * @param value Object - */ - public static void setValue(String key, Object value) { - instance.set(key, value); - save(); - } - - /** - * Method getPasswordHash. - - * @return HashAlgorithm */ - private static HashAlgorithm getPasswordHash() { - String key = "settings.security.passwordHash"; - try { - return HashAlgorithm.valueOf(configFile.getString(key, "SHA256").toUpperCase()); - } catch (IllegalArgumentException ex) { - ConsoleLogger.showError("Unknown Hash Algorithm; defaulting to SHA256"); - return HashAlgorithm.SHA256; - } - } - - /** - * Method getDataSource. - - * @return DataSourceType */ - private static DataSourceType getDataSource() { - String key = "DataSource.backend"; - try { - return DataSource.DataSourceType.valueOf(configFile.getString(key, "sqlite").toUpperCase()); - } catch (IllegalArgumentException ex) { - ConsoleLogger.showError("Unknown database backend; defaulting to SQLite database"); - return DataSource.DataSourceType.SQLITE; - } - } - - /** - * Config option for setting and check restricted user by username;ip , - * return false if ip and name doesn't match with player that join the - * server, so player has a restricted access - * @param name String - * @param ip String - - * @return boolean */ - public static boolean getRestrictedIp(String name, String ip) { - - Iterator iterator = getRestrictedIp.iterator(); - boolean trueOnce = false; - boolean nameFound = false; - while(iterator.hasNext()) { - String[] args = iterator.next().split(";"); - String testName = args[0]; - String testIp = args[1]; - if (testName.equalsIgnoreCase(name)) { - nameFound = true; - if (testIp.equalsIgnoreCase(ip)) { - trueOnce = true; - } - } - } - return !nameFound || trueOnce; - } - - /** - * Saves the configuration to disk - * - - * @return True if saved successfully */ - public static boolean save() { - try { - instance.save(SETTINGS_FILE); - return true; - } catch (Exception ex) { - return false; - } - } - /** * Saves current configuration (plus defaults) to disk. *

* If defaults and configuration are empty, saves blank file. * - - * @return True if saved successfully */ + * @return True if saved successfully + */ public final boolean saveDefaults() { options().copyDefaults(true); options().copyHeader(true); @@ -606,101 +698,4 @@ public final class Settings extends YamlConfiguration { options().copyHeader(false); return success; } - - /** - * Method checkLang. - * @param lang String - - * @return String */ - public static String checkLang(String lang) { - if (new File(PLUGIN_FOLDER, "messages" + File.separator + "messages_" + lang + ".yml").exists()) { - ConsoleLogger.info("Set Language to: " + lang); - return lang; - } - if (AuthMe.class.getResourceAsStream("/messages/messages_" + lang + ".yml") != null) { - ConsoleLogger.info("Set Language to: " + lang); - return lang; - } - ConsoleLogger.info("Language file not found for " + lang + ", set to default language: en !"); - return "en"; - } - - /** - * Method switchAntiBotMod. - * @param mode boolean - */ - public static void switchAntiBotMod(boolean mode) { - if (mode) { - isKickNonRegisteredEnabled = true; - antiBotInAction = true; - } else { - isKickNonRegisteredEnabled = configFile.getBoolean("settings.restrictions.kickNonRegistered", false); - antiBotInAction = false; - } - } - - private static void getWelcomeMessage() { - AuthMe plugin = AuthMe.getInstance(); - welcomeMsg = new ArrayList<>(); - if (!useWelcomeMessage) { - return; - } - if (!(new File(plugin.getDataFolder() + File.separator + "welcome.txt").exists())) { - try { - FileWriter fw = new FileWriter(plugin.getDataFolder() + File.separator + "welcome.txt", true); - BufferedWriter w = new BufferedWriter(fw); - w.write("Welcome {PLAYER} on {SERVER} server"); - w.newLine(); - w.write("This server uses " + AuthMe.PLUGIN_NAME + " protection!"); - w.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - try { - FileReader fr = new FileReader(plugin.getDataFolder() + File.separator + "welcome.txt"); - BufferedReader br = new BufferedReader(fr); - String line; - while ((line = br.readLine()) != null) { - welcomeMsg.add(line); - } - br.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Method isEmailCorrect. - * @param email String - - * @return boolean */ - public static boolean isEmailCorrect(String email) { - if (!email.contains("@")) - return false; - if (email.equalsIgnoreCase("your@email.com")) - return false; - String emailDomain = email.split("@")[1]; - boolean correct = true; - if (emailWhitelist != null && !emailWhitelist.isEmpty()) { - for (String domain : emailWhitelist) { - if (!domain.equalsIgnoreCase(emailDomain)) { - correct = false; - } else { - correct = true; - break; - } - } - return correct; - } - if (emailBlacklist != null && !emailBlacklist.isEmpty()) { - for (String domain : emailBlacklist) { - if (domain.equalsIgnoreCase(emailDomain)) { - correct = false; - break; - } - } - } - return correct; - } } diff --git a/src/main/java/fr/xephi/authme/settings/Spawn.java b/src/main/java/fr/xephi/authme/settings/Spawn.java index 0671ceb4..a37e90ce 100644 --- a/src/main/java/fr/xephi/authme/settings/Spawn.java +++ b/src/main/java/fr/xephi/authme/settings/Spawn.java @@ -1,12 +1,11 @@ package fr.xephi.authme.settings; -import java.io.File; - import org.bukkit.Bukkit; import org.bukkit.Location; +import java.io.File; + /** - * * @author Xephi59 * @version $Revision: 1.0 $ */ @@ -22,6 +21,18 @@ public class Spawn extends CustomConfiguration { saveDefault(); } + /** + * Method getInstance. + * + * @return Spawn + */ + public static Spawn getInstance() { + if (spawn == null) { + spawn = new Spawn(); + } + return spawn; + } + private void saveDefault() { if (!contains("spawn")) { set("spawn.world", ""); @@ -43,22 +54,12 @@ public class Spawn extends CustomConfiguration { } } - /** - * Method getInstance. - - * @return Spawn */ - public static Spawn getInstance() { - if (spawn == null) { - spawn = new Spawn(); - } - return spawn; - } - /** * Method setSpawn. + * * @param location Location - - * @return boolean */ + * @return boolean + */ public boolean setSpawn(Location location) { try { set("spawn.world", location.getWorld().getName()); @@ -76,9 +77,10 @@ public class Spawn extends CustomConfiguration { /** * Method setFirstSpawn. + * * @param location Location - - * @return boolean */ + * @return boolean + */ public boolean setFirstSpawn(Location location) { try { set("firstspawn.world", location.getWorld().getName()); @@ -96,8 +98,9 @@ public class Spawn extends CustomConfiguration { /** * Method getLocation. - - * @return Location */ + * + * @return Location + */ @Deprecated public Location getLocation() { return getSpawn(); @@ -105,8 +108,9 @@ public class Spawn extends CustomConfiguration { /** * Method getSpawn. - - * @return Location */ + * + * @return Location + */ public Location getSpawn() { try { if (this.getString("spawn.world").isEmpty() || this.getString("spawn.world").equals("")) @@ -120,8 +124,9 @@ public class Spawn extends CustomConfiguration { /** * Method getFirstSpawn. - - * @return Location */ + * + * @return Location + */ public Location getFirstSpawn() { try { if (this.getString("firstspawn.world").isEmpty() || this.getString("firstspawn.world").equals("")) diff --git a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java index 77a1155e..c7929b6e 100644 --- a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java +++ b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java @@ -1,9 +1,5 @@ package fr.xephi.authme.task; -import java.security.NoSuchAlgorithmException; - -import org.bukkit.entity.Player; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; @@ -11,6 +7,9 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; +import org.bukkit.entity.Player; + +import java.security.NoSuchAlgorithmException; /** */ @@ -23,8 +22,9 @@ public class ChangePasswordTask implements Runnable { /** * Constructor for ChangePasswordTask. - * @param plugin AuthMe - * @param player Player + * + * @param plugin AuthMe + * @param player Player * @param oldPassword String * @param newPassword String */ @@ -37,6 +37,7 @@ public class ChangePasswordTask implements Runnable { /** * Method run. + * * @see java.lang.Runnable#run() */ @Override diff --git a/src/main/java/fr/xephi/authme/task/MessageTask.java b/src/main/java/fr/xephi/authme/task/MessageTask.java index af947440..1540e1dc 100644 --- a/src/main/java/fr/xephi/authme/task/MessageTask.java +++ b/src/main/java/fr/xephi/authme/task/MessageTask.java @@ -1,12 +1,11 @@ package fr.xephi.authme.task; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitTask; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.util.Utils; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; /** */ @@ -19,9 +18,10 @@ public class MessageTask implements Runnable { /** * Constructor for MessageTask. - * @param plugin AuthMe - * @param name String - * @param strings String[] + * + * @param plugin AuthMe + * @param name String + * @param strings String[] * @param interval int */ public MessageTask(AuthMe plugin, String name, String[] strings, @@ -34,6 +34,7 @@ public class MessageTask implements Runnable { /** * Method run. + * * @see java.lang.Runnable#run() */ @Override diff --git a/src/main/java/fr/xephi/authme/task/TimeoutTask.java b/src/main/java/fr/xephi/authme/task/TimeoutTask.java index 4fed3583..8cbad290 100644 --- a/src/main/java/fr/xephi/authme/task/TimeoutTask.java +++ b/src/main/java/fr/xephi/authme/task/TimeoutTask.java @@ -1,11 +1,10 @@ package fr.xephi.authme.task; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.settings.Messages; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; /** */ @@ -18,8 +17,9 @@ public class TimeoutTask implements Runnable { /** * Constructor for TimeoutTask. + * * @param plugin AuthMe - * @param name String + * @param name String * @param player Player */ public TimeoutTask(AuthMe plugin, String name, Player player) { @@ -30,14 +30,16 @@ public class TimeoutTask implements Runnable { /** * Method getName. - - * @return String */ + * + * @return String + */ public String getName() { return name; } /** * Method run. + * * @see java.lang.Runnable#run() */ @Override diff --git a/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java b/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java new file mode 100644 index 00000000..76bd9d8c --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java @@ -0,0 +1,96 @@ +package fr.xephi.authme.util; + +import com.maxmind.geoip.LookupService; +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.settings.Settings; + +import java.io.*; +import java.net.URL; +import java.net.URLConnection; +import java.util.zip.GZIPInputStream; + +public class GeoLiteAPI { + + private static final String GEOIP_URL = "http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry" + + "/GeoIP.dat.gz"; + private static final AuthMe plugin = AuthMe.getInstance(); + private static LookupService lookupService; + + /** + * Download (if absent) the GeoIpLite data file and then try to load it. + * + * @return Boolean True if the data is available, false if not. + */ + public static boolean isDataAvailable() { + if (lookupService != null) { + return true; + } + final File data = new File(Settings.PLUGIN_FOLDER, "GeoIP.dat"); + if (data.exists()) { + try { + lookupService = new LookupService(data); + plugin.getLogger().info("[LICENSE] This product uses data from the GeoLite API created by MaxMind, " + + "available at http://www.maxmind.com"); + return true; + } catch (IOException e) { + return false; + } + } + // Ok, let's try to download the data file! + plugin.getGameServer().getScheduler().runTaskAsynchronously(plugin, 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(data); + 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.writeStackTrace(e); + } + } + }); + return false; + } + + /** + * Get the country code of the given IP address. + * + * @param ip Ip address + * @return String + */ + public static String getCountryCode(String ip) { + if (isDataAvailable()) { + return lookupService.getCountry(ip).getCode(); + } + return "--"; + } + + /** + * Get the country name of the given IP address. + * + * @param ip Ip address + * @return String + */ + public static String getCountryName(String ip) { + if (isDataAvailable()) { + return lookupService.getCountry(ip).getName(); + } + return "N/A"; + } + +} diff --git a/src/main/java/fr/xephi/authme/util/Profiler.java b/src/main/java/fr/xephi/authme/util/Profiler.java index 92e3c51f..0784aa65 100644 --- a/src/main/java/fr/xephi/authme/util/Profiler.java +++ b/src/main/java/fr/xephi/authme/util/Profiler.java @@ -7,9 +7,13 @@ import java.text.DecimalFormat; @SuppressWarnings("UnusedDeclaration") public class Profiler { - /** Defines the past time in milliseconds. */ + /** + * Defines the past time in milliseconds. + */ private long time = 0; - /** Defines the time in milliseconds the profiler last started at. */ + /** + * Defines the time in milliseconds the profiler last started at. + */ private long start = -1; /** @@ -26,19 +30,19 @@ public class Profiler { */ public Profiler(boolean start) { // Should the timer be started - if(start) + if (start) start(); } /** * Start the profiler. * - * @return True if the profiler was started, false otherwise possibly due to an error. - * True will also be returned if the profiler was started already. */ + * True will also be returned if the profiler was started already. + */ public boolean start() { // Make sure the timer isn't started already - if(isActive()) + if (isActive()) return true; // Set the start time @@ -49,11 +53,11 @@ public class Profiler { /** * This will start the profiler if it's not active, or will stop the profiler if it's currently active. * - - * @return True if the profiler has been started, false if the profiler has been stopped. */ + * @return True if the profiler has been started, false if the profiler has been stopped. + */ public boolean pause() { // Toggle the profiler state - if(isStarted()) + if (isStarted()) stop(); else start(); @@ -65,12 +69,12 @@ public class Profiler { /** * Stop the profiler if it's active. * - * @return True will be returned if the profiler was stopped while it was active. False will be returned if the - * profiler was stopped already. */ + * profiler was stopped already. + */ public boolean stop() { // Make sure the profiler is active - if(!isActive()) + if (!isActive()) return false; // Stop the profiler, calculate the passed time @@ -82,8 +86,8 @@ public class Profiler { /** * Check whether the profiler has been started. The profiler doesn't need to be active right now. * - - * @return True if the profiler was started, false otherwise. */ + * @return True if the profiler was started, false otherwise. + */ public boolean isStarted() { return isActive() || this.time > 0; } @@ -91,8 +95,8 @@ public class Profiler { /** * Check whether the profiler is currently active. * - - * @return True if the profiler is active, false otherwise. */ + * @return True if the profiler is active, false otherwise. + */ public boolean isActive() { return this.start >= 0; } @@ -100,11 +104,11 @@ public class Profiler { /** * Get the passed time in milliseconds. * - - * @return The passed time in milliseconds. */ + * @return The passed time in milliseconds. + */ public long getTime() { // Check whether the profiler is currently active - if(isActive()) + if (isActive()) return this.time + (System.currentTimeMillis() - this.start); return this.time; } @@ -112,18 +116,18 @@ public class Profiler { /** * Get the passed time in a formatted string. * - - * @return The passed time in a formatted string. */ + * @return The passed time in a formatted string. + */ public String getTimeFormatted() { // Get the passed time long time = getTime(); // Return the time if it's less than one millisecond - if(time <= 0) + if (time <= 0) return "<1 ms"; // Return the time in milliseconds - if(time < 1000) + if (time < 1000) return time + " ms"; // Convert the time into seconds with a single decimal diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java index c5d34c57..c5c0f6e8 100644 --- a/src/main/java/fr/xephi/authme/util/StringUtils.java +++ b/src/main/java/fr/xephi/authme/util/StringUtils.java @@ -12,17 +12,18 @@ import java.io.StringWriter; */ public class StringUtils { + public static final String newline = System.getProperty("line.separator"); + /** * Get the difference of two strings. * - * @param first First string + * @param first First string * @param second Second string - * * @return The difference value */ public static double getDifference(String first, String second) { // Make sure the strings are valid. - if(first == null || second == null) + if (first == null || second == null) return 1.0; // Create a string similarity service instance, to allow comparison @@ -35,21 +36,20 @@ public class StringUtils { /** * Returns whether the given string contains any of the provided elements. * - * @param str the string to analyze + * @param str the string to analyze * @param pieces the items to check the string for - * * @return true if the string contains at least one of the items */ public static boolean containsAny(String str, String... pieces) { - if (str == null) { - return false; - } - for (String piece : pieces) { - if (piece != null && str.contains(piece)) { - return true; - } - } - return false; + if (str == null) { + return false; + } + for (String piece : pieces) { + if (piece != null && str.contains(piece)) { + return true; + } + } + return false; } /** @@ -57,7 +57,6 @@ public class StringUtils { * is trimmed, so this method also considers a string with whitespace as empty. * * @param str the string to verify - * * @return true if the string is empty, false otherwise */ public static boolean isEmpty(String str) { @@ -68,8 +67,7 @@ public class StringUtils { * Joins a list of elements into a single string with the specified delimiter. * * @param delimiter the delimiter to use - * @param elements the elements to join - * + * @param elements the elements to join * @return a new String that is composed of the elements separated by the delimiter */ public static String join(String delimiter, Iterable elements) { @@ -91,7 +89,6 @@ public class StringUtils { * Get a full stack trace of an exception as a string. * * @param exception The exception. - * * @return Stack trace as a string. */ public static String getStackTrace(Exception exception) { diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 6623c8dc..3262f2f5 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -95,45 +95,30 @@ public final class Utils { return false; } - public static String getCountryCode(String ip) { - if (checkGeoIP()) { - return lookupService.getCountry(ip).getCode(); - } - return "--"; - } - - public static String getCountryName(String ip) { - if (checkGeoIP()) { - return lookupService.getCountry(ip).getName(); - } - return "N/A"; - } - /** * Set the group of a player, by its AuthMe group type. * * @param player The player. - * @param group The group type. - * - * @return True if succeed, false otherwise. - * False is also returned if groups aren't supported with the current permissions system. + * @param group The group type. + * @return True if succeed, false otherwise. False is also returned if groups aren't supported + * with the current permissions system. */ public static boolean setGroup(Player player, GroupType group) { // Check whether the permissions check is enabled - if(!Settings.isPermissionCheckEnabled) + if (!Settings.isPermissionCheckEnabled) return false; // Get the permissions manager, and make sure it's valid PermissionsManager permsMan = plugin.getPermissionsManager(); - if(permsMan == null) + if (permsMan == null) ConsoleLogger.showError("Failed to access permissions manager instance, shutting down."); assert permsMan != null; // Make sure group support is available - if(!permsMan.hasGroupSupport()) + if (!permsMan.hasGroupSupport()) ConsoleLogger.showError("The current permissions system doesn't have group support, unable to set group!"); - switch(group) { + switch (group) { case UNREGISTERED: // Remove the other group type groups, set the current group permsMan.removeGroups(player, Arrays.asList(Settings.getRegisteredGroup, Settings.getUnloggedinGroup)); @@ -152,7 +137,7 @@ public final class Utils { case LOGGEDIN: // Get the limbo player data LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(player.getName().toLowerCase()); - if(limbo == null) + if (limbo == null) return false; // Get the players group @@ -169,21 +154,20 @@ public final class Utils { /** * TODO: This method requires better explanation. - * + *

* Set the normal group of a player. * * @param player The player. - * @param group The normal group. - + * @param group The normal group. * @return True on success, false on failure. */ public static boolean addNormal(Player player, String group) { - if(!Settings.isPermissionCheckEnabled) + if (!Settings.isPermissionCheckEnabled) return false; // Get the permissions manager, and make sure it's valid PermissionsManager permsMan = plugin.getPermissionsManager(); - if(permsMan == null) + if (permsMan == null) ConsoleLogger.showError("Failed to access permissions manager instance, shutting down."); assert permsMan != null; @@ -221,10 +205,11 @@ public final class Utils { /** * Method packCoords. - * @param x double - * @param y double - * @param z double - * @param w String + * + * @param x double + * @param y double + * @param z double + * @param w String * @param pl Player */ public static void packCoords(double x, double y, double z, String w, @@ -265,40 +250,35 @@ public final class Utils { } /** + * Delete a given directory and all his content. + * + * @param directory File */ - public enum GroupType { - UNREGISTERED, - REGISTERED, - NOTLOGGEDIN, - LOGGEDIN - } - - public static void purgeDirectory(File file) { - if (!file.isDirectory()) { + public static void purgeDirectory(File directory) { + if (!directory.isDirectory()) { return; } - File[] files = file.listFiles(); + File[] files = directory.listFiles(); if (files == null) { return; } for (File target : files) { if (target.isDirectory()) { purgeDirectory(target); - target.delete(); - } else { - target.delete(); } + target.delete(); } } /** - * 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. + * 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 SpigotMC forum + * @see SpigotMC + * forum * @see StackOverflow */ @SuppressWarnings("unchecked") @@ -309,7 +289,7 @@ public final class Utils { try { // The lookup of a method via Reflections is rather expensive, so we keep a reference to it if (getOnlinePlayers == null) { - getOnlinePlayers = Bukkit.class.getMethod("getOnlinePlayers"); + getOnlinePlayers = Bukkit.class.getDeclaredMethod("getOnlinePlayers"); } Object obj = getOnlinePlayers.invoke(null); if (obj instanceof Collection) { @@ -328,8 +308,8 @@ public final class Utils { } /** - * Method run when the Utils class is loaded to verify whether or not the Bukkit - * implementation returns the online players as a Collection. + * Method run when the Utils class is loaded to verify whether or not the Bukkit implementation + * returns the online players as a Collection. * * @see Utils#getOnlinePlayers() */ @@ -373,4 +353,13 @@ public final class Utils { } } } + + /** + */ + public enum GroupType { + UNREGISTERED, + REGISTERED, + NOTLOGGEDIN, + LOGGEDIN + } } From cf475932feb9447d9afcffb353d271968ae2f3c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Mon, 23 Nov 2015 20:43:25 +0100 Subject: [PATCH 091/199] Added basic .editorconfig configuration --- .editorconfig | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..ae800ac6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Set the charset, and space indention +[*.java] +charset = utf-8 +indent_style = space +indent_size = 4 From 614aa9d5e5dafc071a297b047caf826d91eab563 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Mon, 23 Nov 2015 20:59:13 +0100 Subject: [PATCH 092/199] Fix noTeleport in the PlayerListener --- .../authme/listener/AuthMePlayerListener.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 9f17421f..3543acbf 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -166,24 +166,25 @@ public class AuthMePlayerListener implements Listener { public void onPlayerMove(PlayerMoveEvent event) { int radius = Settings.getMovementRadius; boolean allowMove = Settings.isMovementAllowed; - if (allowMove && radius <= 0) + if (Settings.noTeleport) { return; + } + if (allowMove && radius <= 0) { + return; + } Player player = event.getPlayer(); - if (Utils.checkAuth(player)) + if (Utils.checkAuth(player)) { return; + } if (!allowMove) { if (event.getFrom().distance(event.getTo()) > 0) { - event.setTo(event.getFrom()); + event.setCancelled(true); return; } } - if (radius <= 0) { - return; - } - Location spawn = plugin.getSpawnLocation(player); if (spawn != null && spawn.getWorld() != null) { if (!event.getPlayer().getWorld().equals(spawn.getWorld())) { From 10435657c58de3d2757062fbf6df305396113f7e Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Mon, 23 Nov 2015 20:59:25 +0100 Subject: [PATCH 093/199] Fix enableBackup option --- src/main/java/fr/xephi/authme/PerformBackup.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/fr/xephi/authme/PerformBackup.java b/src/main/java/fr/xephi/authme/PerformBackup.java index 92194949..e8349f5e 100644 --- a/src/main/java/fr/xephi/authme/PerformBackup.java +++ b/src/main/java/fr/xephi/authme/PerformBackup.java @@ -33,9 +33,14 @@ public class PerformBackup { } /** + * Perform a backup with the given reason. * + * @param cause BackupCause The cause of the backup. */ public void doBackup(BackupCause cause) { + if(!Settings.isBackupActivated) { + ConsoleLogger.showError("Can't perform a Backup: disabled in configuration. Cause of the Backup: " + cause.name()); + } // Check whether a backup should be made at the specified point in time switch (cause) { case START: @@ -50,9 +55,9 @@ public class PerformBackup { // Do backup and check return value! if (doBackup()) { - ConsoleLogger.info("A backup has been performed successfully"); + ConsoleLogger.info("A backup has been performed successfully. Cause of the Backup: " + cause.name()); } else { - ConsoleLogger.showError("Error while performing a backup!"); + ConsoleLogger.showError("Error while performing a backup! Cause of the Backup: " + cause.name()); } } From 87566cb8eacf3a283e4593e943a9c716bb2fec2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Mon, 23 Nov 2015 20:59:12 +0100 Subject: [PATCH 094/199] Updated .gitignore to include project's code style settings file --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e9c355d0..12166c9c 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ hs_err_pid* ## Directory-based project format: .idea/ +# Include the project's code style settings file +!.idea/codeStyleSettings.xml # if you remove the above rule, at least ignore the following: # User-specific stuff: From a9900336ae979b77874949e16bced008b2b635bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Mon, 23 Nov 2015 21:00:58 +0100 Subject: [PATCH 095/199] Don't ignore the .gitignore file itself --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 12166c9c..e0d930e9 100644 --- a/.gitignore +++ b/.gitignore @@ -123,3 +123,6 @@ nbdist/ nbactions.xml nb-configuration.xml .nb-gradle/ + +# Don't exclude the .gitignore itself +!.gitignore From b0cc6826b41523d7f3439d3f25dfc706cef19a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Mon, 23 Nov 2015 21:06:17 +0100 Subject: [PATCH 096/199] Include the project's code style, fixed invalid .idea directory being ignored --- .gitignore | 3 ++- .idea/codeStyleSettings.xml | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .idea/codeStyleSettings.xml diff --git a/.gitignore b/.gitignore index e0d930e9..de0c25c5 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,10 @@ hs_err_pid* *.iml ## Directory-based project format: -.idea/ +.idea/* # Include the project's code style settings file !.idea/codeStyleSettings.xml + # if you remove the above rule, at least ignore the following: # User-specific stuff: diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 00000000..84c6b86a --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file From 8719cce334ef7432dd3e021bcbe40777684e8642 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 23 Nov 2015 21:13:12 +0100 Subject: [PATCH 097/199] Create enum for message keys in Messages --- .../fr/xephi/authme/settings/MessageKey.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/main/java/fr/xephi/authme/settings/MessageKey.java diff --git a/src/main/java/fr/xephi/authme/settings/MessageKey.java b/src/main/java/fr/xephi/authme/settings/MessageKey.java new file mode 100644 index 00000000..f2316ded --- /dev/null +++ b/src/main/java/fr/xephi/authme/settings/MessageKey.java @@ -0,0 +1,128 @@ +package fr.xephi.authme.settings; + +/** + * Keys for translatable messages managed by {@link Messages}. + */ +public enum MessageKey { + + UNKNOWN_USER("unknown_user"), + + UNSAFE_QUIT_LOCATION("unsafe_spawn"), + + NOT_LOGGED_IN("not_logged_in"), + + REGISTER_VOLUNTARILY("reg_voluntarily"), + + USAGE_LOGIN("usage_log"), + + WRONG_PASSWORD("wrong_pwd"), + + UNREGISTERED_SUCCESS("unregistered"), + + REGISTRATION_DISABLED("reg_disabled"), + + SESSION_RECONNECTION("valid_session"), + + LOGIN_SUCCESS("login"), + + ACCOUNT_NOT_ACTIVATED("vb_nonActiv"), + + NAME_ALREADY_REGISTERED("user_regged"), + + NO_PERMISSION("no_perm"), + + ERROR("error"), + + LOGIN_MESSAGE("login_msg"), + + REGISTER_MESSAGE("reg_msg"), + + REGISTER_EMAIL_MESSAGE("reg_email_msg"), + + USAGE_UNREGISTER("usage_unreg"), + + PASSWORD_CHANGED_SUCCESS("pwd_changed"), + + USER_NOT_REGISTERED("user_unknown"), + + PASSWORD_MATCH_ERROR("password_error"), + + PASSWORD_IS_USERNAME_ERROR("password_error_nick"), + + PASSWORD_UNSAFE_ERROR("password_error_unsafe"), + + SESSION_EXPIRED("invalid_session"), + + MUST_REGISTER_MESSAGE("reg_only"), + + ALREADY_LOGGED_IN_ERROR("logged_in"), + + LOGOUT_SUCCESS("logout_success"), + + USERNAME_ALREADY_ONLINE_ERROR("same_nick"), + + REGISTER_SUCCESS("registered"), + + INVALID_PASSWORD_LENGTH("pass_len"), + + CONFIG_RELOAD_SUCCESS("reload"), + + LOGIN_TIMEOUT_ERROR("timeout"), + + USAGE_CHANGE_PASSWORD("usage_changepassword"), + + INVALID_NAME_LENGTH("name_len"), + + INVALID_NAME_CHARACTERS("regex"), + + ADD_EMAIL_MESSAGE("add_email"), + + FORGOT_PASSWORD_MESSAGE("recovery_email"), + + USAGE_CAPTCHA("usage_captcha"), + + CAPTCHA_WRONG_ERROR("wrong_captcha"), + + CAPTCHA_SUCCESS("valid_captcha"), + + KICK_FOR_VIP("kick_forvip"), + + KICK_FULL_SERVER("kick_fullserver"), + + USAGE_ADD_EMAIL("usage_email_add"), + + USAGE_RECOVER_EMAIL("usage_email_recovery"), + + INVALID_NEW_EMAIL("new_email_invalid"), + + INVALID_OLD_EMAIL("old_email_invalid"), + + INVALID_EMAIL("email_invalid"), + + EMAIL_ADDED_SUCCESS("email_added"), + + CONFIRM_EMAIL_MESSAGE("email_confirm"), + + EMAIL_CHANGED_SUCCESS("email_changed"), + + RECOVERY_EMAIL_SENT_MESSAGE("email_send"), + + RECOVERY_EMAIL_ALREADY_SENT_MESSAGE("email_exists"), + + COUNTRY_BANNED_ERROR("country_banned"), + + ANTIBOT_AUTO_ENABLED_MESSAGE("antibot_auto_enabled"), + + ANTIBOT_AUTO_DISABLED_MESSAGE("antibot_auto_disabled"); + + + private String key; + + MessageKey(String key) { + this.key = key; + } + + public String getKey() { + return key; + } +} From 6519f1d8651d07c81201da92242c6617cc9ba99c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Mon, 23 Nov 2015 21:13:15 +0100 Subject: [PATCH 098/199] Set up an extreme code style test --- .idea/codeStyleSettings.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml index 84c6b86a..ff94aa94 100644 --- a/.idea/codeStyleSettings.xml +++ b/.idea/codeStyleSettings.xml @@ -3,9 +3,10 @@

+ *

* A permissions manager, to manage and use various permissions systems. * This manager supports dynamic plugin hooking and various other features. - *

+ *

* Written by Tim Visée. * * @author Tim Visée, http://timvisee.com @@ -267,9 +267,9 @@ public class PermissionsManager { // Check if any known permissions system is enabling if (pluginName.equals("PermissionsEx") || pluginName.equals("PermissionsBukkit") || - pluginName.equals("bPermissions") || pluginName.equals("GroupManager") || - pluginName.equals("zPermissions") || pluginName.equals("Vault") || - pluginName.equals("Permissions")) { + pluginName.equals("bPermissions") || pluginName.equals("GroupManager") || + pluginName.equals("zPermissions") || pluginName.equals("Vault") || + pluginName.equals("Permissions")) { this.log.info(pluginName + " plugin enabled, dynamically updating permissions hooks!"); setup(); } @@ -287,9 +287,9 @@ public class PermissionsManager { // Is the WorldGuard plugin disabled if (pluginName.equals("PermissionsEx") || pluginName.equals("PermissionsBukkit") || - pluginName.equals("bPermissions") || pluginName.equals("GroupManager") || - pluginName.equals("zPermissions") || pluginName.equals("Vault") || - pluginName.equals("Permissions")) { + pluginName.equals("bPermissions") || pluginName.equals("GroupManager") || + pluginName.equals("zPermissions") || pluginName.equals("Vault") || + pluginName.equals("Permissions")) { this.log.info(pluginName + " plugin disabled, updating hooks!"); setup(); } @@ -915,4 +915,4 @@ public class PermissionsManager { return this.name; } } -} \ No newline at end of file +} diff --git a/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java index 5bddc07d..9ce2bf4a 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsyncronousJoin.java @@ -248,7 +248,7 @@ public class AsyncronousJoin { } String[] msg = isAuthAvailable ? m.send("login_msg") : - m.send("reg_" + (Settings.emailRegistration ? "email_" : "") + "msg"); + m.send("reg_" + (Settings.emailRegistration ? "email_" : "") + "msg"); BukkitTask msgTask = sched.runTaskAsynchronously(plugin, new MessageTask(plugin, name, msg, msgInterval)); LimboCache.getInstance().getLimboPlayer(name).setMessageTaskId(msgTask); } @@ -306,7 +306,7 @@ public class AsyncronousJoin { Material cur = player.getLocation().getBlock().getType(); Material top = player.getLocation().add(0D, 1D, 0D).getBlock().getType(); if (cur == Material.PORTAL || cur == Material.ENDER_PORTAL - || top == Material.PORTAL || top == Material.ENDER_PORTAL) { + || top == Material.PORTAL || top == Material.ENDER_PORTAL) { m.send(player, "unsafe_spawn"); player.teleport(spawnLoc); } diff --git a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java index 6f115659..fbbb88f8 100644 --- a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java @@ -25,8 +25,8 @@ public class ProcessSyncronousPlayerQuit implements Runnable { * @param needToChange boolean */ public ProcessSyncronousPlayerQuit(AuthMe plugin, Player player - , boolean isOp, boolean isFlying - , boolean needToChange) { + , boolean isOp, boolean isFlying + , boolean needToChange) { this.plugin = plugin; this.player = player; this.isOp = isOp; diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 453db7fb..2a679da7 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -28,7 +28,7 @@ public class PasswordSecurity { * @return String * @throws NoSuchAlgorithmException */ public static String createSalt(int length) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { byte[] msg = new byte[40]; rnd.nextBytes(msg); MessageDigest sha1 = MessageDigest.getInstance("SHA1"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java index b0c1adbe..23412bf1 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -21,37 +21,37 @@ import java.security.SecureRandom; * BCrypt implements OpenBSD-style Blowfish password hashing using the scheme * described in "A Future-Adaptable Password Scheme" by Niels Provos and David * Mazieres. - *

+ *

* This password hashing system tries to thwart off-line password cracking using * a computationally-intensive hashing algorithm, based on Bruce Schneier's * Blowfish cipher. The work factor of the algorithm is parameterised, so it can * be increased as computers get faster. - *

+ *

* Usage is really simple. To hash a password for the first time, call the * hashpw method with a random salt, like this: - *

+ *

* * String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
*
- *

+ *

* To check whether a plaintext password matches one that has been hashed * previously, use the checkpw method: - *

+ *

* * if (BCrypt.checkpw(candidate_password, stored_hash))
*     System.out.println("It matches");
* else
*     System.out.println("It does not match");
*
- *

+ *

* The gensalt() method takes an optional parameter (log_rounds) that determines * the computational complexity of the hashing: - *

+ *

* * String strong_salt = BCrypt.gensalt(10)
* String stronger_salt = BCrypt.gensalt(12)
*
- *

+ *

* The amount of work increases exponentially (2**log_rounds), so each increment * is twice as much work. The default log_rounds is 10, and the valid range is 4 * to 31. @@ -95,7 +95,7 @@ public class BCRYPT implements EncryptionMethod { * @return base64-encoded string * @throws IllegalArgumentException if the length is invalid * @throws IllegalArgumentException */ private static String encode_base64(byte d[], int len) - throws IllegalArgumentException { + throws IllegalArgumentException { int off = 0; StringBuffer rs = new StringBuffer(); int c1, c2; @@ -150,7 +150,7 @@ public class BCRYPT implements EncryptionMethod { * @return an array containing the decoded bytes * @throws IllegalArgumentException if maxolen is invalid * @throws IllegalArgumentException */ private static byte[] decode_base64(String s, int maxolen) - throws IllegalArgumentException { + throws IllegalArgumentException { StringBuffer rs = new StringBuffer(); int off = 0, slen = s.length(), olen = 0; byte ret[]; @@ -513,7 +513,7 @@ public class BCRYPT implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return hashpw(password, salt); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java index 4db8d8c6..2ff8a44f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java @@ -1,42 +1,42 @@ -package fr.xephi.authme.security.crypts; - -import java.security.NoSuchAlgorithmException; - -/** - */ -public class BCRYPT2Y implements EncryptionMethod { - - /** - * Method getHash. - * - * @param password String - * @param salt String - * @param name String - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) - */ - @Override - public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - if (salt.length() == 22) - salt = "$2y$10$" + salt; - return (BCRYPT.hashpw(password, salt)); - } - - /** - * Method comparePassword. - * - * @param hash String - * @param password String - * @param playerName String - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) - */ - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String ok = hash.substring(0, 29); - if (ok.length() != 29) - return false; - return hash.equals(getHash(password, ok, playerName)); - } - -} +package fr.xephi.authme.security.crypts; + +import java.security.NoSuchAlgorithmException; + +/** + */ +public class BCRYPT2Y implements EncryptionMethod { + + /** + * Method getHash. + * + * @param password String + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ + @Override + public String getHash(String password, String salt, String name) + throws NoSuchAlgorithmException { + if (salt.length() == 22) + salt = "$2y$10$" + salt; + return (BCRYPT.hashpw(password, salt)); + } + + /** + * Method comparePassword. + * + * @param hash String + * @param password String + * @param playerName String + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ + @Override + public boolean comparePassword(String hash, String password, + String playerName) throws NoSuchAlgorithmException { + String ok = hash.substring(0, 29); + if (ok.length() != 29) + return false; + return hash.equals(getHash(password, ok, playerName)); + } + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java index ac890a88..041113c7 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java @@ -36,7 +36,7 @@ public class CRAZYCRYPT1 implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { final String text = "ÜÄaeut//&/=I " + password + "7421€547" + name + "__+IÄIH§%NK " + password; try { final MessageDigest md = MessageDigest.getInstance("SHA-512"); diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java index e6bc7c03..18e5b532 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java @@ -19,7 +19,7 @@ public class CryptPBKDF2 implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { String result = "pbkdf2_sha256$10000$" + salt + "$"; PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 10000); PBKDF2Engine engine = new PBKDF2Engine(params); @@ -46,4 +46,4 @@ public class CryptPBKDF2 implements EncryptionMethod { return engine.verifyKey(password); } -} \ No newline at end of file +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java index 13aaf49f..0bf1d171 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java @@ -20,7 +20,7 @@ public class CryptPBKDF2Django implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { String result = "pbkdf2_sha256$15000$" + salt + "$"; PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 15000); PBKDF2Engine engine = new PBKDF2Engine(params); diff --git a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java index 486ffbae..fb008fec 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java @@ -15,7 +15,7 @@ public class DOUBLEMD5 implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.reset(); md5.update(message.getBytes()); @@ -33,7 +33,7 @@ public class DOUBLEMD5 implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return getMD5(getMD5(password)); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java index e56e5ecd..ac61f993 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java @@ -27,7 +27,7 @@ public interface EncryptionMethod { * @return Hashing password * @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException */ String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException; + throws NoSuchAlgorithmException; /** * @param hash @@ -36,6 +36,6 @@ public interface EncryptionMethod { * @return true if password match, false else * @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException */ boolean comparePassword(String hash, String password, String playerName) - throws NoSuchAlgorithmException; + throws NoSuchAlgorithmException; } diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java index 857c3fef..60bd159b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java @@ -17,7 +17,7 @@ public class IPB3 implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.reset(); md5.update(message.getBytes()); @@ -35,7 +35,7 @@ public class IPB3 implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return getMD5(getMD5(salt) + getMD5(password)); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java index 73fbd3bd..9a988ad1 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java +++ b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java @@ -15,7 +15,7 @@ public class JOOMLA implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.reset(); md5.update(message.getBytes()); @@ -33,7 +33,7 @@ public class JOOMLA implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return getMD5(password + salt) + ":" + salt; } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5.java b/src/main/java/fr/xephi/authme/security/crypts/MD5.java index af94098b..38ba26fe 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5.java @@ -15,7 +15,7 @@ public class MD5 implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.reset(); md5.update(message.getBytes()); @@ -33,7 +33,7 @@ public class MD5 implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return getMD5(password); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java index ab2878f4..4004f7a3 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java @@ -15,7 +15,7 @@ public class MD5VB implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.reset(); md5.update(message.getBytes()); @@ -33,7 +33,7 @@ public class MD5VB implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return "$MD5vb$" + salt + "$" + getMD5(getMD5(password) + salt); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java index 0cae271f..8e1b0d1a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java @@ -17,7 +17,7 @@ public class MYBB implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.reset(); md5.update(message.getBytes()); @@ -35,7 +35,7 @@ public class MYBB implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return getMD5(getMD5(salt) + getMD5(password)); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java index 58ebdc74..30e580b6 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java @@ -214,7 +214,7 @@ public class PHPBB implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return phpbb_hash(password, salt); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java index 54e6cd66..d793fbd4 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java @@ -21,7 +21,7 @@ public class PHPFUSION implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getSHA1(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); sha1.reset(); sha1.update(message.getBytes()); @@ -39,7 +39,7 @@ public class PHPFUSION implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { String digest = null; String algo = "HmacSHA256"; String keyString = getSHA1(salt); diff --git a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java index afb6dde9..7f9bffaa 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java @@ -16,7 +16,7 @@ public class PLAINTEXT implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return password; } diff --git a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java index ad3901e4..8c6f4a51 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java @@ -17,7 +17,7 @@ public class ROYALAUTH implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { for (int i = 0; i < 25; i++) password = hash(password, salt); return password; @@ -31,7 +31,7 @@ public class ROYALAUTH implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ public String hash(String password, String salt) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-512"); md.update(password.getBytes()); byte byteData[] = md.digest(); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java index f71018ac..88194ad7 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java @@ -17,7 +17,7 @@ public class SALTED2MD5 implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getMD5(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.reset(); md5.update(message.getBytes()); @@ -35,7 +35,7 @@ public class SALTED2MD5 implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return getMD5(getMD5(password) + salt); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java index dcacb63c..ea58023a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java @@ -17,7 +17,7 @@ public class SALTEDSHA512 implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getSHA512(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); sha512.reset(); sha512.update(message.getBytes()); @@ -35,7 +35,7 @@ public class SALTEDSHA512 implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return getSHA512(password + salt); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java index 7d1a48a6..834428af 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java @@ -15,7 +15,7 @@ public class SHA1 implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getSHA1(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); sha1.reset(); sha1.update(message.getBytes()); @@ -33,7 +33,7 @@ public class SHA1 implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return getSHA1(password); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java index b0ddfa56..2522852c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java @@ -15,7 +15,7 @@ public class SHA256 implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getSHA256(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); sha256.reset(); sha256.update(message.getBytes()); @@ -33,7 +33,7 @@ public class SHA256 implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return "$SHA$" + salt + "$" + getSHA256(getSHA256(password) + salt); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java index a6ccd615..df4f7e41 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java @@ -15,7 +15,7 @@ public class SHA512 implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getSHA512(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); sha512.reset(); sha512.update(message.getBytes()); @@ -33,7 +33,7 @@ public class SHA512 implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return getSHA512(password); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/SMF.java index ce15e030..af40aa81 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SMF.java @@ -15,7 +15,7 @@ public class SMF implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getSHA1(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); sha1.reset(); sha1.update(message.getBytes()); @@ -33,7 +33,7 @@ public class SMF implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return getSHA1(name.toLowerCase() + password); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java index 1d966423..57bd1b2c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java @@ -17,7 +17,7 @@ public class WBB3 implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException */ private static String getSHA1(String message) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); sha1.reset(); sha1.update(message.getBytes()); @@ -35,7 +35,7 @@ public class WBB3 implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return getSHA1(salt.concat(getSHA1(salt.concat(getSHA1(password))))); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java index ebe79438..50ef3fb7 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java @@ -16,7 +16,7 @@ public class WBB4 implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return BCRYPT.getDoubleHash(password, salt); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java index 596aafe8..eee8bd69 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java @@ -2,15 +2,15 @@ package fr.xephi.authme.security.crypts; /** * The Whirlpool hashing function. - *

- *

+ *

+ *

* References - *

- *

+ *

+ *

* The Whirlpool algorithm was developed by Paulo S. L. M. Barreto and Vincent Rijmen. - *

+ *

* See P.S.L.M. Barreto, V. Rijmen, ``The Whirlpool hashing function,'' First * NESSIE workshop, 2000 (tweaked version, 2003), * + *

* ==================================================================== * ========= - *

+ *

* Differences from version 2.1: - *

+ *

* - Suboptimal diffusion matrix replaced by cir(1, 1, 4, 1, 8, 5, 2, * 9). - *

+ *

* ==================================================================== * ========= - *

+ *

* Differences from version 2.0: - *

+ *

* - Generation of ISO/IEC 10118-3 test vectors. - Bug fix: nonzero * carry was ignored when tallying the data length (this bug apparently * only manifested itself when feeding data in pieces rather than in a * single chunk at once). - *

+ *

* Differences from version 1.0: - *

+ *

* - Original S-box replaced by the tweaked, hardware-efficient * version. - *

+ *

* ==================================================================== * ========= - *

+ *

* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE @@ -401,7 +401,7 @@ public class WHIRLPOOL implements EncryptionMethod { * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { byte[] digest = new byte[DIGESTBYTES]; NESSIEinit(); NESSIEadd(password); diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java index 557c4bdd..b16015ad 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java @@ -137,7 +137,7 @@ public class WORDPRESS implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { byte random[] = new byte[6]; this.randomGen.nextBytes(random); return crypt(password, gensaltPrivate(stringToUtf8(new String(random)))); diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java index 6848d2ea..1560f956 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java @@ -31,7 +31,7 @@ public class XAUTH implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { String hash = getWhirlpool(salt + password).toLowerCase(); int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length()); return hash.substring(0, saltPos) + salt + hash.substring(saltPos); diff --git a/src/main/java/fr/xephi/authme/security/crypts/XF.java b/src/main/java/fr/xephi/authme/security/crypts/XF.java index 41d665ac..0a879a21 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XF.java @@ -23,7 +23,7 @@ public class XF implements EncryptionMethod { */ @Override public String getHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { return getSHA256(getSHA256(password) + regmatch("\"salt\";.:..:\"(.*)\";.:.:\"hashFunc\"", salt)); } diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java index fb8252ba..f1009c0a 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/PBKDF2Engine.java @@ -115,7 +115,7 @@ public class PBKDF2Engine implements PBKDF2 { * ISO-8559-1 encoding. Output result as * "Salt:iteration-count:PBKDF2" with binary data in hexadecimal * encoding. - *

+ *

* Example: Password "password" (without the quotes) leads to * 48290A0B96C426C3:1000:973899B1D4AFEB3ED371060D0797E0EE0142BD04 * @@ -123,7 +123,7 @@ public class PBKDF2Engine implements PBKDF2 { * @throws IOException * @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException */ public static void main(String[] args) - throws IOException, NoSuchAlgorithmException { + throws IOException, NoSuchAlgorithmException { String password = "password"; String candidate = null; PBKDF2Formatter formatter = new PBKDF2HexFormatter(); diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index 49a78db0..7dbb2f26 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -48,46 +48,46 @@ public final class Settings extends YamlConfiguration { // Due to compatibility issues with plugins like FactionsChat public static Boolean isChatAllowed; public static boolean isPermissionCheckEnabled, isRegistrationEnabled, - isForcedRegistrationEnabled, isTeleportToSpawnEnabled, - isSessionsEnabled, isAllowRestrictedIp, - isMovementAllowed, isKickNonRegisteredEnabled, - isForceSingleSessionEnabled, isForceSpawnLocOnJoinEnabled, - isSaveQuitLocationEnabled, isForceSurvivalModeEnabled, - isResetInventoryIfCreative, isCachingEnabled, - isKickOnWrongPasswordEnabled, getEnablePasswordVerifier, - protectInventoryBeforeLogInEnabled, isBackupActivated, - isBackupOnStart, isBackupOnStop, isStopEnabled, reloadSupport, - rakamakUseIp, noConsoleSpam, removePassword, displayOtherAccounts, - useCaptcha, emailRegistration, multiverse, bungee, - banUnsafeIp, doubleEmailCheck, sessionExpireOnIpChange, - disableSocialSpy, forceOnlyAfterLogin, useEssentialsMotd, usePurge, - purgePlayerDat, purgeEssentialsFile, supportOldPassword, - purgeLimitedCreative, purgeAntiXray, purgePermissions, - enableProtection, enableAntiBot, recallEmail, useWelcomeMessage, - broadcastWelcomeMessage, forceRegKick, forceRegLogin, - checkVeryGames, delayJoinLeaveMessages, noTeleport, applyBlindEffect, - customAttributes, generateImage, isRemoveSpeedEnabled, isMySQLWebsite; + isForcedRegistrationEnabled, isTeleportToSpawnEnabled, + isSessionsEnabled, isAllowRestrictedIp, + isMovementAllowed, isKickNonRegisteredEnabled, + isForceSingleSessionEnabled, isForceSpawnLocOnJoinEnabled, + isSaveQuitLocationEnabled, isForceSurvivalModeEnabled, + isResetInventoryIfCreative, isCachingEnabled, + isKickOnWrongPasswordEnabled, getEnablePasswordVerifier, + protectInventoryBeforeLogInEnabled, isBackupActivated, + isBackupOnStart, isBackupOnStop, isStopEnabled, reloadSupport, + rakamakUseIp, noConsoleSpam, removePassword, displayOtherAccounts, + useCaptcha, emailRegistration, multiverse, bungee, + banUnsafeIp, doubleEmailCheck, sessionExpireOnIpChange, + disableSocialSpy, forceOnlyAfterLogin, useEssentialsMotd, usePurge, + purgePlayerDat, purgeEssentialsFile, supportOldPassword, + purgeLimitedCreative, purgeAntiXray, purgePermissions, + enableProtection, enableAntiBot, recallEmail, useWelcomeMessage, + broadcastWelcomeMessage, forceRegKick, forceRegLogin, + checkVeryGames, delayJoinLeaveMessages, noTeleport, applyBlindEffect, + customAttributes, generateImage, isRemoveSpeedEnabled, isMySQLWebsite; public static String getNickRegex, getUnloggedinGroup, getMySQLHost, - getMySQLPort, getMySQLUsername, getMySQLPassword, getMySQLDatabase, - getMySQLTablename, getMySQLColumnName, getMySQLColumnPassword, - getMySQLColumnIp, getMySQLColumnLastLogin, getMySQLColumnSalt, - getMySQLColumnGroup, getMySQLColumnEmail, unRegisteredGroup, - backupWindowsPath, getRegisteredGroup, - messagesLanguage, getMySQLlastlocX, getMySQLlastlocY, - getMySQLlastlocZ, rakamakUsers, rakamakUsersIp, getmailAccount, - getmailPassword, getmailSMTP, getMySQLColumnId, getmailSenderName, - getMailSubject, getMailText, getMySQLlastlocWorld, defaultWorld, - getPhpbbPrefix, getWordPressPrefix, getMySQLColumnLogged, - spawnPriority, crazyloginFileName, getPassRegex, - getMySQLColumnRealName; + getMySQLPort, getMySQLUsername, getMySQLPassword, getMySQLDatabase, + getMySQLTablename, getMySQLColumnName, getMySQLColumnPassword, + getMySQLColumnIp, getMySQLColumnLastLogin, getMySQLColumnSalt, + getMySQLColumnGroup, getMySQLColumnEmail, unRegisteredGroup, + backupWindowsPath, getRegisteredGroup, + messagesLanguage, getMySQLlastlocX, getMySQLlastlocY, + getMySQLlastlocZ, rakamakUsers, rakamakUsersIp, getmailAccount, + getmailPassword, getmailSMTP, getMySQLColumnId, getmailSenderName, + getMailSubject, getMailText, getMySQLlastlocWorld, defaultWorld, + getPhpbbPrefix, getWordPressPrefix, getMySQLColumnLogged, + spawnPriority, crazyloginFileName, getPassRegex, + getMySQLColumnRealName; public static int getWarnMessageInterval, getSessionTimeout, - getRegistrationTimeout, getMaxNickLength, getMinNickLength, - getPasswordMinLen, getMovementRadius, getmaxRegPerIp, - getNonActivatedGroup, passwordMaxLength, getRecoveryPassLength, - getMailPort, maxLoginTry, captchaLength, saltLength, - getmaxRegPerEmail, bCryptLog2Rounds, getPhpbbGroup, - antiBotSensibility, antiBotDuration, delayRecall, getMaxLoginPerIp, - getMaxJoinPerIp, getMySQLMaxConnections; + getRegistrationTimeout, getMaxNickLength, getMinNickLength, + getPasswordMinLen, getMovementRadius, getmaxRegPerIp, + getNonActivatedGroup, passwordMaxLength, getRecoveryPassLength, + getMailPort, maxLoginTry, captchaLength, saltLength, + getmaxRegPerEmail, bCryptLog2Rounds, getPhpbbGroup, + antiBotSensibility, antiBotDuration, delayRecall, getMaxLoginPerIp, + getMaxJoinPerIp, getMySQLMaxConnections; protected static YamlConfiguration configFile; private static AuthMe plugin; private static Settings instance; @@ -480,7 +480,7 @@ public final class Settings extends YamlConfiguration { changes = true; } if (configFile.getString("settings.security.passwordHash", "SHA256").toUpperCase().equals("XFSHA1") || - configFile.getString("settings.security.passwordHash", "SHA256").toUpperCase().equals("XFSHA256")) { + configFile.getString("settings.security.passwordHash", "SHA256").toUpperCase().equals("XFSHA256")) { set("settings.security.passwordHash", "XENFORO"); changes = true; } @@ -685,7 +685,7 @@ public final class Settings extends YamlConfiguration { /** * Saves current configuration (plus defaults) to disk. - *

+ *

* If defaults and configuration are empty, saves blank file. * * @return True if saved successfully diff --git a/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java b/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java index 76bd9d8c..21fd8f0d 100644 --- a/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java +++ b/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java @@ -13,7 +13,7 @@ import java.util.zip.GZIPInputStream; public class GeoLiteAPI { private static final String GEOIP_URL = "http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry" + - "/GeoIP.dat.gz"; + "/GeoIP.dat.gz"; private static final AuthMe plugin = AuthMe.getInstance(); private static LookupService lookupService; @@ -31,7 +31,7 @@ public class GeoLiteAPI { try { lookupService = new LookupService(data); plugin.getLogger().info("[LICENSE] This product uses data from the GeoLite API created by MaxMind, " + - "available at http://www.maxmind.com"); + "available at http://www.maxmind.com"); return true; } catch (IOException e) { return false; diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 3262f2f5..a928f05f 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -58,7 +58,7 @@ public final class Utils { try { lookupService = new LookupService(data); ConsoleLogger.info("[LICENSE] This product uses data from the GeoLite API created by MaxMind, " + - "available at http://www.maxmind.com"); + "available at http://www.maxmind.com"); return true; } catch (IOException e) { return false; @@ -154,7 +154,7 @@ public final class Utils { /** * TODO: This method requires better explanation. - *

+ *

* Set the normal group of a player. * * @param player The player. @@ -200,7 +200,7 @@ public final class Utils { public static boolean isUnrestricted(Player player) { return Settings.isAllowRestrictedIp && !Settings.getUnrestrictedName.isEmpty() - && (Settings.getUnrestrictedName.contains(player.getName())); + && (Settings.getUnrestrictedName.contains(player.getName())); } /** @@ -302,7 +302,7 @@ public final class Utils { } } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { ConsoleLogger.showError("Could not retrieve list of online players: [" - + e.getClass().getName() + "] " + e.getMessage()); + + e.getClass().getName() + "] " + e.getMessage()); } return Collections.emptyList(); } @@ -333,8 +333,8 @@ public final class Utils { if (player.hasMetadata("NPC")) { return true; } else if (plugin.combatTagPlus != null - && player instanceof Player - && plugin.combatTagPlus.getNpcPlayerHelper().isNpc((Player) player)) { + && player instanceof Player + && plugin.combatTagPlus.getNpcPlayerHelper().isNpc((Player) player)) { return true; } return false; From ab77b1291d870b7ed3ef6d80e03664582dee5223 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Mon, 23 Nov 2015 21:33:34 +0100 Subject: [PATCH 102/199] codestyle optimizations --- .idea/codeStyleSettings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml index 84c6b86a..4ecfccc1 100644 --- a/.idea/codeStyleSettings.xml +++ b/.idea/codeStyleSettings.xml @@ -3,6 +3,8 @@ - \ No newline at end of file + From 4a2a5ed3094d43d4a0fe3bc0331a6e916e0c3dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Mon, 23 Nov 2015 21:39:42 +0100 Subject: [PATCH 104/199] Improved .gitignore --- .gitignore | 45 ++++++++++++++------------------------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index de0c25c5..573eb061 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,7 @@ -### Java ### +### Java files ### *.class -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # +# Package Files #*.jar *.war *.ear @@ -13,43 +10,23 @@ hs_err_pid* + ### Intellij ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm - +# Ignore project files *.iml -## Directory-based project format: +# Ignore IDEA directory .idea/* + # Include the project's code style settings file !.idea/codeStyleSettings.xml -# if you remove the above rule, at least ignore the following: - -# User-specific stuff: -# .idea/workspace.xml -# .idea/tasks.xml -# .idea/dictionaries - -# Sensitive or high-churn files: -# .idea/dataSources.ids -# .idea/dataSources.xml -# .idea/sqlDataSources.xml -# .idea/dynamic.xml -# .idea/uiDesigner.xml - -# Gradle: -# .idea/gradle.xml -# .idea/libraries - -# Mongo Explorer plugin: -# .idea/mongoSettings.xml - -## File-based project format: +# File-based project format: *.ipr *.iws -## Plugin-specific files: - +### Plugin-specific files: ### # IntelliJ /out/ @@ -65,6 +42,7 @@ crashlytics.properties crashlytics-build.properties + ### Eclipse ### *.pydevproject .metadata @@ -104,6 +82,7 @@ local.properties .texlipse + ### Maven ### target/ pom.xml.tag @@ -115,6 +94,7 @@ dependency-reduced-pom.xml buildNumber.properties + ### NetBeans ### nbproject/private/ build/ @@ -125,5 +105,8 @@ nbactions.xml nb-configuration.xml .nb-gradle/ + + +### Git ### # Don't exclude the .gitignore itself !.gitignore From 53117928f5640425191f99fd082c30e6b744fd63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Mon, 23 Nov 2015 21:40:11 +0100 Subject: [PATCH 105/199] Reformatted all files once again --- .../authme/security/crypts/WHIRLPOOL.java | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java index eee8bd69..ef0ec6c1 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java @@ -166,9 +166,10 @@ public class WHIRLPOOL implements EncryptionMethod { /** * Method display. + * * @param array byte[] - - * @return String */ + * @return String + */ protected static String display(byte[] array) { char[] val = new char[2 * array.length]; String hex = "0123456789ABCDEF"; @@ -247,12 +248,10 @@ public class WHIRLPOOL implements EncryptionMethod { /** * Delivers input data to the hashing algorithm. * - * @param source - * plaintext data to hash. - * @param sourceBits - * how many bits of plaintext to process. - * - * This method maintains the invariant: bufferBits < 512 + * @param source plaintext data to hash. + * @param sourceBits how many bits of plaintext to process. + *

+ * This method maintains the invariant: bufferBits < 512 */ public void NESSIEadd(byte[] source, long sourceBits) { /* @@ -333,8 +332,9 @@ public class WHIRLPOOL implements EncryptionMethod { /** * Get the hash value from the hashing state. - * + *

* This method uses the invariant: bufferBits < 512 + * * @param digest byte[] */ public void NESSIEfinalize(byte[] digest) { @@ -375,10 +375,9 @@ public class WHIRLPOOL implements EncryptionMethod { /** * Delivers string input data to the hashing algorithm. * - * @param source - * plaintext data to hash (ASCII text string). - * - * This method maintains the invariant: bufferBits < 512 + * @param source plaintext data to hash (ASCII text string). + *

+ * This method maintains the invariant: bufferBits < 512 */ public void NESSIEadd(String source) { if (source.length() > 0) { @@ -392,13 +391,12 @@ public class WHIRLPOOL implements EncryptionMethod { /** * Method getHash. + * * @param password String - * @param salt String - * @param name String - - - - * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) */ + * @param salt String + * @param name String + * @return String * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#getHash(String, String, String) + */ @Override public String getHash(String password, String salt, String name) throws NoSuchAlgorithmException { @@ -411,13 +409,12 @@ public class WHIRLPOOL implements EncryptionMethod { /** * Method comparePassword. - * @param hash String - * @param password String + * + * @param hash String + * @param password String * @param playerName String - - - - * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) */ + * @return boolean * @throws NoSuchAlgorithmException * @see fr.xephi.authme.security.crypts.EncryptionMethod#comparePassword(String, String, String) + */ @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { From 1cdf49f7e10dc4f7a9086435ecc97e67a5248d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Mon, 23 Nov 2015 21:40:34 +0100 Subject: [PATCH 106/199] Disabled wrap when typing option --- .idea/codeStyleSettings.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml index 946c013e..f51445e2 100644 --- a/.idea/codeStyleSettings.xml +++ b/.idea/codeStyleSettings.xml @@ -4,7 +4,6 @@

+ *

* Set the normal group of a player. * * @param player The player. @@ -332,19 +331,8 @@ public final class Utils { return plugin.getServer().getPlayer(name); } - public static boolean isNPC(final Entity player) { - try { - if (player.hasMetadata("NPC")) { - return true; - } else if (plugin.combatTagPlus != null - && player instanceof Player - && plugin.combatTagPlus.getNpcPlayerHelper().isNpc((Player) player)) { - return true; - } - return false; - } catch (Exception e) { - return false; - } + public static boolean isNPC(Player player) { + return player.hasMetadata("NPC") || plugin.combatTagPlus != null && plugin.combatTagPlus.getNpcPlayerHelper().isNpc(player); } public static void teleportToSpawn(Player player) { From 5be476e2d6e30d482a81a5cfbabb2a88d581e9c6 Mon Sep 17 00:00:00 2001 From: DNx5 Date: Tue, 24 Nov 2015 21:55:55 +0700 Subject: [PATCH 121/199] ignore yaw changes in player move event. --- .../authme/listener/AuthMePlayerListener.java | 20 ++++++++++++------- src/main/java/fr/xephi/authme/util/Utils.java | 8 ++++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index c774038c..5e0d690e 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -164,15 +164,21 @@ public class AuthMePlayerListener implements Listener { */ @EventHandler(priority = EventPriority.MONITOR) public void onPlayerMove(PlayerMoveEvent event) { - if (event.isCancelled()) + if (event.isCancelled()) { return; + } - int radius = Settings.getMovementRadius; - boolean allowMove = Settings.isMovementAllowed; if (Settings.noTeleport) { return; } - if (allowMove && radius <= 0) { + + if (Settings.isMovementAllowed && Settings.getMovementRadius <= 0) { + return; + } + + if (event.getFrom().getBlockX() == event.getTo().getBlockX() + && event.getFrom().getBlockY() == event.getTo().getBlockY() + && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) { return; } @@ -181,9 +187,9 @@ public class AuthMePlayerListener implements Listener { return; } - if (!allowMove) { + if (!Settings.isMovementAllowed) { if (event.getFrom().distance(event.getTo()) > 0) { - event.setCancelled(true); + event.setTo(event.getFrom()); return; } } @@ -194,7 +200,7 @@ public class AuthMePlayerListener implements Listener { player.teleport(spawn); return; } - if ((spawn.distance(player.getLocation()) > radius)) { + if ((spawn.distance(player.getLocation()) > Settings.getMovementRadius)) { player.teleport(spawn); } } diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 338d1e14..9b8c90bf 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -173,7 +173,8 @@ public final class Utils { assert permsMan != null; // Remove old groups - permsMan.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getRegisteredGroup, Settings.getUnloggedinGroup)); + permsMan.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, + Settings.getRegisteredGroup, Settings.getUnloggedinGroup)); // Add the normal group, return the result return permsMan.addGroup(player, group); @@ -185,13 +186,12 @@ public final class Utils { return true; } - String name = player.getName().toLowerCase(); - if (PlayerCache.getInstance().isAuthenticated(name)) { + if (PlayerCache.getInstance().isAuthenticated(player.getName())) { return true; } if (!Settings.isForcedRegistrationEnabled) { - if (!plugin.database.isAuthAvailable(name)) { + if (!plugin.database.isAuthAvailable(player.getName())) { return true; } } From e39ab8a644d332cdf1ae8d0b346f6c58ca8c16aa Mon Sep 17 00:00:00 2001 From: DNx5 Date: Tue, 24 Nov 2015 22:08:09 +0700 Subject: [PATCH 122/199] removed old GeoIP data check. --- .../java/fr/xephi/authme/util/GeoLiteAPI.java | 12 +++-- src/main/java/fr/xephi/authme/util/Utils.java | 54 +------------------ 2 files changed, 8 insertions(+), 58 deletions(-) diff --git a/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java b/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java index f59437a3..aa6e773c 100644 --- a/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java +++ b/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java @@ -4,6 +4,7 @@ import com.maxmind.geoip.LookupService; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.settings.Settings; +import org.bukkit.Bukkit; import java.io.*; import java.net.URL; @@ -12,10 +13,12 @@ import java.util.zip.GZIPInputStream; public class GeoLiteAPI { - private static final String GEOIP_URL = "http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry" + + public static final String LICENSE = "[LICENSE] This product uses data from the GeoLite API created by MaxMind, " + + "available at http://www.maxmind.com"; + public static final String GEOIP_URL = "http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry" + "/GeoIP.dat.gz"; - private static final AuthMe plugin = AuthMe.getInstance(); private static LookupService lookupService; + private static final AuthMe plugin = AuthMe.getInstance(); /** * Download (if absent) the GeoIpLite data file and then try to load it. @@ -30,15 +33,14 @@ public class GeoLiteAPI { if (data.exists()) { try { lookupService = new LookupService(data); - plugin.getLogger().info("[LICENSE] This product uses data from the GeoLite API created by MaxMind, " + - "available at http://www.maxmind.com"); + plugin.getLogger().info(LICENSE); return true; } catch (IOException e) { return false; } } // Ok, let's try to download the data file! - plugin.getGameServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { + Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() { @Override public void run() { try { diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 9b8c90bf..63d7ec42 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -15,15 +15,12 @@ import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; -import java.io.*; +import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLConnection; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.zip.GZIPInputStream; /** * Utility class for various operations used in the codebase. @@ -38,7 +35,6 @@ public final class Utils { static { plugin = AuthMe.getInstance(); - checkGeoIP(); initializeOnlinePlayersIsCollectionField(); } @@ -46,54 +42,6 @@ public final class Utils { // Utility class } - // Check and Download GeoIP data if it doesn't exist - public static boolean checkGeoIP() { - if (lookupService != null) { - return true; - } - final File data = new File(Settings.PLUGIN_FOLDER, "GeoIP.dat"); - if (data.exists()) { - if (lookupService == null) { - try { - lookupService = new LookupService(data); - ConsoleLogger.info("[LICENSE] This product uses data from the GeoLite API created by MaxMind, " + - "available at http://www.maxmind.com"); - return true; - } catch (IOException e) { - return false; - } - } - } - plugin.getGameServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { - @Override - public void run() { - try { - String url = "http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz"; - URL downloadUrl = new URL(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(data); - 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.writeStackTrace(e); - } - } - }); - return false; - } - /** * Set the group of a player, by its AuthMe group type. * From bc223b04f97578174790c5684a08599aefc246cc Mon Sep 17 00:00:00 2001 From: DNx5 Date: Tue, 24 Nov 2015 22:14:14 +0700 Subject: [PATCH 123/199] shouldn't allow movement event noTeleport enabled. --- .../fr/xephi/authme/listener/AuthMePlayerListener.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 5e0d690e..d5ddd747 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -168,10 +168,6 @@ public class AuthMePlayerListener implements Listener { return; } - if (Settings.noTeleport) { - return; - } - if (Settings.isMovementAllowed && Settings.getMovementRadius <= 0) { return; } @@ -194,6 +190,10 @@ public class AuthMePlayerListener implements Listener { } } + if (Settings.noTeleport) { + return; + } + Location spawn = plugin.getSpawnLocation(player); if (spawn != null && spawn.getWorld() != null) { if (!player.getWorld().equals(spawn.getWorld())) { From fb6ddb4b594c6d426d3e8c5aeb907d5212f64ce6 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Tue, 24 Nov 2015 17:47:10 +0100 Subject: [PATCH 124/199] Cleanup --- src/main/java/fr/xephi/authme/process/Management.java | 2 -- src/main/java/fr/xephi/authme/util/Utils.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/main/java/fr/xephi/authme/process/Management.java b/src/main/java/fr/xephi/authme/process/Management.java index 1ac2425e..1796d8d7 100644 --- a/src/main/java/fr/xephi/authme/process/Management.java +++ b/src/main/java/fr/xephi/authme/process/Management.java @@ -8,8 +8,6 @@ import fr.xephi.authme.process.logout.AsynchronousLogout; import fr.xephi.authme.process.quit.AsynchronousQuit; import fr.xephi.authme.process.register.AsyncRegister; import fr.xephi.authme.process.unregister.AsynchronousUnregister; -import fr.xephi.authme.security.RandomString; -import fr.xephi.authme.settings.Settings; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitScheduler; diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 63d7ec42..bf8821ab 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -1,6 +1,5 @@ package fr.xephi.authme.util; -import com.maxmind.geoip.LookupService; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerCache; @@ -31,7 +30,6 @@ public final class Utils { private static boolean getOnlinePlayersIsCollection = false; private static Method getOnlinePlayers; - private static LookupService lookupService; static { plugin = AuthMe.getInstance(); From 25c23e144c1cb48fa0a1d0cc6336d66ee1213979 Mon Sep 17 00:00:00 2001 From: DNx5 Date: Tue, 24 Nov 2015 23:13:51 +0700 Subject: [PATCH 125/199] - send blank inventory on logout if protect inventory is enabled. - added reload support for protect inventory option. --- src/main/java/fr/xephi/authme/AuthMe.java | 17 +++++++++---- .../AuthMeInventoryPacketAdapter.java | 25 ++++++++++++++++--- .../login/ProcessSyncronousPlayerLogin.java | 11 +++----- .../process/logout/AsynchronousLogout.java | 7 +++--- .../logout/ProcessSyncronousPlayerLogout.java | 14 +++++++---- .../fr/xephi/authme/settings/Settings.java | 6 +++++ 6 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index f8e3f28e..37d57444 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -1,5 +1,6 @@ package fr.xephi.authme; +import com.comphenix.protocol.ProtocolLibrary; import com.earth2me.essentials.Essentials; import com.onarandombox.MultiverseCore.MultiverseCore; import fr.xephi.authme.api.API; @@ -687,14 +688,20 @@ public class AuthMe extends JavaPlugin { // Check the presence of the ProtocolLib plugin public void checkProtocolLib() { - if (Settings.protectInventoryBeforeLogInEnabled) { - if (server.getPluginManager().isPluginEnabled("ProtocolLib")) { - inventoryProtector = new AuthMeInventoryPacketAdapter(this); - inventoryProtector.register(); - } else { + if (!server.getPluginManager().isPluginEnabled("ProtocolLib")) { + if (Settings.protectInventoryBeforeLogInEnabled) { ConsoleLogger.showError("WARNING!!! The protectInventory feature requires ProtocolLib! Disabling it..."); Settings.protectInventoryBeforeLogInEnabled = false; } + return; + } + + if (Settings.protectInventoryBeforeLogInEnabled) { + inventoryProtector = new AuthMeInventoryPacketAdapter(this); + inventoryProtector.register(); + } else if (inventoryProtector != null) { + ProtocolLibrary.getProtocolManager().removePacketListener(inventoryProtector); + inventoryProtector = null; } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java b/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java index 96495fdb..388f4049 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java @@ -39,9 +39,11 @@ import java.util.logging.Level; public class AuthMeInventoryPacketAdapter extends PacketAdapter { private static final int PLAYER_INVENTORY = 0; - //http://wiki.vg/Inventory#Inventory (0-4 crafting, 5-8 armor, 9-35 main inventory, 36-44 inventory) - //+1 because an index starts with 0 - private static final int PLAYER_CRAFTING_SIZE = 5; + // http://wiki.vg/Inventory#Inventory (0-4 crafting, 5-8 armor, 9-35 main inventory, 36-44 hotbar) + // +1 because an index starts with 0 + private static final int CRAFTING_SIZE = 5; + private static final int ARMOR_SIZE = 4; + private static final int MAIN_SIZE = 27; private static final int HOTBAR_SIZE = 9; /** @@ -88,7 +90,7 @@ public class AuthMeInventoryPacketAdapter extends PacketAdapter { //we are sending our own inventory inventoryPacket.getIntegers().write(0, PLAYER_INVENTORY); - ItemStack[] playerCrafting = new ItemStack[PLAYER_CRAFTING_SIZE]; + ItemStack[] playerCrafting = new ItemStack[CRAFTING_SIZE]; Arrays.fill(playerCrafting, new ItemStack(Material.AIR)); ItemStack[] armorContents = player.getInventory().getArmorContents(); ItemStack[] mainInventory = player.getInventory().getContents(); @@ -120,4 +122,19 @@ public class AuthMeInventoryPacketAdapter extends PacketAdapter { plugin.getLogger().log(Level.WARNING, "Error during inventory recovery", invocationExc); } } + + public void sendBlankInventoryPacket(Player player) { + ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager(); + PacketContainer inventoryPacket = protocolManager.createPacket(PacketType.Play.Server.WINDOW_ITEMS); + inventoryPacket.getIntegers().write(0, PLAYER_INVENTORY); + int inventorySize = CRAFTING_SIZE + ARMOR_SIZE + MAIN_SIZE; + ItemStack[] blankInventory = new ItemStack[inventorySize]; + Arrays.fill(blankInventory, new ItemStack(Material.AIR)); + inventoryPacket.getItemArrayModifier().write(0, blankInventory); + try { + protocolManager.sendServerPacket(player, inventoryPacket, false); + } catch (InvocationTargetException invocationExc) { + plugin.getLogger().log(Level.WARNING, "Error during sending blank inventory", invocationExc); + } + } } diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java index 1b705d8a..96f65ddc 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncronousPlayerLogin.java @@ -98,18 +98,15 @@ public class ProcessSyncronousPlayerLogin implements Runnable { protected void restoreInventory() { RestoreInventoryEvent event = new RestoreInventoryEvent(player); - Bukkit.getServer().getPluginManager().callEvent(event); - if (!event.isCancelled()) { + pm.callEvent(event); + if (!event.isCancelled() && plugin.inventoryProtector != null) { plugin.inventoryProtector.sendInventoryPacket(player); } } protected void forceCommands() { for (String command : Settings.forceCommands) { - try { - player.performCommand(command.replace("%p", player.getName())); - } catch (Exception ignored) { - } + player.performCommand(command.replace("%p", player.getName())); } for (String command : Settings.forceCommandsAsConsole) { Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), command.replace("%p", player.getName())); @@ -154,7 +151,7 @@ public class ProcessSyncronousPlayerLogin implements Runnable { restoreFlyghtState(); - if (Settings.protectInventoryBeforeLogInEnabled && plugin.inventoryProtector != null) { + if (Settings.protectInventoryBeforeLogInEnabled) { restoreInventory(); } diff --git a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java index d4f35331..d6642f0e 100644 --- a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java @@ -46,8 +46,9 @@ public class AsynchronousLogout { public void process() { preLogout(); - if (!canLogout) + if (!canLogout) { return; + } final Player p = player; BukkitScheduler scheduler = p.getServer().getScheduler(); PlayerAuth auth = PlayerCache.getInstance().getAuth(name); @@ -66,11 +67,11 @@ public class AsynchronousLogout { Utils.teleportToSpawn(p); } }); - if (LimboCache.getInstance().hasLimboPlayer(name)) + if (LimboCache.getInstance().hasLimboPlayer(name)) { LimboCache.getInstance().deleteLimboPlayer(name); + } LimboCache.getInstance().addLimboPlayer(player); Utils.setGroup(player, GroupType.NOTLOGGEDIN); - scheduler.scheduleSyncDelayedTask(plugin, new ProcessSyncronousPlayerLogout(p, plugin)); } } diff --git a/src/main/java/fr/xephi/authme/process/logout/ProcessSyncronousPlayerLogout.java b/src/main/java/fr/xephi/authme/process/logout/ProcessSyncronousPlayerLogout.java index b23ce6e8..9bf98aab 100644 --- a/src/main/java/fr/xephi/authme/process/logout/ProcessSyncronousPlayerLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/ProcessSyncronousPlayerLogout.java @@ -43,14 +43,18 @@ public class ProcessSyncronousPlayerLogout implements Runnable { */ @Override public void run() { - if (plugin.sessions.containsKey(name)) + if (plugin.sessions.containsKey(name)) { plugin.sessions.get(name).cancel(); - plugin.sessions.remove(name); - int delay = Settings.getRegistrationTimeout * 20; + plugin.sessions.remove(name); + } + if (Settings.protectInventoryBeforeLogInEnabled) { + plugin.inventoryProtector.sendBlankInventoryPacket(player); + } + int timeOut = Settings.getRegistrationTimeout * 20; int interval = Settings.getWarnMessageInterval; BukkitScheduler sched = player.getServer().getScheduler(); - if (delay != 0) { - BukkitTask id = sched.runTaskLaterAsynchronously(plugin, new TimeoutTask(plugin, name, player), delay); + if (timeOut != 0) { + BukkitTask id = sched.runTaskLaterAsynchronously(plugin, new TimeoutTask(plugin, name, player), timeOut); LimboCache.getInstance().getLimboPlayer(name).setTimeoutTaskId(id); } BukkitTask msgT = sched.runTaskAsynchronously(plugin, new MessageTask(plugin, name, m.send("login_msg"), interval)); diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index 503c217e..ad501994 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -4,6 +4,7 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource.DataSourceType; +import fr.xephi.authme.listener.AuthMeInventoryPacketAdapter; import fr.xephi.authme.security.HashAlgorithm; import org.bukkit.configuration.file.YamlConfiguration; @@ -187,7 +188,10 @@ public final class Settings extends YamlConfiguration { getUnrestrictedName = configFile.getStringList("settings.unrestrictions.UnrestrictedName"); getRegisteredGroup = configFile.getString("GroupOptions.RegisteredPlayerGroup", ""); getEnablePasswordVerifier = configFile.getBoolean("settings.restrictions.enablePasswordVerifier", true); + protectInventoryBeforeLogInEnabled = configFile.getBoolean("settings.restrictions.ProtectInventoryBeforeLogIn", true); + plugin.checkProtocolLib(); + passwordMaxLength = configFile.getInt("settings.security.passwordMaxLength", 20); isBackupActivated = configFile.getBoolean("BackupSystem.ActivateBackup", false); isBackupOnStart = configFile.getBoolean("BackupSystem.OnServerStart", false); @@ -195,6 +199,7 @@ public final class Settings extends YamlConfiguration { backupWindowsPath = configFile.getString("BackupSystem.MysqlWindowsPath", "C:\\Program Files\\MySQL\\MySQL Server 5.1\\"); isStopEnabled = configFile.getBoolean("Security.SQLProblem.stopServer", true); reloadSupport = configFile.getBoolean("Security.ReloadCommand.useReloadCommandSupport", true); + allowCommands = new ArrayList<>(); allowCommands.addAll(Arrays.asList("/login", "/l", "/register", "/reg", "/email", "/captcha")); for (String cmd : configFile.getStringList("settings.restrictions.allowCommands")) { @@ -203,6 +208,7 @@ public final class Settings extends YamlConfiguration { allowCommands.add(cmd); } } + rakamakUsers = configFile.getString("Converter.Rakamak.fileName", "users.rak"); rakamakUsersIp = configFile.getString("Converter.Rakamak.ipFileName", "UsersIp.rak"); rakamakUseIp = configFile.getBoolean("Converter.Rakamak.useIp", false); From ea420ab05f3fee65bf1d9011e0b5cfa74e531866 Mon Sep 17 00:00:00 2001 From: DNx5 Date: Wed, 25 Nov 2015 00:43:55 +0700 Subject: [PATCH 126/199] set protect inventory to false in config file if protocol lib is not found. --- src/main/java/fr/xephi/authme/AuthMe.java | 15 ++++++++++----- .../java/fr/xephi/authme/settings/Settings.java | 4 ---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 37d57444..2fd5d980 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -692,16 +692,21 @@ public class AuthMe extends JavaPlugin { if (Settings.protectInventoryBeforeLogInEnabled) { ConsoleLogger.showError("WARNING!!! The protectInventory feature requires ProtocolLib! Disabling it..."); Settings.protectInventoryBeforeLogInEnabled = false; + getSettings().set("settings.restrictions.ProtectInventoryBeforeLogIn", false); } return; } if (Settings.protectInventoryBeforeLogInEnabled) { - inventoryProtector = new AuthMeInventoryPacketAdapter(this); - inventoryProtector.register(); - } else if (inventoryProtector != null) { - ProtocolLibrary.getProtocolManager().removePacketListener(inventoryProtector); - inventoryProtector = null; + if (inventoryProtector == null) { + inventoryProtector = new AuthMeInventoryPacketAdapter(this); + inventoryProtector.register(); + } + } else { + if (inventoryProtector != null) { + ProtocolLibrary.getProtocolManager().removePacketListener(inventoryProtector); + inventoryProtector = null; + } } } diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index ad501994..11ec0a39 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -621,10 +621,6 @@ public final class Settings extends YamlConfiguration { set("Email.emailBlacklisted", new ArrayList()); changes = true; } - if (contains("Performances.useMultiThreading")) { - set("Performances.useMultiThreading", null); - changes = true; - } if (contains("Performances")) { set("Performances", null); changes = true; From 489708b2069656f671359f73683407ea7a2d5ef0 Mon Sep 17 00:00:00 2001 From: DNx5 Date: Wed, 25 Nov 2015 00:44:38 +0700 Subject: [PATCH 127/199] i forgot about the hotbar. --- .../fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java b/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java index 388f4049..a333dbd2 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeInventoryPacketAdapter.java @@ -127,7 +127,7 @@ public class AuthMeInventoryPacketAdapter extends PacketAdapter { ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager(); PacketContainer inventoryPacket = protocolManager.createPacket(PacketType.Play.Server.WINDOW_ITEMS); inventoryPacket.getIntegers().write(0, PLAYER_INVENTORY); - int inventorySize = CRAFTING_SIZE + ARMOR_SIZE + MAIN_SIZE; + int inventorySize = CRAFTING_SIZE + ARMOR_SIZE + MAIN_SIZE + HOTBAR_SIZE; ItemStack[] blankInventory = new ItemStack[inventorySize]; Arrays.fill(blankInventory, new ItemStack(Material.AIR)); inventoryPacket.getItemArrayModifier().write(0, blankInventory); From cc8401e841b7f3bb0fff0fb872fc9c8a300f3b72 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Tue, 24 Nov 2015 18:50:56 +0100 Subject: [PATCH 128/199] Move the antibot management --- src/main/java/fr/xephi/authme/AntiBot.java | 101 ++++++++++++++++++ src/main/java/fr/xephi/authme/AuthMe.java | 46 +++----- .../authme/SwitchAntiBotCommand.java | 21 ++-- .../authme/listener/AuthMePlayerListener.java | 45 +------- 4 files changed, 127 insertions(+), 86 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/AntiBot.java diff --git a/src/main/java/fr/xephi/authme/AntiBot.java b/src/main/java/fr/xephi/authme/AntiBot.java new file mode 100644 index 00000000..381acfac --- /dev/null +++ b/src/main/java/fr/xephi/authme/AntiBot.java @@ -0,0 +1,101 @@ +package fr.xephi.authme; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import fr.xephi.authme.settings.Messages; +import fr.xephi.authme.settings.Settings; + +/** + * The AntiBot Service Management class. + */ +public class AntiBot { + + public enum AntiBotStatus { + ARMED, + DISARMED, + DELAYED, + ACTIVATED + } + + private static AntiBotStatus antiBotStatus = AntiBotStatus.DISARMED; + private static final AuthMe plugin = AuthMe.getInstance(); + private static final Messages messages = plugin.getMessages(); + private static final List antibotPlayers = new ArrayList<>(); + + public static void setupAntiBotService() { + if (!Settings.enableAntiBot) { + return; + } + Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { + @Override + public void run() { + antiBotStatus = AntiBotStatus.ARMED; + } + }, 2400); + } + + public static void switchAntiBotStatus(boolean activated) { + if(antiBotStatus == AntiBotStatus.DISARMED || antiBotStatus == AntiBotStatus.DELAYED) { + return; + } + if(activated) { + antiBotStatus = AntiBotStatus.ACTIVATED; + } else { + antiBotStatus = AntiBotStatus.ARMED; + } + } + + public static AntiBotStatus getAntiBotStatus() { + return antiBotStatus; + } + + public static void activateAntiBot() { + antiBotStatus = AntiBotStatus.ACTIVATED; + for (String s : messages.send("antibot_auto_enabled")) { + Bukkit.broadcastMessage(s); + } + + Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { + @Override + public void run() { + if (antiBotStatus == AntiBotStatus.ACTIVATED) { + antiBotStatus = AntiBotStatus.ARMED; + antibotPlayers.clear(); + for (String s : messages.send("antibot_auto_disabled")) + Bukkit.broadcastMessage(s.replace("%m", "" + Settings.antiBotDuration)); + } + } + }, Settings.antiBotDuration * 1200); + } + + /** + * Method checkAntiBotMod. + * + * @param player Player + */ + public static void checkAntiBot(final Player player) { + if (antiBotStatus == AntiBotStatus.ACTIVATED || antiBotStatus == AntiBotStatus.DISARMED) { + return; + } + if (plugin.getPermissionsManager().hasPermission(player, "authme.bypassantibot")) { + return; + } + + antibotPlayers.add(player.getName().toLowerCase()); + if (antibotPlayers.size() > Settings.antiBotSensibility) { + activateAntiBot(); + return; + } + Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { + @Override + public void run() { + antibotPlayers.remove(player.getName().toLowerCase()); + } + }, 300); + } + +} diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 2fd5d980..d5aa5e2a 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -24,6 +24,7 @@ import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.OtherAccounts; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Spawn; +import fr.xephi.authme.AntiBot; import fr.xephi.authme.util.GeoLiteAPI; import fr.xephi.authme.util.Utils; import net.minelink.ctplus.CombatTagPlus; @@ -82,30 +83,30 @@ public class AuthMe extends JavaPlugin { public DataSource database; public OtherAccounts otherAccounts; public Location essentialsSpawn; - // Hooks TODO: move into modules + + // Hooks TODO: Move into modules public Essentials ess; public MultiverseCore multiverse; public CombatTagPlus combatTagPlus; public AuthMeInventoryPacketAdapter inventoryProtector; - // Random data maps and stuff - // TODO: Create Manager for this + + // Data maps and stuff + // TODO: Move into a manager public final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); public final ConcurrentHashMap captcha = new ConcurrentHashMap<>(); public final ConcurrentHashMap cap = new ConcurrentHashMap<>(); public final ConcurrentHashMap realIp = new ConcurrentHashMap<>(); - // AntiBot Status - // TODO: Create Manager for this - public boolean antiBotMod = false; - public boolean delayedAntiBot = true; + + // If cache is enabled, prevent any connection before the players data caching is completed. + // TODO: Move somewhere + private boolean canConnect = true; + private CommandHandler commandHandler = null; private PermissionsManager permsMan = null; private Settings settings; private Messages messages; private JsonCache playerBackup; private ModuleManager moduleManager; - // If cache is enabled, prevent any connection before the players data caching is completed. - // TODO: Move somewhere - private boolean canConnect = true; /** * Returns the plugin's instance. @@ -229,7 +230,7 @@ public class AuthMe extends JavaPlugin { setupConsoleFilter(); // AntiBot delay - setupAntiBotDelay(); + AntiBot.setupAntiBotService(); // Download and load GeoIp.dat file if absent GeoLiteAPI.isDataAvailable(); @@ -443,20 +444,6 @@ public class AuthMe extends JavaPlugin { return false; } - /** - * Set up the AntiBot delay. - */ - private void setupAntiBotDelay() { - if (Settings.enableAntiBot) { - Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() { - @Override - public void run() { - delayedAntiBot = false; - } - }, 2400); - } - } - /** * Set up the console filter. */ @@ -836,15 +823,6 @@ public class AuthMe extends JavaPlugin { return player.getWorld().getSpawnLocation(); } - public void switchAntiBotMod(boolean mode) { - this.antiBotMod = mode; - Settings.switchAntiBotMod(mode); - } - - public boolean getAntiBotModMode() { - return this.antiBotMod; - } - private void recallEmail() { if (!Settings.recallEmail) return; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java index bbbf8999..b8b83605 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java @@ -1,6 +1,6 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.AuthMe; +import fr.xephi.authme.AntiBot; import fr.xephi.authme.command.CommandParts; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.command.help.HelpProvider; @@ -22,25 +22,26 @@ public class SwitchAntiBotCommand extends ExecutableCommand { */ @Override public boolean executeCommand(final CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); - // Get the new state - String newState = plugin.getAntiBotModMode() ? "OFF" : "ON"; - if (commandArguments.getCount() >= 1) + String newState = null; + if (commandArguments.getCount() == 1) { newState = commandArguments.get(0); + } else if(commandArguments.getCount() == 0) { + sender.sendMessage("[AuthMe] AntiBot status: " + AntiBot.getAntiBotStatus().name()); + return true; + } // Enable the mod if (newState.equalsIgnoreCase("ON")) { - plugin.switchAntiBotMod(true); - sender.sendMessage("[AuthMe] AntiBotMod enabled"); + AntiBot.switchAntiBotStatus(true); + sender.sendMessage("[AuthMe] AntiBot Manual Ovverride: enabled!"); return true; } // Disable the mod if (newState.equalsIgnoreCase("OFF")) { - plugin.switchAntiBotMod(false); - sender.sendMessage("[AuthMe] AntiBotMod disabled"); + AntiBot.switchAntiBotStatus(false); + sender.sendMessage("[AuthMe] AntiBotMod Manual Ovverride: disabled!"); return true; } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index d5ddd747..4a5e705a 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -2,6 +2,8 @@ package fr.xephi.authme.listener; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; + +import fr.xephi.authme.AntiBot; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; @@ -27,9 +29,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.player.*; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import java.util.concurrent.ConcurrentHashMap; /** @@ -41,7 +41,6 @@ public class AuthMePlayerListener implements Listener { public static final ConcurrentHashMap causeByAuthMe = new ConcurrentHashMap<>(); public final AuthMe plugin; private final Messages m = Messages.getInstance(); - private final List antibot = new ArrayList<>(); /** * Constructor for AuthMePlayerListener. @@ -206,44 +205,6 @@ public class AuthMePlayerListener implements Listener { } } - /** - * Method checkAntiBotMod. - * - * @param player Player - */ - private void checkAntiBotMod(final Player player) { - if (plugin.delayedAntiBot || plugin.antiBotMod) - return; - if (plugin.getPermissionsManager().hasPermission(player, "authme.bypassantibot")) - return; - if (antibot.size() > Settings.antiBotSensibility) { - plugin.switchAntiBotMod(true); - for (String s : m.send("antibot_auto_enabled")) - Bukkit.broadcastMessage(s); - Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { - - @Override - public void run() { - if (plugin.antiBotMod) { - plugin.switchAntiBotMod(false); - antibot.clear(); - for (String s : m.send("antibot_auto_disabled")) - Bukkit.broadcastMessage(s.replace("%m", "" + Settings.antiBotDuration)); - } - } - }, Settings.antiBotDuration * 1200); - return; - } - antibot.add(player.getName().toLowerCase()); - Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { - - @Override - public void run() { - antibot.remove(player.getName().toLowerCase()); - } - }, 300); - } - /** * Method onPlayerJoin. * @@ -402,7 +363,7 @@ public class AuthMePlayerListener implements Listener { return; } - checkAntiBotMod(player); + AntiBot.checkAntiBot(player); if (Settings.bungee) { ByteArrayDataOutput out = ByteStreams.newDataOutput(); From 28e742e42f8ce55441ca4dbdeb9b0fa3271f54d7 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Tue, 24 Nov 2015 19:04:18 +0100 Subject: [PATCH 129/199] cleanup --- src/main/java/fr/xephi/authme/AntiBot.java | 33 +++++++++---------- .../authme/SwitchAntiBotCommand.java | 4 +-- .../fr/xephi/authme/settings/Settings.java | 1 - 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AntiBot.java b/src/main/java/fr/xephi/authme/AntiBot.java index 381acfac..16b0ff23 100644 --- a/src/main/java/fr/xephi/authme/AntiBot.java +++ b/src/main/java/fr/xephi/authme/AntiBot.java @@ -1,30 +1,22 @@ package fr.xephi.authme; -import java.util.ArrayList; -import java.util.List; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; /** * The AntiBot Service Management class. */ public class AntiBot { - public enum AntiBotStatus { - ARMED, - DISARMED, - DELAYED, - ACTIVATED - } - - private static AntiBotStatus antiBotStatus = AntiBotStatus.DISARMED; private static final AuthMe plugin = AuthMe.getInstance(); private static final Messages messages = plugin.getMessages(); private static final List antibotPlayers = new ArrayList<>(); + private static AntiBotStatus antiBotStatus = AntiBotStatus.DISARMED; public static void setupAntiBotService() { if (!Settings.enableAntiBot) { @@ -38,11 +30,11 @@ public class AntiBot { }, 2400); } - public static void switchAntiBotStatus(boolean activated) { - if(antiBotStatus == AntiBotStatus.DISARMED || antiBotStatus == AntiBotStatus.DELAYED) { + public static void overrideAntiBotStatus(boolean activated) { + if (antiBotStatus == AntiBotStatus.DISARMED || antiBotStatus == AntiBotStatus.DELAYED) { return; } - if(activated) { + if (activated) { antiBotStatus = AntiBotStatus.ACTIVATED; } else { antiBotStatus = AntiBotStatus.ARMED; @@ -98,4 +90,11 @@ public class AntiBot { }, 300); } + public enum AntiBotStatus { + ARMED, + DISARMED, + DELAYED, + ACTIVATED + } + } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java index b8b83605..fb844c60 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java @@ -33,14 +33,14 @@ public class SwitchAntiBotCommand extends ExecutableCommand { // Enable the mod if (newState.equalsIgnoreCase("ON")) { - AntiBot.switchAntiBotStatus(true); + AntiBot.overrideAntiBotStatus(true); sender.sendMessage("[AuthMe] AntiBot Manual Ovverride: enabled!"); return true; } // Disable the mod if (newState.equalsIgnoreCase("OFF")) { - AntiBot.switchAntiBotStatus(false); + AntiBot.overrideAntiBotStatus(false); sender.sendMessage("[AuthMe] AntiBotMod Manual Ovverride: disabled!"); return true; } diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index 11ec0a39..ecbc8c54 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -4,7 +4,6 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource.DataSourceType; -import fr.xephi.authme.listener.AuthMeInventoryPacketAdapter; import fr.xephi.authme.security.HashAlgorithm; import org.bukkit.configuration.file.YamlConfiguration; From 43b9462ddc1f802144838cc6f1a0746776ad72b8 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Tue, 24 Nov 2015 21:13:09 +0100 Subject: [PATCH 130/199] cleanup --- .idea/codeStyleSettings.xml | 2 +- pom.xml | 1203 ++++++++++----------- src/main/java/fr/xephi/authme/AuthMe.java | 2 - src/main/resources/plugin.yml | 2 +- 4 files changed, 600 insertions(+), 609 deletions(-) diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml index 38354352..6e95fe79 100644 --- a/.idea/codeStyleSettings.xml +++ b/.idea/codeStyleSettings.xml @@ -3,6 +3,7 @@ \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9c19232f..7e52a4b1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,641 +1,634 @@ - - 4.0.0 - - fr.xephi - authme - 5.1-SNAPSHOT - jar - - AuthMeReloaded - Authentication plugin for CraftBukkit/Spigot! - 2013 - http://dev.bukkit.org/bukkit-plugins/authme-reloaded/ - - - - AuthMe-Team - https://github.com/AuthMe-Team - - - - scm:git:https://github.com/Xephi/AuthMeReloaded.git - scm:git:git@github.com:Xephi/AuthMeReloaded.git - http://github.com/Xephi/AuthMeReloaded - - - - jenkins - http://ci.xephi.fr/job/AuthMeReloaded/ - - - - GitHub - https://github.com/Xephi/AuthMeReloaded/issues - - - - - The GNU General Public Licence version 3 (GPLv3) - http://www.gnu.org/licenses/gpl-3.0.html - - - - + + 4.0.0 + + fr.xephi + authme + 5.1-SNAPSHOT + jar + + AuthMeReloaded + Authentication plugin for CraftBukkit/Spigot! + 2013 + http://dev.bukkit.org/bukkit-plugins/authme-reloaded/ + + + + AuthMe-Team + https://github.com/AuthMe-Team + + + + scm:git:https://github.com/Xephi/AuthMeReloaded.git + scm:git:git@github.com:Xephi/AuthMeReloaded.git + http://github.com/Xephi/AuthMeReloaded + + + + jenkins + http://ci.xephi.fr/job/AuthMeReloaded/ + + + + GitHub + https://github.com/Xephi/AuthMeReloaded/issues + + + + + The GNU General Public Licence version 3 (GPLv3) + http://www.gnu.org/licenses/gpl-3.0.html + + + + + 3.3.3 + + + + UTF-8 + - UTF-8 + AuthMe fr.xephi.authme.AuthMe - 100 + 100 [Xephi, sgdc3, DNx5, timvisee, games647, ljacqu] - - - 1.7 - - - 1.8.8-R0.1-SNAPSHOT - - - AuthMe-${project.version} - src/main/java - src/test/java - - - - . - true - src/main/resources/ - - plugin.yml - - - - . - false - src/main/resources/ - - *.yml - - - plugin.yml - - - - ./messages/ - false - src/main/resources/messages/ - - *.yml - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.3 - - 1.7 - ${javaVersion} - - - + + 1.7 + + + 1.8.8-R0.1-SNAPSHOT + + + + AuthMe-${project.version} + src/main/java + src/test/java + + + + . + true + src/main/resources/ + + plugin.yml + + + + . + false + src/main/resources/ + + *.yml + + + plugin.yml + + + + ./messages/ + false + src/main/resources/messages/ + + *.yml + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + true + 1.7 + ${javaVersion} + + + - - org.apache.maven.plugins - maven-shade-plugin - 2.4.2 - - false - false - - - org.mcstats - fr.xephi.authme - - - com.google.gson - fr.xephi.authme.libs.gson - - - com.zaxxer.hikari - fr.xephi.authme.libs.hikari - - - org.slf4j - fr.xephi.authme.libs.slf4j - - - com.maxmind.geoip - fr.xephi.authme.libs.geoip - - - net.ricecode.similarity - fr.xephi.authme.libs.similarity - - - - - - package - - shade - - - - - - + + org.apache.maven.plugins + maven-shade-plugin + 2.4.2 + + false + false + + + org.mcstats + fr.xephi.authme + + + com.google.gson + fr.xephi.authme.libs.gson + + + com.zaxxer.hikari + fr.xephi.authme.libs.hikari + + + org.slf4j + fr.xephi.authme.libs.slf4j + + + com.maxmind.geoip + fr.xephi.authme.libs.geoip + + + net.ricecode.similarity + fr.xephi.authme.libs.similarity + + + + + + package + + shade + + + + + + - - - - spigot-repo - http://hub.spigotmc.org/nexus/content/groups/public - + + + + spigot-repo + http://hub.spigotmc.org/nexus/content/groups/public + - - - ess-repo - http://ci.drtshock.net/plugin/repository/everything - + + + ess-repo + http://ci.drtshock.net/plugin/repository/everything + - - - minelink-thirdparty - http://repo.minelink.net/content/repositories/public - + + + minelink-thirdparty + http://repo.minelink.net/content/repositories/public + - - - dmulloy2-repo - http://repo.dmulloy2.net/content/groups/public/ - + + + dmulloy2-repo + http://repo.dmulloy2.net/content/groups/public/ + - - - onarandombox - http://repo.onarandombox.com/content/groups/public - + + + onarandombox + http://repo.onarandombox.com/content/groups/public + - - - vault-repo - http://nexus.theyeticave.net/content/repositories/pub_releases - + + + vault-repo + http://nexus.theyeticave.net/content/repositories/pub_releases + - - - luricos-releases - http://repo.luricos.de/content/repositories/releases - + + + luricos-releases + http://repo.luricos.de/content/repositories/releases + - - - xephi-repo - http://ci.xephi.fr/plugin/repository/everything/ - + + + xephi-repo + http://ci.xephi.fr/plugin/repository/everything/ + - - - pex-repo - http://pex-repo.aoeu.xyz/ - - + + + pex-repo + http://pex-repo.aoeu.xyz/ + + - - - - com.zaxxer - HikariCP - 2.4.1 - compile - - - slf4j-api - org.slf4j - - - true - - - org.slf4j - slf4j-jdk14 - 1.7.12 - compile - true - + + + + com.zaxxer + HikariCP + 2.4.1 + compile + + + slf4j-api + org.slf4j + + + true + + + org.slf4j + slf4j-jdk14 + 1.7.12 + compile + true + - - - org.apache.logging.log4j - log4j-core - - 2.0-beta9 - provided - true - + + + org.apache.logging.log4j + log4j-core + + 2.0-beta9 + provided + true + - - - com.sun.mail - mailapi - 1.5.4 - compile - true - - - com.sun.mail - smtp - 1.5.4 - compile - true - - - org.apache.commons - commons-email - 1.4 - compile - true - + + + org.apache.commons + commons-email + 1.4 + compile + true + - - - com.google.code.gson - gson - 2.4 - compile - true - + + + com.google.code.gson + gson + 2.4 + compile + true + - - - com.maxmind.geoip - geoip-api - 1.2.15 - compile - true - + + + com.maxmind.geoip + geoip-api + 1.2.15 + compile + true + - - - org.mcstats.bukkit - metrics - R8-SNAPSHOT - compile - - - org.bukkit - bukkit - - - true - + + + org.mcstats.bukkit + metrics + R8-SNAPSHOT + compile + + + org.bukkit + bukkit + + + true + - - - org.bukkit - bukkit - ${bukkitVersion} - provided - true - - - junit - junit - - - json-simple - com.googlecode.json-simple - - - gson - com.google.code.gson - - - persistence-api - javax.persistence - - - + + + org.bukkit + bukkit + ${bukkitVersion} + provided + true + + + junit + junit + + + json-simple + com.googlecode.json-simple + + + gson + com.google.code.gson + + + persistence-api + javax.persistence + + + - - - com.comphenix.protocol - ProtocolLib - 3.6.5-SNAPSHOT - provided - true - - - cglib-nodep - cglib - - - BukkitExecutors - com.comphenix.executors - - - + + + com.comphenix.protocol + ProtocolLib + 3.6.5-SNAPSHOT + provided + true + + + cglib-nodep + cglib + + + BukkitExecutors + com.comphenix.executors + + + - - - ru.tehkode - PermissionsEx - 1.23.1 - provided - - - org.bukkit - bukkit - - - net.gravitydevelopment.updater - updater - - - commons-dbcp - commons-dbcp - - - AccountsClient - com.mojang - - - + + + ru.tehkode + PermissionsEx + 1.23.1 + provided + + + org.bukkit + bukkit + + + net.gravitydevelopment.updater + updater + + + commons-dbcp + commons-dbcp + + + AccountsClient + com.mojang + + + - - - org.anjocaido - groupmanager - 2.13.1 - system - ${basedir}/lib/EssentialsGroupManager-2.13.1.jar - + + + org.anjocaido + groupmanager + 2.13.1 + system + ${basedir}/lib/EssentialsGroupManager-2.13.1.jar + - - - de.bananaco - bpermissions - 1.12-DEV - system - ${basedir}/lib/bPermissions-2.12-DEV.jar - + + + de.bananaco + bpermissions + 1.12-DEV + system + ${basedir}/lib/bPermissions-2.12-DEV.jar + - - - org.tyrannyofheaven.bukkit - zPermissions - 1.3-SNAPSHOT - system - ${basedir}/lib/zPermissions-1.3beta1.jar - + + + org.tyrannyofheaven.bukkit + zPermissions + 1.3-SNAPSHOT + system + ${basedir}/lib/zPermissions-1.3beta1.jar + - - - com.nijiko - permissions - 3.1.6 - system - ${basedir}/lib/Permission-3.1.6.jar - + + + com.nijiko + permissions + 3.1.6 + system + ${basedir}/lib/Permission-3.1.6.jar + - - - net.milkbowl.vault - VaultAPI - 1.5 - provided - - - org.bukkit - bukkit - - - org.bukkit - craftbukkit - - - true - + + + net.milkbowl.vault + VaultAPI + 1.5 + provided + + + org.bukkit + bukkit + + + org.bukkit + craftbukkit + + + true + - - - com.onarandombox.multiversecore - Multiverse-Core - 2.5 - jar - provided - - - org.bukkit - bukkit - - - org.bukkit - craftbukkit - - - AllPay - com.fernferret.allpay - - - Vault - net.milkbowl.vault - - - CommandHandler - com.pneumaticraft.commandhandler - - - SerializationConfig - me.main__.util - - - Logging - com.dumptruckman.minecraft - - - metrics - org.mcstats.bukkit - - - buscript - com.dumptruckman.minecraft - - - junit - junit - - - true - + + + com.onarandombox.multiversecore + Multiverse-Core + 2.5 + jar + provided + + + org.bukkit + bukkit + + + org.bukkit + craftbukkit + + + AllPay + com.fernferret.allpay + + + VaultAPI + net.milkbowl.vault + + + CommandHandler + com.pneumaticraft.commandhandler + + + SerializationConfig + me.main__.util + + + Logging + com.dumptruckman.minecraft + + + metrics + org.mcstats.bukkit + + + buscript + com.dumptruckman.minecraft + + + junit + junit + + + true + - - - net.ess3 - EssentialsX - 2.0.1-SNAPSHOT - provided - - - org.spigotmc - spigot-api - - - true - + + + net.ess3 + EssentialsX + 2.0.1-SNAPSHOT + provided + + + org.spigotmc + spigot-api + + + true + - - - net.minelink - CombatTagPlus - 1.2.1-SNAPSHOT - provided - - - org.bukkit - bukkit - - - org.bukkit - craftbukkit - - - CombatTagPlusHook - net.minelink - - - CombatTagPlusFactions-v1_6 - net.minelink - - - CombatTagPlusCompat-v1_7_R3 - net.minelink - - - CombatTagPlusFactions-v1_8 - net.minelink - - - CombatTagPlusCompat-v1_7_R4 - net.minelink - - - CombatTagPlusWG-v5 - net.minelink - - - CombatTagPlusWG-v6 - net.minelink - - - CombatTagPlusCompat-API - net.minelink - - - CombatTagPlusFactions-v2_6 - net.minelink - - - CombatTagPlusCompat-v1_8_R3 - net.minelink - - - CombatTagPlusFactions-v2_7 - net.minelink - - - CombatTagPlusCompat-v1_8_R2 - net.minelink - - - CombatTagPlusCompat-v1_8_R1 - net.minelink - - - metrics-lite - org.mcstats.bukkit - - - true - + + + net.minelink + CombatTagPlus + 1.2.1-SNAPSHOT + provided + + + org.bukkit + bukkit + + + org.bukkit + craftbukkit + + + CombatTagPlusHook + net.minelink + + + CombatTagPlusFactions-v1_6 + net.minelink + + + CombatTagPlusCompat-v1_7_R3 + net.minelink + + + CombatTagPlusFactions-v1_8 + net.minelink + + + CombatTagPlusCompat-v1_7_R4 + net.minelink + + + CombatTagPlusWG-v5 + net.minelink + + + CombatTagPlusWG-v6 + net.minelink + + + CombatTagPlusCompat-API + net.minelink + + + CombatTagPlusFactions-v2_6 + net.minelink + + + CombatTagPlusCompat-v1_8_R3 + net.minelink + + + CombatTagPlusFactions-v2_7 + net.minelink + + + CombatTagPlusCompat-v1_8_R2 + net.minelink + + + CombatTagPlusCompat-v1_8_R1 + net.minelink + + + metrics-lite + org.mcstats.bukkit + + + true + - - - de.luricos.bukkit - xAuth - 2.6 - provided - - - org.bukkit - bukkit - - - org.bukkit - craftbukkit - - - updater - net.gravitydevelopment.updater - - - lombok - org.projectlombok - - - EssentialsGroupManager - net.ess3 - - - PermissionsEx - ru.tehkode - - - AccountsClient - com.mojang - - - log4j-core - org.apache.logging.log4j - - - true - + + + de.luricos.bukkit + xAuth + 2.6 + provided + + + org.bukkit + bukkit + + + org.bukkit + craftbukkit + + + updater + net.gravitydevelopment.updater + + + lombok + org.projectlombok + + + EssentialsGroupManager + net.ess3 + + + PermissionsEx + ru.tehkode + + + AccountsClient + com.mojang + + + log4j-core + org.apache.logging.log4j + + + true + - - - junit - junit - test - 4.12 - true - - - org.hamcrest - java-hamcrest - test - 2.0.0.0 - true - - - org.mockito - mockito-core - test - 2.0.5-beta - true - + + + junit + junit + test + 4.12 + true + + + org.hamcrest + java-hamcrest + test + 2.0.0.0 + true + + + org.mockito + mockito-core + test + 2.0.5-beta + true + - - - net.ricecode - string-similarity - 1.0.0 - compile - true - - + + + net.ricecode + string-similarity + 1.0.0 + compile + true + + diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index d5aa5e2a..aff34acd 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -24,7 +24,6 @@ import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.OtherAccounts; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Spawn; -import fr.xephi.authme.AntiBot; import fr.xephi.authme.util.GeoLiteAPI; import fr.xephi.authme.util.Utils; import net.minelink.ctplus.CombatTagPlus; @@ -70,7 +69,6 @@ public class AuthMe extends JavaPlugin { * Defines the current AuthMeReloaded version code. */ // TODO: Increase this number by one when an update is release - // TODO: Increase the count via maven private static final int PLUGIN_VERSION_CODE = 100; private static AuthMe plugin; diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 6fed05cb..9001d7e2 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,4 +1,4 @@ -name: AuthMe +name: ${pluginName} authors: ${pluginAuthors} website: ${project.url} description: ${project.description} From e9b2517188f432ef9a68a1931e46f75a75860603 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Tue, 24 Nov 2015 21:55:55 +0100 Subject: [PATCH 131/199] enhance the removespeed feature --- src/main/java/fr/xephi/authme/AntiBot.java | 25 +++++++++---------- .../authme/listener/AuthMePlayerListener.java | 8 +++--- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AntiBot.java b/src/main/java/fr/xephi/authme/AntiBot.java index 16b0ff23..dc681c25 100644 --- a/src/main/java/fr/xephi/authme/AntiBot.java +++ b/src/main/java/fr/xephi/authme/AntiBot.java @@ -16,7 +16,7 @@ public class AntiBot { private static final AuthMe plugin = AuthMe.getInstance(); private static final Messages messages = plugin.getMessages(); private static final List antibotPlayers = new ArrayList<>(); - private static AntiBotStatus antiBotStatus = AntiBotStatus.DISARMED; + private static AntiBotStatus antiBotStatus = AntiBotStatus.DISABLED; public static void setupAntiBotService() { if (!Settings.enableAntiBot) { @@ -25,19 +25,19 @@ public class AntiBot { Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { @Override public void run() { - antiBotStatus = AntiBotStatus.ARMED; + antiBotStatus = AntiBotStatus.ENABLED; } }, 2400); } public static void overrideAntiBotStatus(boolean activated) { - if (antiBotStatus == AntiBotStatus.DISARMED || antiBotStatus == AntiBotStatus.DELAYED) { + if (antiBotStatus == AntiBotStatus.DISABLED) { return; } if (activated) { - antiBotStatus = AntiBotStatus.ACTIVATED; + antiBotStatus = AntiBotStatus.INACTION; } else { - antiBotStatus = AntiBotStatus.ARMED; + antiBotStatus = AntiBotStatus.ENABLED; } } @@ -46,7 +46,7 @@ public class AntiBot { } public static void activateAntiBot() { - antiBotStatus = AntiBotStatus.ACTIVATED; + antiBotStatus = AntiBotStatus.INACTION; for (String s : messages.send("antibot_auto_enabled")) { Bukkit.broadcastMessage(s); } @@ -54,8 +54,8 @@ public class AntiBot { Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { @Override public void run() { - if (antiBotStatus == AntiBotStatus.ACTIVATED) { - antiBotStatus = AntiBotStatus.ARMED; + if (antiBotStatus == AntiBotStatus.INACTION) { + antiBotStatus = AntiBotStatus.ENABLED; antibotPlayers.clear(); for (String s : messages.send("antibot_auto_disabled")) Bukkit.broadcastMessage(s.replace("%m", "" + Settings.antiBotDuration)); @@ -70,7 +70,7 @@ public class AntiBot { * @param player Player */ public static void checkAntiBot(final Player player) { - if (antiBotStatus == AntiBotStatus.ACTIVATED || antiBotStatus == AntiBotStatus.DISARMED) { + if (antiBotStatus == AntiBotStatus.INACTION || antiBotStatus == AntiBotStatus.DISABLED) { return; } if (plugin.getPermissionsManager().hasPermission(player, "authme.bypassantibot")) { @@ -91,10 +91,9 @@ public class AntiBot { } public enum AntiBotStatus { - ARMED, - DISARMED, - DELAYED, - ACTIVATED + ENABLED, + DISABLED, + INACTION } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 4a5e705a..e2852359 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -183,10 +183,12 @@ public class AuthMePlayerListener implements Listener { } if (!Settings.isMovementAllowed) { - if (event.getFrom().distance(event.getTo()) > 0) { - event.setTo(event.getFrom()); - return; + event.setTo(event.getFrom()); + if(Settings.isRemoveSpeedEnabled) { + player.setFlySpeed(0.0f); + player.setWalkSpeed(0.0f); } + return; } if (Settings.noTeleport) { From a9832533842a5d1b1d85ec87d1f9fcd1a8dfe739 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Tue, 24 Nov 2015 22:08:06 +0100 Subject: [PATCH 132/199] better names --- src/main/java/fr/xephi/authme/AntiBot.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AntiBot.java b/src/main/java/fr/xephi/authme/AntiBot.java index dc681c25..ea9fc22b 100644 --- a/src/main/java/fr/xephi/authme/AntiBot.java +++ b/src/main/java/fr/xephi/authme/AntiBot.java @@ -25,7 +25,7 @@ public class AntiBot { Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { @Override public void run() { - antiBotStatus = AntiBotStatus.ENABLED; + antiBotStatus = AntiBotStatus.LISTENING; } }, 2400); } @@ -35,9 +35,9 @@ public class AntiBot { return; } if (activated) { - antiBotStatus = AntiBotStatus.INACTION; + antiBotStatus = AntiBotStatus.ACTIVE; } else { - antiBotStatus = AntiBotStatus.ENABLED; + antiBotStatus = AntiBotStatus.LISTENING; } } @@ -46,7 +46,7 @@ public class AntiBot { } public static void activateAntiBot() { - antiBotStatus = AntiBotStatus.INACTION; + antiBotStatus = AntiBotStatus.ACTIVE; for (String s : messages.send("antibot_auto_enabled")) { Bukkit.broadcastMessage(s); } @@ -54,8 +54,8 @@ public class AntiBot { Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { @Override public void run() { - if (antiBotStatus == AntiBotStatus.INACTION) { - antiBotStatus = AntiBotStatus.ENABLED; + if (antiBotStatus == AntiBotStatus.ACTIVE) { + antiBotStatus = AntiBotStatus.LISTENING; antibotPlayers.clear(); for (String s : messages.send("antibot_auto_disabled")) Bukkit.broadcastMessage(s.replace("%m", "" + Settings.antiBotDuration)); @@ -70,7 +70,7 @@ public class AntiBot { * @param player Player */ public static void checkAntiBot(final Player player) { - if (antiBotStatus == AntiBotStatus.INACTION || antiBotStatus == AntiBotStatus.DISABLED) { + if (antiBotStatus == AntiBotStatus.ACTIVE || antiBotStatus == AntiBotStatus.DISABLED) { return; } if (plugin.getPermissionsManager().hasPermission(player, "authme.bypassantibot")) { @@ -91,9 +91,9 @@ public class AntiBot { } public enum AntiBotStatus { - ENABLED, + LISTENING, DISABLED, - INACTION + ACTIVE } } From 72fe710b8c603e82b934bc27aada220c745fe7e9 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 24 Nov 2015 22:21:36 +0100 Subject: [PATCH 133/199] Fix UtilsTest, create MessageKeyTest --- src/main/java/fr/xephi/authme/util/Utils.java | 1 + .../xephi/authme/settings/MessageKeyTest.java | 33 +++++++++++++++++++ .../xephi/authme/settings/MessagesTest.java | 22 +++++++++++-- .../java/fr/xephi/authme/util/UtilsTest.java | 4 +++ 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/settings/MessageKeyTest.java diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 9803042c..18a7e206 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -34,6 +34,7 @@ public final class Utils { static { plugin = AuthMe.getInstance(); + wrapper = new Wrapper(plugin); initializeOnlinePlayersIsCollectionField(); } diff --git a/src/test/java/fr/xephi/authme/settings/MessageKeyTest.java b/src/test/java/fr/xephi/authme/settings/MessageKeyTest.java new file mode 100644 index 00000000..2eab88aa --- /dev/null +++ b/src/test/java/fr/xephi/authme/settings/MessageKeyTest.java @@ -0,0 +1,33 @@ +package fr.xephi.authme.settings; + +import fr.xephi.authme.util.StringUtils; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.fail; + +/** + * Test for {@link MessageKey}. + */ +public class MessageKeyTest { + + @Test + public void shouldHaveUniqueMessageKeys() { + // given + MessageKey[] messageKeys = MessageKey.values(); + Set keys = new HashSet<>(); + + // when / then + for (MessageKey messageKey : messageKeys) { + String key = messageKey.getKey(); + if (keys.contains(key)) { + fail("Found key '" + messageKey.getKey() + "' twice!"); + } else if (StringUtils.isEmpty(key)) { + fail("Key for message key '" + messageKey + "' is empty"); + } + keys.add(key); + } + } +} diff --git a/src/test/java/fr/xephi/authme/settings/MessagesTest.java b/src/test/java/fr/xephi/authme/settings/MessagesTest.java index 3d4f138e..842019f2 100644 --- a/src/test/java/fr/xephi/authme/settings/MessagesTest.java +++ b/src/test/java/fr/xephi/authme/settings/MessagesTest.java @@ -7,22 +7,38 @@ import org.junit.Before; import org.junit.Test; import java.io.File; +import java.net.URL; + +import static java.util.Arrays.asList; /** * Test for {@link Messages}. */ public class MessagesTest { + private static final String YML_TEST_FILE = "messages_test.yml"; + private Messages messages; + @Before public void setUpMessages() { AuthMe authMe = AuthMeMockUtil.mockAuthMeInstance(); AuthMeMockUtil.insertMockWrapperInstance(ConsoleLogger.class, "wrapper", authMe); - File file = new File("messages_test.yml"); - Messages messages = new Messages(file, "en"); + + Settings.messagesLanguage = "en"; + URL url = getClass().getClassLoader().getResource(YML_TEST_FILE); + if (url == null) { + throw new RuntimeException("File '" + YML_TEST_FILE + "' could not be loaded"); + } + + File file = new File(url.getFile()); + messages = new Messages(file, "en"); } @Test public void shouldLoadMessages() { - + // given + // when + String[] send = messages.send(MessageKey.CAPTCHA_WRONG_ERROR.getKey()); + System.out.println(asList(send)); } } diff --git a/src/test/java/fr/xephi/authme/util/UtilsTest.java b/src/test/java/fr/xephi/authme/util/UtilsTest.java index 97b64184..f228947f 100644 --- a/src/test/java/fr/xephi/authme/util/UtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/UtilsTest.java @@ -44,10 +44,14 @@ public class UtilsTest { BukkitScheduler schedulerMock = mock(BukkitScheduler.class); when(serverMock.getScheduler()).thenReturn(schedulerMock); + + when(schedulerMock.runTaskAsynchronously(any(Plugin.class), any(Runnable.class))) .thenReturn(mock(BukkitTask.class)); + System.out.println("Initialized scheduler mock for server mock"); AuthMeMockUtil.insertMockWrapperInstance(Utils.class, "wrapper", (WrapperMock) wrapperMock); + System.out.println("Iniadfk"); permissionsManagerMock = mock(PermissionsManager.class); when(authMeMock.getPermissionsManager()).thenReturn(permissionsManagerMock); From cf4e47488bd5849e180cc4202ef0875b642cce64 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 24 Nov 2015 22:54:54 +0100 Subject: [PATCH 134/199] Start refactoring for Messages class --- .../fr/xephi/authme/settings/Messages.java | 71 +++++++++++++++---- .../xephi/authme/settings/MessagesTest.java | 61 ++++++++++++++-- src/test/resources/messages_test.yml | 58 +-------------- 3 files changed, 116 insertions(+), 74 deletions(-) diff --git a/src/main/java/fr/xephi/authme/settings/Messages.java b/src/main/java/fr/xephi/authme/settings/Messages.java index 32a5192b..1b03ed95 100644 --- a/src/main/java/fr/xephi/authme/settings/Messages.java +++ b/src/main/java/fr/xephi/authme/settings/Messages.java @@ -7,10 +7,14 @@ import java.io.File; /** */ +// TODO ljacqu 20151124: This class is a weird mix between singleton and POJO public class Messages extends CustomConfiguration { + /** The section symbol, used in Minecraft for formatting codes. */ + private static final String SECTION_SIGN = "\u00a7"; private static Messages singleton = null; - private String lang = "en"; + private String language = "en"; + /** * Constructor for Messages. @@ -22,7 +26,7 @@ public class Messages extends CustomConfiguration { super(file); load(); singleton = this; - this.lang = lang; + this.language = lang; } public static Messages getInstance() { @@ -32,8 +36,14 @@ public class Messages extends CustomConfiguration { return singleton; } + /** + * Sends the given message code to the player. + * + * @param sender The entity to send the message to + * @param msg The message code to send + */ public void send(CommandSender sender, String msg) { - if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.lang)) { + if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.language)) { singleton.reloadMessages(); } String loc = (String) singleton.get(msg); @@ -42,26 +52,28 @@ public class Messages extends CustomConfiguration { ConsoleLogger.showError("Error with the " + msg + " translation, verify in your " + getConfigFile() + " !"); } for (String l : loc.split("&n")) { - sender.sendMessage(l.replace("&", "\u00a7")); + sender.sendMessage(l.replace("&", SECTION_SIGN)); } } + public String[] send(MessageKey key) { + return send(key.getKey()); + } + + /** + * Retrieve the message from the text file and returns it split by new line as an array. + * + * @param msg The message code to retrieve + * + * @return The message split by new lines + */ public String[] send(String msg) { - if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.lang)) { - singleton.reloadMessages(); - } - String s = (String) singleton.get(msg); - if (s == null) { - ConsoleLogger.showError("Error with the " + msg + " translation, verify in your " + getConfigFile() + " !"); - String[] loc = new String[1]; - loc[0] = "Error with " + msg + " translation; Please contact the admin for verify or update translation files"; - return (loc); - } + String s = retrieveMessage(msg); int i = s.split("&n").length; String[] loc = new String[i]; int a; for (a = 0; a < i; a++) { - loc[a] = ((String) this.get(msg)).split("&n")[a].replace("&", "\u00a7"); + loc[a] = s.split("&n")[a].replace("&", SECTION_SIGN); } if (loc.length == 0) { loc[0] = "Error with " + msg + " translation; Please contact the admin for verify or update translation files"; @@ -69,6 +81,35 @@ public class Messages extends CustomConfiguration { return loc; } + /** + * Retrieve the message from the configuration file. + * + * @param key The key to retrieve + * + * @return The message + */ + private static String retrieveMessage(String key) { + if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.language)) { + singleton.reloadMessages(); + } + String message = (String) singleton.get(key); + if (message != null) { + return message; + } + + // Message is null: log key not being found and send error back as message + String retrievalError = "Error getting message with key '" + key + "'. "; + ConsoleLogger.showError(retrievalError + "Please verify your config file at '" + + singleton.getConfigFile().getName() + "'"); + return retrievalError + "Please contact the admin to verify or update the AuthMe messages file."; + } + + private static String formatChatCodes(String message) { + // TODO: Check that the codes actually exist, i.e. replace &c but not &y + // TODO: Allow '&' to be retained with the code '&&' + return message.replace("&", SECTION_SIGN); + } + public void reloadMessages() { singleton = new Messages(Settings.messageFile, Settings.messagesLanguage); } diff --git a/src/test/java/fr/xephi/authme/settings/MessagesTest.java b/src/test/java/fr/xephi/authme/settings/MessagesTest.java index 842019f2..11ce20ee 100644 --- a/src/test/java/fr/xephi/authme/settings/MessagesTest.java +++ b/src/test/java/fr/xephi/authme/settings/MessagesTest.java @@ -9,7 +9,10 @@ import org.junit.Test; import java.io.File; import java.net.URL; -import static java.util.Arrays.asList; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertThat; /** * Test for {@link Messages}. @@ -19,6 +22,11 @@ public class MessagesTest { private static final String YML_TEST_FILE = "messages_test.yml"; private Messages messages; + /** + * Loads the messages in the file {@code messages_test.yml} in the test resources folder. + * The file does not contain all messages defined in {@link MessageKey} and its contents + * reflect various test cases -- not what the keys stand for. + */ @Before public void setUpMessages() { AuthMe authMe = AuthMeMockUtil.mockAuthMeInstance(); @@ -35,10 +43,55 @@ public class MessagesTest { } @Test - public void shouldLoadMessages() { + public void shouldLoadMessageAndSplitAtNewLines() { // given + MessageKey key = MessageKey.UNKNOWN_USER; + // when - String[] send = messages.send(MessageKey.CAPTCHA_WRONG_ERROR.getKey()); - System.out.println(asList(send)); + String[] send = messages.send(key); + + // then + String[] lines = new String[]{"This test message", "includes", "some new lines"}; + assertThat(send, equalTo(lines)); + } + + @Test + public void shouldFormatColorCodes() { + // given + MessageKey key = MessageKey.UNSAFE_QUIT_LOCATION; + + // when + String[] message = messages.send(key); + + // then + assertThat(message, arrayWithSize(1)); + assertThat(message[0], equalTo("§cHere we have§bdefined some colors §dand some other §lthings")); + } + + @Test + public void shouldRetainApostrophes() { + // given + MessageKey key = MessageKey.NOT_LOGGED_IN; + + // when + String[] message = messages.send(key); + + // then + assertThat(message, arrayWithSize(1)); + assertThat(message[0], equalTo("Apostrophes ' should be loaded correctly, don't you think?")); + } + + @Test + public void shouldReturnErrorForUnknownCode() { + // given + // The following is a key that is not defined in the test file + MessageKey key = MessageKey.UNREGISTERED_SUCCESS; + + // when + String[] message = messages.send(key); + + // then + assertThat(message, arrayWithSize(1)); + assertThat(message[0], startsWith("Error getting message with key '")); } } diff --git a/src/test/resources/messages_test.yml b/src/test/resources/messages_test.yml index 16218f32..23dd12bd 100644 --- a/src/test/resources/messages_test.yml +++ b/src/test/resources/messages_test.yml @@ -1,58 +1,6 @@ -unknown_user: '&cCan''t find the requested user in the database!' -unsafe_spawn: '&cYour quit location was unsafe, you have been teleported to the world''s spawnpoint.' -not_logged_in: '&cYou''re not logged in!' +unknown_user: 'This test message&nincludes&nsome new lines' +unsafe_spawn: '&cHere we have&bdefined some colors &dand some other <hings' +not_logged_in: 'Apostrophes '' should be loaded correctly, don''t you think?' reg_voluntarily: 'You can register yourself to the server with the command "/register "' usage_log: '&cUsage: /login ' wrong_pwd: '&cWrong password!' -unregistered: '&cSuccessfully unregistered!' -reg_disabled: '&cIn-game registration is disabled!' -valid_session: '&2Logged-in due to Session Reconnection.' -login: '&2Successful login!' -vb_nonActiv: '&cYour account isn''t activated yet, please check your emails!' -user_regged: '&cYou already have registered this username!' -usage_reg: '&cUsage: /register ' -max_reg: '&cYou have exceeded the maximum number of registrations for your connection!' -no_perm: '&4You don''t have the permission to perform this action!' -error: '&4An unexpected error occurred, please contact an Administrator!' -login_msg: '&cPlease, login with the command "/login "' -reg_msg: '&3Please, register to the server with the command "/register "' -reg_email_msg: '&3Please, register to the server with the command "/register "' -usage_unreg: '&cUsage: /unregister ' -pwd_changed: '&2Password changed successfully!' -user_unknown: '&cThis user isn''t registered!' -password_error: '&cPasswords didn''t match, check them again!' -password_error_nick: '&cYou can''t use your name as password, please choose another one...' -password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' -invalid_session: '&cYour IP has been changed and your session data has expired!' -reg_only: '&4Only registered users can join the server! Please visit http://example.com to register yourself!' -logged_in: '&cYou''re already logged in!' -logout: '&2Logged-out successfully!' -same_nick: '&4The same username is already playing on the server!' -registered: '&2Successfully registered!' -pass_len: '&cYour password is too short or too long! Please try with another one!' -reload: '&2Configuration and database have been reloaded correctly!' -timeout: '&4Login timeout exceeded, you have been kicked from the server, please try again!' -usage_changepassword: '&cUsage: /changepassword ' -name_len: '&4Your username is either too short or too long!' -regex: '&4Your username contains illegal characters. Allowed chars: REG_EX' -add_email: '&3Please add your email to your account with the command "/email add "' -recovery_email: '&3Forgot your password? Please use the command "/email recovery "' -usage_captcha: '&3To login you have to solve a captcha code, please use the command "/captcha "' -wrong_captcha: '&cWrong Captcha, please type "/captcha THE_CAPTCHA" into the chat!' -valid_captcha: '&2Captcha code solved correctly!' -kick_forvip: '&3A VIP Player has joined the server when it was full!' -kick_fullserver: '&4The server is full, try again later!' -usage_email_add: '&cUsage: /email add ' -usage_email_change: '&cUsage: /email change ' -usage_email_recovery: '&cUsage: /email recovery ' -new_email_invalid: '&cInvalid New Email, try again!' -old_email_invalid: '&cInvalid Old Email, try again!' -email_invalid: '&cInvalid Email address, try again!' -email_added: '&2Email address successfully added to your account!' -email_confirm: '&cPlease confirm your email address!' -email_changed: '&2Email address changed correctly!' -email_send: '&2Recovery email sent correctly! Check your email inbox!' -email_exists: '&cA recovery email was already sent! You can discart it and send a new one using the command below:' -country_banned: '&4Your country is banned from this server!' -antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' -antibot_auto_disabled: '&2[AntiBotService] AntiBot disabled disabled after %m minutes!' From 9f199b1a5a2b6e48e83fd060687fcaf3620dee0e Mon Sep 17 00:00:00 2001 From: DNx Date: Wed, 25 Nov 2015 05:04:38 +0700 Subject: [PATCH 135/199] Update AuthMePlayerListener.java --- .../java/fr/xephi/authme/listener/AuthMePlayerListener.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index e2852359..5fb464f4 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -57,6 +57,12 @@ public class AuthMePlayerListener implements Listener { * @param event AsyncPlayerChatEvent */ private void handleChat(AsyncPlayerChatEvent event) { + for (Player p : Utils.getOnlinePlayers()) { + if (p != player && !PlayerCache.getInstance().isAuthenticated(p)) { + event.getRecipients().remove(p); + } + } + if (Settings.isChatAllowed) { return; } From da390530e9866b9afd1aa724eb959257e2c1a2b6 Mon Sep 17 00:00:00 2001 From: DNx Date: Wed, 25 Nov 2015 05:05:41 +0700 Subject: [PATCH 136/199] Update AuthMePlayerListener.java --- .../java/fr/xephi/authme/listener/AuthMePlayerListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 5fb464f4..563f332e 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -57,6 +57,7 @@ public class AuthMePlayerListener implements Listener { * @param event AsyncPlayerChatEvent */ private void handleChat(AsyncPlayerChatEvent event) { + Player player = event.getPlayer(); for (Player p : Utils.getOnlinePlayers()) { if (p != player && !PlayerCache.getInstance().isAuthenticated(p)) { event.getRecipients().remove(p); @@ -67,7 +68,6 @@ public class AuthMePlayerListener implements Listener { return; } - Player player = event.getPlayer(); if (Utils.checkAuth(player)) { return; } From be1eb9392aaf660185a2dce90b82e0fc21e3ab98 Mon Sep 17 00:00:00 2001 From: DNx Date: Wed, 25 Nov 2015 05:10:13 +0700 Subject: [PATCH 137/199] ugh --- .../java/fr/xephi/authme/listener/AuthMePlayerListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 563f332e..89b75285 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -59,7 +59,7 @@ public class AuthMePlayerListener implements Listener { private void handleChat(AsyncPlayerChatEvent event) { Player player = event.getPlayer(); for (Player p : Utils.getOnlinePlayers()) { - if (p != player && !PlayerCache.getInstance().isAuthenticated(p)) { + if (p != player && !PlayerCache.getInstance().isAuthenticated(p.getName())) { event.getRecipients().remove(p); } } From cb11ae9610cbcb0836bd37f1f1b079f500e2d605 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 24 Nov 2015 23:11:14 +0100 Subject: [PATCH 138/199] Deprecate Messages#send(String) in favor of the MessageKey enum --- .../fr/xephi/authme/settings/Messages.java | 69 ++++++++++--------- .../xephi/authme/settings/MessagesTest.java | 40 +++++++++-- 2 files changed, 74 insertions(+), 35 deletions(-) diff --git a/src/main/java/fr/xephi/authme/settings/Messages.java b/src/main/java/fr/xephi/authme/settings/Messages.java index 1b03ed95..99f30656 100644 --- a/src/main/java/fr/xephi/authme/settings/Messages.java +++ b/src/main/java/fr/xephi/authme/settings/Messages.java @@ -37,27 +37,36 @@ public class Messages extends CustomConfiguration { } /** - * Sends the given message code to the player. + * Send the given message code to the player. * * @param sender The entity to send the message to - * @param msg The message code to send + * @param key The key of the message to send */ - public void send(CommandSender sender, String msg) { - if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.language)) { - singleton.reloadMessages(); - } - String loc = (String) singleton.get(msg); - if (loc == null) { - loc = "Error with Translation files, please contact the admin for verify or update translation"; - ConsoleLogger.showError("Error with the " + msg + " translation, verify in your " + getConfigFile() + " !"); - } - for (String l : loc.split("&n")) { - sender.sendMessage(l.replace("&", SECTION_SIGN)); + public void send(CommandSender sender, MessageKey key) { + String[] lines = retrieve(key); + for (String line : lines) { + sender.sendMessage(line); } } - public String[] send(MessageKey key) { - return send(key.getKey()); + /** + * Send the given message code to the player. + * + * @param sender The entity to send the message to + * @param msg The message code to send + * + * @deprecated Use {@link Messages#send(CommandSender, MessageKey)} instead + */ + @Deprecated + public void send(CommandSender sender, String msg) { + String[] lines = retrieve(msg); + for (String line : lines) { + sender.sendMessage(line); + } + } + + public String[] retrieve(MessageKey key) { + return retrieve(key.getKey()); } /** @@ -66,19 +75,11 @@ public class Messages extends CustomConfiguration { * @param msg The message code to retrieve * * @return The message split by new lines + * @deprecated Use {@link Messages#retrieve(MessageKey)} instead. */ + @Deprecated public String[] send(String msg) { - String s = retrieveMessage(msg); - int i = s.split("&n").length; - String[] loc = new String[i]; - int a; - for (a = 0; a < i; a++) { - loc[a] = s.split("&n")[a].replace("&", SECTION_SIGN); - } - if (loc.length == 0) { - loc[0] = "Error with " + msg + " translation; Please contact the admin for verify or update translation files"; - } - return loc; + return retrieve(msg); } /** @@ -88,26 +89,32 @@ public class Messages extends CustomConfiguration { * * @return The message */ - private static String retrieveMessage(String key) { + private static String[] retrieve(String key) { if (!Settings.messagesLanguage.equalsIgnoreCase(singleton.language)) { singleton.reloadMessages(); } String message = (String) singleton.get(key); if (message != null) { - return message; + return formatMessage(message); } // Message is null: log key not being found and send error back as message String retrievalError = "Error getting message with key '" + key + "'. "; ConsoleLogger.showError(retrievalError + "Please verify your config file at '" + singleton.getConfigFile().getName() + "'"); - return retrievalError + "Please contact the admin to verify or update the AuthMe messages file."; + return new String[]{ + retrievalError + "Please contact the admin to verify or update the AuthMe messages file."}; } - private static String formatChatCodes(String message) { + private static String[] formatMessage(String message) { // TODO: Check that the codes actually exist, i.e. replace &c but not &y // TODO: Allow '&' to be retained with the code '&&' - return message.replace("&", SECTION_SIGN); + String[] lines = message.split("&n"); + for (int i = 0; i < lines.length; ++i) { + // We don't initialize a StringBuilder here because mostly we will only have one entry + lines[i] = lines[i].replace("&", SECTION_SIGN); + } + return lines; } public void reloadMessages() { diff --git a/src/test/java/fr/xephi/authme/settings/MessagesTest.java b/src/test/java/fr/xephi/authme/settings/MessagesTest.java index 11ce20ee..91f6276a 100644 --- a/src/test/java/fr/xephi/authme/settings/MessagesTest.java +++ b/src/test/java/fr/xephi/authme/settings/MessagesTest.java @@ -3,8 +3,10 @@ package fr.xephi.authme.settings; import fr.xephi.authme.AuthMe; import fr.xephi.authme.AuthMeMockUtil; import fr.xephi.authme.ConsoleLogger; +import org.bukkit.entity.Player; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import java.io.File; import java.net.URL; @@ -13,6 +15,7 @@ import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.verify; /** * Test for {@link Messages}. @@ -48,7 +51,7 @@ public class MessagesTest { MessageKey key = MessageKey.UNKNOWN_USER; // when - String[] send = messages.send(key); + String[] send = messages.retrieve(key); // then String[] lines = new String[]{"This test message", "includes", "some new lines"}; @@ -61,7 +64,7 @@ public class MessagesTest { MessageKey key = MessageKey.UNSAFE_QUIT_LOCATION; // when - String[] message = messages.send(key); + String[] message = messages.retrieve(key); // then assertThat(message, arrayWithSize(1)); @@ -74,7 +77,7 @@ public class MessagesTest { MessageKey key = MessageKey.NOT_LOGGED_IN; // when - String[] message = messages.send(key); + String[] message = messages.retrieve(key); // then assertThat(message, arrayWithSize(1)); @@ -88,10 +91,39 @@ public class MessagesTest { MessageKey key = MessageKey.UNREGISTERED_SUCCESS; // when - String[] message = messages.send(key); + String[] message = messages.retrieve(key); // then assertThat(message, arrayWithSize(1)); assertThat(message[0], startsWith("Error getting message with key '")); } + + @Test + public void shouldSendMessageToPlayer() { + // given + MessageKey key = MessageKey.UNSAFE_QUIT_LOCATION; + Player player = Mockito.mock(Player.class); + + // when + messages.send(player, key); + + // then + verify(player).sendMessage("§cHere we have§bdefined some colors §dand some other §lthings"); + } + + @Test + public void shouldSendMultiLineMessageToPlayer() { + // given + MessageKey key = MessageKey.UNKNOWN_USER; + Player player = Mockito.mock(Player.class); + + // when + messages.send(player, key); + + // then + String[] lines = new String[]{"This test message", "includes", "some new lines"}; + for (String line : lines) { + verify(player).sendMessage(line); + } + } } From 2c0d140da99485ae2c16c31a2e25c34e77e15e97 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Tue, 24 Nov 2015 23:23:45 +0100 Subject: [PATCH 139/199] Try to fix NPC issues --- .../java/fr/xephi/authme/listener/AuthMePlayerListener.java | 4 ++-- src/main/java/fr/xephi/authme/util/Utils.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 89b75285..502d2f4f 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -220,7 +220,7 @@ public class AuthMePlayerListener implements Listener { */ @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerJoin(PlayerJoinEvent event) { - if (event.getPlayer() == null) { + if (event.getPlayer() == null || Utils.isNPC(event.getPlayer())) { return; } @@ -256,7 +256,7 @@ public class AuthMePlayerListener implements Listener { public void onPreLogin(AsyncPlayerPreLoginEvent event) { final String name = event.getName().toLowerCase(); final Player player = Utils.getPlayer(name); - if (player == null) { + if (player == null || Utils.isNPC(player)) { return; } diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index bf8821ab..99a01f8f 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -128,7 +128,7 @@ public final class Utils { // TODO: Move to a Manager public static boolean checkAuth(Player player) { - if (player == null || Utils.isUnrestricted(player)) { + if (player == null || Utils.isUnrestricted(player) || Utils.isNPC(player)) { return true; } From d2b3d416a969be7da9a798671b7e3d35df6b1dd0 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 24 Nov 2015 23:27:28 +0100 Subject: [PATCH 140/199] Change calls to Messages to use the MessageKey enum --- src/main/java/fr/xephi/authme/AntiBot.java | 11 ++--- .../executable/authme/UnregisterCommand.java | 14 +++--- .../executable/captcha/CaptchaCommand.java | 22 +++------ .../process/email/AsyncChangeEmail.java | 45 +++++++------------ .../authme/process/join/AsynchronousJoin.java | 35 +++++---------- 5 files changed, 47 insertions(+), 80 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AntiBot.java b/src/main/java/fr/xephi/authme/AntiBot.java index ea9fc22b..a608a598 100644 --- a/src/main/java/fr/xephi/authme/AntiBot.java +++ b/src/main/java/fr/xephi/authme/AntiBot.java @@ -1,5 +1,6 @@ package fr.xephi.authme; +import fr.xephi.authme.settings.MessageKey; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; import org.bukkit.Bukkit; @@ -47,7 +48,7 @@ public class AntiBot { public static void activateAntiBot() { antiBotStatus = AntiBotStatus.ACTIVE; - for (String s : messages.send("antibot_auto_enabled")) { + for (String s : messages.retrieve(MessageKey.ANTIBOT_AUTO_ENABLED_MESSAGE)) { Bukkit.broadcastMessage(s); } @@ -57,18 +58,14 @@ public class AntiBot { if (antiBotStatus == AntiBotStatus.ACTIVE) { antiBotStatus = AntiBotStatus.LISTENING; antibotPlayers.clear(); - for (String s : messages.send("antibot_auto_disabled")) + for (String s : messages.retrieve(MessageKey.ANTIBOT_AUTO_DISABLED_MESSAGE)) { Bukkit.broadcastMessage(s.replace("%m", "" + Settings.antiBotDuration)); + } } } }, Settings.antiBotDuration * 1200); } - /** - * Method checkAntiBotMod. - * - * @param player Player - */ public static void checkAntiBot(final Player player) { if (antiBotStatus == AntiBotStatus.ACTIVE || antiBotStatus == AntiBotStatus.DISABLED) { return; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/UnregisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/UnregisterCommand.java index 2dcde4ab..ddf1c8ec 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/UnregisterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/UnregisterCommand.java @@ -6,6 +6,7 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.command.CommandParts; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.settings.MessageKey; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.task.MessageTask; @@ -46,18 +47,17 @@ public class UnregisterCommand extends ExecutableCommand { // Make sure the user is valid if (!plugin.database.isAuthAvailable(playerNameLowerCase)) { - m.send(sender, "user_unknown"); + m.send(sender, MessageKey.UNKNOWN_USER); return true; } // Remove the player if (!plugin.database.removeAuth(playerNameLowerCase)) { - m.send(sender, "error"); + m.send(sender, MessageKey.ERROR); return true; } // Unregister the player - @SuppressWarnings("deprecation") Player target = Bukkit.getPlayer(playerNameLowerCase); PlayerCache.getInstance().removePlayer(playerNameLowerCase); Utils.setGroup(target, Utils.GroupType.UNREGISTERED); @@ -71,19 +71,21 @@ public class UnregisterCommand extends ExecutableCommand { BukkitTask id = scheduler.runTaskLaterAsynchronously(plugin, new TimeoutTask(plugin, playerNameLowerCase, target), delay); LimboCache.getInstance().getLimboPlayer(playerNameLowerCase).setTimeoutTaskId(id); } - LimboCache.getInstance().getLimboPlayer(playerNameLowerCase).setMessageTaskId(scheduler.runTaskAsynchronously(plugin, new MessageTask(plugin, playerNameLowerCase, m.send("reg_msg"), interval))); + LimboCache.getInstance().getLimboPlayer(playerNameLowerCase).setMessageTaskId( + scheduler.runTaskAsynchronously(plugin, + new MessageTask(plugin, playerNameLowerCase, m.retrieve(MessageKey.REGISTER_MESSAGE), interval))); if (Settings.applyBlindEffect) target.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, Settings.getRegistrationTimeout * 20, 2)); if (!Settings.isMovementAllowed && Settings.isRemoveSpeedEnabled) { target.setWalkSpeed(0.0f); target.setFlySpeed(0.0f); } - m.send(target, "unregistered"); + m.send(target, MessageKey.UNREGISTERED_SUCCESS); } // Show a status message - m.send(sender, "unregistered"); + m.send(sender, MessageKey.UNREGISTERED_SUCCESS); ConsoleLogger.info(playerName + " unregistered"); return true; } diff --git a/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java b/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java index 900fd3e7..bcf013ef 100644 --- a/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java @@ -5,6 +5,7 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.command.CommandParts; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.settings.MessageKey; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; import org.bukkit.command.CommandSender; @@ -14,15 +15,6 @@ import org.bukkit.entity.Player; */ public class CaptchaCommand extends ExecutableCommand { - /** - * Execute the command. - * - * @param sender The command sender. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - * @return True if the command was executed successfully, false otherwise. - */ @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // Make sure the current command executor is a player @@ -42,12 +34,12 @@ public class CaptchaCommand extends ExecutableCommand { // Command logic if (PlayerCache.getInstance().isAuthenticated(playerNameLowerCase)) { - m.send(player, "logged_in"); + m.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); return true; } if (!Settings.useCaptcha) { - m.send(player, "usage_log"); + m.send(player, MessageKey.USAGE_LOGIN); return true; } @@ -55,7 +47,7 @@ public class CaptchaCommand extends ExecutableCommand { final AuthMe plugin = AuthMe.getInstance(); if (!plugin.cap.containsKey(playerNameLowerCase)) { - m.send(player, "usage_log"); + m.send(player, MessageKey.USAGE_LOGIN); return true; } @@ -63,7 +55,7 @@ public class CaptchaCommand extends ExecutableCommand { plugin.cap.remove(playerNameLowerCase); String randStr = new RandomString(Settings.captchaLength).nextString(); plugin.cap.put(playerNameLowerCase, randStr); - for (String s : m.send("wrong_captcha")) { + for (String s : m.retrieve(MessageKey.CAPTCHA_WRONG_ERROR)) { player.sendMessage(s.replace("THE_CAPTCHA", plugin.cap.get(playerNameLowerCase))); } return true; @@ -73,8 +65,8 @@ public class CaptchaCommand extends ExecutableCommand { plugin.cap.remove(playerNameLowerCase); // Show a status message - m.send(player, "valid_captcha"); - m.send(player, "login_msg"); + m.send(player, MessageKey.CAPTCHA_SUCCESS); + m.send(player, MessageKey.LOGIN_MESSAGE); return true; } } diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java index e85bac0a..e7147dce 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java @@ -4,6 +4,7 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.settings.MessageKey; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; import org.bukkit.entity.Player; @@ -21,15 +22,6 @@ public class AsyncChangeEmail { private final String newEmailVerify; private final Messages m; - /** - * Constructor for AsyncChangeEmail. - * - * @param player Player - * @param plugin AuthMe - * @param oldEmail String - * @param newEmail String - * @param newEmailVerify String - */ public AsyncChangeEmail(Player player, AuthMe plugin, String oldEmail, String newEmail, String newEmailVerify) { this.player = player; this.plugin = plugin; @@ -39,14 +31,6 @@ public class AsyncChangeEmail { this.m = Messages.getInstance(); } - /** - * Constructor for AsyncChangeEmail. - * - * @param player Player - * @param plugin AuthMe - * @param oldEmail String - * @param newEmail String - */ public AsyncChangeEmail(Player player, AuthMe plugin, String oldEmail, String newEmail) { this(player, plugin, oldEmail, newEmail, newEmail); } @@ -56,7 +40,9 @@ public class AsyncChangeEmail { String playerName = player.getName().toLowerCase(); if (Settings.getmaxRegPerEmail > 0) { - if (!plugin.getPermissionsManager().hasPermission(player, "authme.allow2accounts") && plugin.database.getAllAuthsByEmail(newEmail).size() >= Settings.getmaxRegPerEmail) { + if (!plugin.getPermissionsManager().hasPermission(player, "authme.allow2accounts") + && plugin.database.getAllAuthsByEmail(newEmail).size() >= Settings.getmaxRegPerEmail) { + // TODO ljacqu 20151124: max_reg is not in enum m.send(player, "max_reg"); return; } @@ -64,53 +50,54 @@ public class AsyncChangeEmail { if (PlayerCache.getInstance().isAuthenticated(playerName)) { if (!newEmail.equals(newEmailVerify)) { - m.send(player, "email_confirm"); + m.send(player, MessageKey.CONFIRM_EMAIL_MESSAGE); return; } PlayerAuth auth = PlayerCache.getInstance().getAuth(playerName); if (oldEmail != null) { if (auth.getEmail() == null || auth.getEmail().equals("your@email.com") || auth.getEmail().isEmpty()) { - m.send(player, "usage_email_add"); + m.send(player, MessageKey.USAGE_ADD_EMAIL); return; } if (!oldEmail.equals(auth.getEmail())) { - m.send(player, "old_email_invalid"); + m.send(player, MessageKey.INVALID_OLD_EMAIL); return; } } if (!Settings.isEmailCorrect(newEmail)) { - m.send(player, "new_email_invalid"); + m.send(player, MessageKey.INVALID_NEW_EMAIL); return; } String old = auth.getEmail(); auth.setEmail(newEmail); if (!plugin.database.updateEmail(auth)) { - m.send(player, "error"); + m.send(player, MessageKey.ERROR); auth.setEmail(old); return; } PlayerCache.getInstance().updatePlayer(auth); if (oldEmail == null) { - m.send(player, "email_added"); + m.send(player, MessageKey.EMAIL_ADDED_SUCCESS); player.sendMessage(auth.getEmail()); return; } - m.send(player, "email_changed"); + m.send(player, MessageKey.EMAIL_CHANGED_SUCCESS); + // TODO ljacqu 20151124: Did I really miss "email_defined" or is it not present in the 'en' messages? player.sendMessage(Arrays.toString(m.send("email_defined")) + auth.getEmail()); } else { if (plugin.database.isAuthAvailable(playerName)) { - m.send(player, "login_msg"); + m.send(player, MessageKey.LOGIN_MESSAGE); } else { if (Settings.emailRegistration) - m.send(player, "reg_email_msg"); + m.send(player, MessageKey.REGISTER_EMAIL_MESSAGE); else - m.send(player, "reg_msg"); + m.send(player, MessageKey.REGISTER_MESSAGE); } } } catch (Exception e) { ConsoleLogger.showError(e.getMessage()); ConsoleLogger.writeStackTrace(e); - m.send(player, "error"); + m.send(player, MessageKey.ERROR); } } } diff --git a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java index a05c726d..f3604cde 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -10,6 +10,7 @@ import fr.xephi.authme.events.FirstSpawnTeleportEvent; import fr.xephi.authme.events.ProtectInventoryEvent; import fr.xephi.authme.events.SpawnTeleportEvent; import fr.xephi.authme.listener.AuthMePlayerListener; +import fr.xephi.authme.settings.MessageKey; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Spawn; @@ -38,13 +39,6 @@ public class AsynchronousJoin { private final Messages m; private final BukkitScheduler sched; - /** - * Constructor for AsynchronousJoin. - * - * @param player Player - * @param plugin AuthMe - * @param database DataSource - */ public AsynchronousJoin(Player player, AuthMe plugin, DataSource database) { this.player = player; this.plugin = plugin; @@ -239,25 +233,26 @@ public class AsynchronousJoin { database.setUnlogged(name); PlayerCache.getInstance().removePlayer(name); if (auth != null && auth.getIp().equals(ip)) { - m.send(player, "valid_session"); + m.send(player, MessageKey.SESSION_RECONNECTION); plugin.management.performLogin(player, "dontneed", true); return; } else if (Settings.sessionExpireOnIpChange) { - m.send(player, "invalid_session"); + m.send(player, MessageKey.SESSION_EXPIRED); } } - String[] msg = isAuthAvailable ? m.send("login_msg") : - m.send("reg_" + (Settings.emailRegistration ? "email_" : "") + "msg"); + String[] msg; + if (isAuthAvailable) { + msg = m.retrieve(MessageKey.LOGIN_MESSAGE); + } else { + msg = Settings.emailRegistration + ? m.retrieve(MessageKey.REGISTER_EMAIL_MESSAGE) + : m.retrieve(MessageKey.REGISTER_MESSAGE); + } BukkitTask msgTask = sched.runTaskAsynchronously(plugin, new MessageTask(plugin, name, msg, msgInterval)); LimboCache.getInstance().getLimboPlayer(name).setMessageTaskId(msgTask); } - /** - * Method needFirstSpawn. - * - * @return boolean - */ private boolean needFirstSpawn() { if (player.hasPlayedBefore()) return false; @@ -282,12 +277,6 @@ public class AsynchronousJoin { return true; } - /** - * Method placePlayerSafely. - * - * @param player Player - * @param spawnLoc Location - */ private void placePlayerSafely(final Player player, final Location spawnLoc) { if (spawnLoc == null) return; @@ -307,7 +296,7 @@ public class AsynchronousJoin { Material top = player.getLocation().add(0D, 1D, 0D).getBlock().getType(); if (cur == Material.PORTAL || cur == Material.ENDER_PORTAL || top == Material.PORTAL || top == Material.ENDER_PORTAL) { - m.send(player, "unsafe_spawn"); + m.send(player, MessageKey.UNSAFE_QUIT_LOCATION); player.teleport(spawnLoc); } } From 2c2dd8e0d5ca321481c2203b695c03d3bb01a932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Wed, 25 Nov 2015 01:13:06 +0100 Subject: [PATCH 141/199] Disabled NPC check in player quit event --- .idea/codeStyleSettings.xml | 1 + .../java/fr/xephi/authme/process/quit/AsynchronousQuit.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml index 6e95fe79..757a8a17 100644 --- a/.idea/codeStyleSettings.xml +++ b/.idea/codeStyleSettings.xml @@ -13,5 +13,6 @@


###Plugin Description: @@ -129,7 +128,7 @@ typing commands or use the inventory. It can also kick players with uncommon lon
#####Donate -

Do you like my work? Do you want to buy me a coffee? :)
+

Do you like our work? Do you want to buy us a coffee? :)
EUR: USD:

From 109c85f4dd1d7c4480bf7fcb571fb58a20acf323 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sun, 29 Nov 2015 14:27:44 +0100 Subject: [PATCH 196/199] Auto build number + helpHeader config --- pom.xml | 65 +++++++++++++++++++ src/main/java/fr/xephi/authme/AuthMe.java | 30 +++++---- .../executable/authme/AuthMeCommand.java | 2 +- .../executable/authme/VersionCommand.java | 6 +- .../authme/command/help/HelpProvider.java | 4 +- .../fr/xephi/authme/settings/Settings.java | 7 +- src/main/resources/config.yml | 2 + src/main/resources/plugin.yml | 2 +- 8 files changed, 99 insertions(+), 19 deletions(-) diff --git a/pom.xml b/pom.xml index 42a9edc9..2e8daf93 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,7 @@ AuthMe fr.xephi.authme.AuthMe Xephi, sgdc3, DNx5, timvisee, games647, ljacqu + Unknown 1.7 @@ -61,6 +62,20 @@ 1.8.8-R0.1-SNAPSHOT + + + jenkins + + + env.BUILD_NUMBER + + + + ${env.BUILD_NUMBER} + + + + AuthMe-${project.version} src/main/java @@ -101,6 +116,39 @@ + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.codehaus.mojo + buildnumber-maven-plugin + [1.0,) + + create-timestamp + + + + + true + true + + + + + + + + + + org.apache.maven.plugins @@ -111,6 +159,23 @@ ${javaVersion} + + org.codehaus.mojo + buildnumber-maven-plugin + 1.4 + + dd-MM-yy_HH:mm + build.time + + + + generate-resources + + create-timestamp + + + + diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index ab54071a..800cbc71 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -60,15 +60,9 @@ public class AuthMe extends JavaPlugin { * Defines the name of the plugin. */ private static final String PLUGIN_NAME = "AuthMeReloaded"; - /** - * Defines the current AuthMeReloaded version name. - */ - private static final String PLUGIN_VERSION_NAME = "5.1-SNAPSHOT"; - /** - * Defines the current AuthMeReloaded version code. - */ - // TODO: increment this value manually - private static final int PLUGIN_VERSION_CODE = 120; + + private static String pluginVersion = "N/D"; + private static String pluginBuildNumber = "Unknown"; private static AuthMe plugin; private static Server server; @@ -128,8 +122,8 @@ public class AuthMe extends JavaPlugin { * * @return The version name of the currently installed AuthMeReloaded instance. */ - public static String getVersionName() { - return PLUGIN_VERSION_NAME; + public static String getPluginVersion() { + return pluginVersion; } /** @@ -137,8 +131,8 @@ public class AuthMe extends JavaPlugin { * * @return The version code of the currently installed AuthMeReloaded instance. */ - public static int getVersionCode() { - return PLUGIN_VERSION_CODE; + public static String getPluginBuildNumber() { + return pluginBuildNumber; } /** @@ -187,6 +181,15 @@ public class AuthMe extends JavaPlugin { this.canConnect = canConnect; } + // Get version and build number of the plugin + // TODO: enhance this + private void setupConstants() { + String versionRaw = this.getDescription().getVersion(); + pluginVersion = versionRaw.substring(0, versionRaw.indexOf("-b")); + String buildRaw = versionRaw.substring(1, versionRaw.indexOf("-b")); + pluginBuildNumber = buildRaw.substring(0, buildRaw.indexOf("-t")); + } + /** * Method called when the server enables the plugin. * @@ -197,6 +200,7 @@ public class AuthMe extends JavaPlugin { // Set various instances server = getServer(); plugin = this; + setupConstants(); // Set up the permissions manager setupPermissionsManager(); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java index 98ea2e9c..f00ea916 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java @@ -13,7 +13,7 @@ public class AuthMeCommand extends ExecutableCommand { @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // Show some version info - sender.sendMessage(ChatColor.GREEN + "This server is running " + AuthMe.getPluginName() + " v" + AuthMe.getVersionName() + "! " + ChatColor.RED + "<3"); + sender.sendMessage(ChatColor.GREEN + "This server is running " + AuthMe.getPluginName() + " v" + AuthMe.getPluginVersion() + " b" + AuthMe.getPluginBuildNumber()+ "! " + ChatColor.RED + "<3"); sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + commandReference.get(0) + " help" + ChatColor.YELLOW + " to view help."); sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + commandReference.get(0) + " about" + ChatColor.YELLOW + " to view about."); return true; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java index a07a0f88..fc92ac8f 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java @@ -3,6 +3,8 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.CommandParts; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.settings.Settings; + import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -24,8 +26,8 @@ public class VersionCommand extends ExecutableCommand { @Override public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { // Show some version info - sender.sendMessage(ChatColor.GOLD + "==========[ " + AuthMe.getPluginName().toUpperCase() + " ABOUT ]=========="); - sender.sendMessage(ChatColor.GOLD + "Version: " + ChatColor.WHITE + AuthMe.getPluginName() + " v" + AuthMe.getVersionName() + ChatColor.GRAY + " (code: " + AuthMe.getVersionCode() + ")"); + sender.sendMessage(ChatColor.GOLD + "==========[ " + Settings.helpHeader.toUpperCase() + " ABOUT ]=========="); + sender.sendMessage(ChatColor.GOLD + "Version: " + ChatColor.WHITE + AuthMe.getPluginName() + " v" + AuthMe.getPluginVersion() + ChatColor.GRAY + " (build: " + AuthMe.getPluginBuildNumber() + ")"); sender.sendMessage(ChatColor.GOLD + "Developers:"); printDeveloper(sender, "Xephi", "xephi59", "Lead Developer"); printDeveloper(sender, "DNx5", "DNx5", "Developer"); diff --git a/src/main/java/fr/xephi/authme/command/help/HelpProvider.java b/src/main/java/fr/xephi/authme/command/help/HelpProvider.java index ce4e8317..45427c50 100644 --- a/src/main/java/fr/xephi/authme/command/help/HelpProvider.java +++ b/src/main/java/fr/xephi/authme/command/help/HelpProvider.java @@ -4,6 +4,8 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.CommandDescription; import fr.xephi.authme.command.CommandParts; import fr.xephi.authme.command.FoundCommandResult; +import fr.xephi.authme.settings.Settings; + import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -99,7 +101,7 @@ public class HelpProvider { } // Print the help header - sender.sendMessage(ChatColor.GOLD + "==========[ " + AuthMe.getPluginName().toUpperCase() + " HELP ]=========="); + sender.sendMessage(ChatColor.GOLD + "==========[ " + Settings.helpHeader.toUpperCase() + " HELP ]=========="); // Print the command help information if (showCommand) diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index 55c24cf9..7a209680 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -69,7 +69,7 @@ public final class Settings extends YamlConfiguration { broadcastWelcomeMessage, forceRegKick, forceRegLogin, checkVeryGames, delayJoinLeaveMessages, noTeleport, applyBlindEffect, customAttributes, generateImage, isRemoveSpeedEnabled, isMySQLWebsite; - public static String getNickRegex, getUnloggedinGroup, getMySQLHost, + public static String helpHeader, getNickRegex, getUnloggedinGroup, getMySQLHost, getMySQLPort, getMySQLUsername, getMySQLPassword, getMySQLDatabase, getMySQLTablename, getMySQLColumnName, getMySQLColumnPassword, getMySQLColumnIp, getMySQLColumnLastLogin, getMySQLColumnSalt, @@ -129,6 +129,7 @@ public final class Settings extends YamlConfiguration { public static void loadVariables() { + helpHeader = configFile.getString("settings.helpHeader", "AuthMeReloaded"); messagesLanguage = checkLang(configFile.getString("settings.messagesLanguage", "en").toLowerCase()); isPermissionCheckEnabled = configFile.getBoolean("permission.EnablePermissionCheck", false); isForcedRegistrationEnabled = configFile.getBoolean("settings.registration.force", true); @@ -551,6 +552,10 @@ public final class Settings extends YamlConfiguration { set("Protection.countriesBlacklist", countriesBlacklist); changes = true; } + if (!contains("settings.helpHeader")) { + set("settings.helpHeader", "AuthMeReloaded"); + changes = true; + } if (!contains("settings.broadcastWelcomeMessage")) { set("settings.broadcastWelcomeMessage", false); changes = true; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index cbf62dcb..5abd17eb 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -45,6 +45,8 @@ DataSource: # Enable this when you allow registration through a website mySQLWebsite: false settings: + # The name shown in the help messages. + helpHeader: AuthMeReloaded sessions: # Do you want to enable the session feature? # If enabled, when a player authenticates successfully, diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 50d9ff38..3cb51c69 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,7 +3,7 @@ authors: [${pluginAuthors}] website: ${project.url} description: ${project.description} main: ${mainClass} -version: ${project.version}-b${env.BUILD_NUMBER} +version: ${project.version}-b${buildNumber}-t${build.time} softdepend: - Vault - PermissionsBukkit From 22c22ba7845e73fad595818c8dbc9acc8835d220 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sun, 29 Nov 2015 14:29:38 +0100 Subject: [PATCH 197/199] fix invalid plugin.yml version timestamp --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2e8daf93..ceeaefca 100644 --- a/pom.xml +++ b/pom.xml @@ -164,7 +164,7 @@ buildnumber-maven-plugin 1.4 - dd-MM-yy_HH:mm + dd-MM-yy_HH-mm build.time From 406c58f5854a4aa18a2b82c8c67ad5b2ce3624be Mon Sep 17 00:00:00 2001 From: DNx Date: Mon, 30 Nov 2015 10:24:33 +0700 Subject: [PATCH 198/199] Remove build time from plugin version. --- src/main/resources/plugin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3cb51c69..d037f06f 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,7 +3,7 @@ authors: [${pluginAuthors}] website: ${project.url} description: ${project.description} main: ${mainClass} -version: ${project.version}-b${buildNumber}-t${build.time} +version: ${project.version}-b${buildNumber} softdepend: - Vault - PermissionsBukkit From ea932712c3a55c7b1953f44e209b099407544e5a Mon Sep 17 00:00:00 2001 From: DNx Date: Mon, 30 Nov 2015 10:57:02 +0700 Subject: [PATCH 199/199] Fix constants setup. --- src/main/java/fr/xephi/authme/AuthMe.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 800cbc71..4f1886fc 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -185,9 +185,14 @@ public class AuthMe extends JavaPlugin { // TODO: enhance this private void setupConstants() { String versionRaw = this.getDescription().getVersion(); - pluginVersion = versionRaw.substring(0, versionRaw.indexOf("-b")); - String buildRaw = versionRaw.substring(1, versionRaw.indexOf("-b")); - pluginBuildNumber = buildRaw.substring(0, buildRaw.indexOf("-t")); + int index = versionRaw.lastIndexOf("-"); + if (index != -1) { + pluginVersion = versionRaw.substring(0, index); + pluginBuildNumber = versionRaw.substring(index + 1); + if (pluginBuildNumber.startsWith("b")) { + pluginBuildNumber = pluginBuildNumber.substring(1); + } + } } /**