Merge branch 'master' of https://github.com/AuthMe-Team/AuthMeReloaded into 437-add-email

This commit is contained in:
ljacqu 2016-01-19 17:16:05 +01:00
commit d4e3e55e07
60 changed files with 4380 additions and 752 deletions

570
NewConfig.yml Normal file
View File

@ -0,0 +1,570 @@
# =======================================================================================================
# _____ __ .__ _____ __________ .__ .___ .___
# / _ \ __ ___/ |_| |__ / \ ____\______ \ ____ | | _________ __| _/____ __| _/
# / /_\ \| | \ __| | \ / \ / \_/ __ \| __/ __ \| | / _ \__ \ / __ _/ __ \ / __ |
# / | | | /| | | Y / Y \ ___/| | \ ___/| |_( <_> / __ \/ /_/ \ ___// /_/ |
# \____|__ |____/ |__| |___| \____|__ /\___ |____|_ /\___ |____/\____(____ \____ |\___ \____ |
# \/ \/ \/ \/ \/ \/ \/ \/ \/ \/
#
# =======================================================================================================
#
# Authme Main Configuration File.
#
# =======================================================================================================
# Plugin infos (overwritten on start, just a simple way to find out your plugin version).
authors: ${pluginAuthors}
version: ${project.version}
buildNumber: ${buildNumber}
# Set this setting to true when you have configured the plugin,
# when false the server will be stopped with a warning message.
enabled: false
# Database settings.
data_source:
# ===========================
# Database general settings.
# ===========================
# Database backend (sqlite, mysql).
backend: sqlite
# Enable database queries caching, should improve performance.
caching: true
# ===========================
# SqLite db parameters.
# ===========================
sqlite:
# The name of the database storage file.
filename: 'authme.db'
# ===========================
# MySql db parameters.
# ===========================
mysql:
# Connection parameters.
host: '127.0.0.1'
port: 3306
username: 'change_me'
password: 'change_me'
database: 'my_minecraft_server'
tablename: 'authme'
# Column names.
column_names:
id: id
# Column for storing nicknames (ignore case nickname).
name: username
# Column for storing the realname (case sensitive nickname).
real_name: realname
# Column for storing passwords.
password: password
# Column for storing email addresses.
email: email
# Column for storing the authentication status (logged or not).
login_status: isLogged
# Column for storing player IPs.
ip: ip
# Column for storing lastlogins date and time.
last_login_timestamp: lastlogin
# Latest logout location of the players.
last_location:
world: world
x: x
y: y
z: z
# Enabled only if the bungeecord integration is activated.
server: world
# Support for registrations via WebInterfaces/CSM.
# Disable some backend caching parameters.
disableAggressiveCaching: false
# Main settings
settings:
# ===========================
# Bungeecord integration
# ===========================
bungeecord:
# Enable bungeecord integration features
enabled: true
# Server name (must be unique, please use the name in the bungeecord configuration).
# Use 'auto' for auto configuration (requires the bungeecord module).
serverName: LoginLobby1
# Keep the auth status when the player moves between servers.
# Required if you're using the bungeecord module.
keepAuthBetweenServers: true
# Target server after login
send_after_login:
enabled: false
message: ''
delay: 5
# Server name ("ServerName") or group ("G:GroupName")
# Groups are avariable only when the bungeecord module is avariable.
# If the server change fails the player will be kicked.
target: Lobby1
failKickMessage: 'Failed to connect to the lobby! Please try to join the server again!'
# Target server after logout
send_after_logout:
enabled: false
message: ''
delay: 5
# Server name ("ServerName") or group ("G:GroupName")
# Groups are avariable only when the bungeecord module is avariable.
# If the server change fails the player will be kicked.
target: LoginLobby1
failKickMessage: 'Failed to connect to the lobby! Please try to join the server again!'
# Variables:
# %p playername
bungee_commands:
player_command_after_register:
enabled: false
cmd: ''
console_command_after_register:
enabled: false
cmd: 'alert %p joined for the first time the network!'
player_command_after_login:
enabled: false
cmd: 'glist'
console_command_after_login:
enabled: false
cmd: 'alert %p logged in correctly!'
player_command_after_join:
enabled: false
cmd: ''
console_command_after_join:
enabled: false
cmd: 'alert %p joined the network!'
player_command_first_join:
enabled: false
cmd: ''
console_command_first_join:
enabled: false
cmd: 'alert %p joined for the first time the network!'
# ===========================
# Sessions configuration.
# ===========================
sessions:
# Enable sessions.
# When a player is authenticated, his IP and his nickname is saved.
# The next time the player will join the server, if his IP is the same
# of the last time, and the timeout time hasn't expired, he will be
# automatically authenticated.
enabled: false
# Session timeout.
# 0 for unlimited time (Very dangerous, use it at your own risk!)
# Consider that if player's ip has changed but the timeout hasn't
# expired, player will be kicked out of the sever!
timeout: 10
# When enabled a player's session will expire if someone tries to
# login with a different IP Address.
expire_on_ip_change: true
# ===========================
# Registration settings.
# ===========================
registration:
# After how many time unregistered players should be kicked?
# Set to 0 to disable. (default: 30)
timeout: 30
nickname:
min_length: 4
max_lenght: 16
# Regex syntax.
allowed_characters: '[a-zA-Z0-9_]*'
password:
# Enable double check of password on registration:
# /register <password> <confirmPassword>
double_check: true
# Minimum password lenght.
min_length: 5
# Regex syntax.
allowed_characters: '[\x21-\x7E]*'
# Denied unsafe passwords.
unsafePasswords:
- '123456'
- 'password'
- 'qwerty'
- '12345'
- '54321'
# ===========================
# Login settings.
# ===========================
login:
# After how many time unlogged players should be kicked?
# Set to 0 to disable. (default: 30)
timeout: 30
# ===========================
# Encryption parameters.
# ===========================
password_encryption:
# The hashing algorithm.
# Possible values: MD5, SHA1, SHA256, WHIRLPOOL, XAUTH, MD5VB, PHPBB, MYBB, IPB3,
# PHPFUSION, SMF, XENFORO, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512, DOUBLEMD5,
# PBKDF2, PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (developpers only).
encryption_algorithm: SHA256
# The salt length for the SALTED2MD5 and MD5(MD5(password)+salt) algorithms.
md5_salt_length: 8
# If password check fails try all the other hash algorithm.
# AuthMe will update the password to the new passwordHash.
enable_convertion: false
# ===========================
# Unlogged user restrictions.
# ===========================
unlogged_restrictions:
# Deny chat messages send for unlogged users.
deny_chat: true
# Hide chat to unlogged users.
# Only player messages, plugins will be able to send messages to the player anyway.
hide_chat: false
# Deny any command message not in the whitelist below.
deny_commands: true
command_whitelist:
- /login
- /register
- /l
- /reg
- /email
- /captcha
movements:
# Restrict player movements.
restrict: true
# Allowed radius.
allowed_radius: 0
# Should unlogged players have speed = 0?
# After the login the walking/flying speeed will be reset to the default value.
removeSpeed: true
# End is there atm xD
# This option will save the quit location of the players.
SaveQuitLocation: false
# Should not logged in players be teleported to the spawn?
# After the authentication, if SaveQuitLocation is enabled,
# they will be teleported back to their normal position.
teleportUnAuthedToSpawn: false
# If enabled, after the login, if the ForceSpawnOnTheseWorlds setting contains
# the player's world, he will be teleported to the world spawnpoint.
# The quit location of the player will be overwritten.
# This is different from "teleportUnAuthedToSpawn" that teleports player
# back to his quit location after the authentication.
ForceSpawnLocOnJoinEnabled: false
# WorldNames where we need to force the spawn location
# Warning: This setting is Case Sensitive!
ForceSpawnOnTheseWorlds:
- world
- world_nether
- world_the_end
# this is very important options,
# every time player join the server,
# if they are registered, AuthMe will switch him
# to unLoggedInGroup, this
# should prevent all major exploit.
# So you can set up on your Permission Plugin
# this special group with 0 permissions, or permissions to chat,
# or permission to
# send private message or all other perms that you want,
# the better way is to set up
# this group with few permissions,
# so if player try to exploit some account,
# they can
# do anything except what you set in perm Group.
# After a correct logged-in player will be
# moved to his correct permissions group!
# Pay attention group name is case sensitive,
# so Admin is different from admin,
# otherwise your group will be wiped,
# and player join in default group []!
# Example unLoggedinGroup: NotLogged
unLoggedinGroup: unLoggedinGroup
# ===========================
# Address restrictions
# ===========================
# Max number of registrations per IP (default: 1)
maxRegPerIp: 1
# Maximum allowed number of Logins per IP, 0 to disable (default: 0)
maxLoginPerIp: 0
# Maximum allowed number of Joins per IP, 0 to disable (default: 0)
maxJoinPerIp: 0
# When this setting is enabled, online players can't be kicked out
# due to "Logged in from another Location"
# This setting will prevent potetial security exploits.
ForceSingleSession: true
# To activate the restricted user feature you need
# to enable this option and configure the
# AllowedRestrctedUser field.
AllowRestrictedUser: false
# The restricted user feature will kick players listed below
# if they dont match of the defined ip address.
# Example:
# AllowedRestrictedUser:
# - playername;127.0.0.1
AllowedRestrictedUser:
- playername;127.0.0.
# Ban ip when the ip is not the ip registered in database
banUnsafedIP: false
# ===============================
# Other restrictions
# ===============================
# Should we protect the player inventory before logging in?
# Warning: Requires the latest version of ProtocolLib!
ProtectInventoryBeforeLogIn: true
# Should unregistered players be kicked immediately?
kickNonRegistered: false
# Should players be kicked on wrong password?
kickOnWrongPassword: false
# Should we display all other accounts of a player when he joins?
# Required permission: authme.admin.accounts
displayOtherAccounts: true
# ===============================
# Restrictions compatibility
# ===============================
# Spawn Priority. Avariable values : authme, essentials, multiverse, default
spawnPriority: authme,essentials,multiverse,default
# AuthMe will NEVER teleport players!
noTeleport: false
GameMode:
# Do you want to set player's gamemode to survival when he joins?
# This enables also the settings below.
ForceSurvivalMode: false
# Do you want to reset player's inventory if player joins with creative mode?
ResetInventoryIfCreative: false
# Do you want to force the survival mode ONLY after the /login process?
ForceOnlyAfterLogin: false
# sgdc3: Ok, our configuration is shit.... xD Today I will stop there
registration:
# enable registration on the server?
enabled: true
# Send every X seconds a message to a player to
# remind him that he has to login/register
messageInterval: 5
# Only registered and logged in players can play.
# See restrictions for exceptions
force: true
# Does we replace password registration by an Email registration method ?
enableEmailRegistrationSystem: false
# Enable double check of email when you register
# when it's true, registration require that kind of command:
# /register <email> <confirmEmail>
doubleEmailCheck: false
# Do we force kicking player after a successful registration ?
# Do not use with login feature below
forceKickAfterRegister: false
# Does AuthMe need to enforce a /login after a successful registration ?
forceLoginAfterRegister: false
unrestrictions:
# below you can list all your account name, that
# AuthMe will ignore for registration or login, configure it
# at your own risk!! Remember that if you are going to add
# nickname with [], you have to delimit name with ' '.
# this option add compatibility with BuildCraft and some
# other mods.
# It is CaseSensitive!
UnrestrictedName: []
# Message language, available : en, de, br, cz, pl, fr, ru, hu, sk, es, zhtw, fi, zhcn, lt, it, ko, pt
messagesLanguage: en
# Force these commands after /login, without any '/', use %p for replace with player name
forceCommands: []
# Force these commands after /login as a server console, without any '/', use %p for replace with player name
forceCommandsAsConsole: []
# Force these commands after /register, without any '/', use %p for replace with player name
forceRegisterCommands: []
# Force these commands after /register as a server console, without any '/', use %p for replace with player name
forceRegisterCommandsAsConsole: []
# Do we need to display the welcome message (welcome.txt) after a register or a login?
# You can use colors in this welcome.txt + some replaced strings :
# {PLAYER} : player name, {ONLINE} : display number of online players, {MAXPLAYERS} : display server slots,
# {IP} : player ip, {LOGINS} : number of players logged, {WORLD} : player current world, {SERVER} : server name
# {VERSION} : get current bukkit version, {COUNTRY} : player country
useWelcomeMessage: true
# Do we need to broadcast the welcome message to all server or only to the player? set true for server or false for player
broadcastWelcomeMessage: false
# Do we need to delay the join/leave message to be displayed only when the player is authenticated ?
delayJoinLeaveMessages: true
# Do we need to add potion effect Blinding before login/register ?
applyBlindEffect: false
ExternalBoardOptions:
# MySQL column for the salt , needed for some forum/cms support
mySQLColumnSalt: ''
# MySQL column for the group, needed for some forum/cms support
mySQLColumnGroup: ''
# -1 mean disabled. If u want that only
# activated player can login in your server
# u can put in this options the group number
# of unactivated user, needed for some forum/cms support
nonActivedUserGroup: -1
# Other MySQL columns where we need to put the Username (case sensitive)
mySQLOtherUsernameColumns: []
# How much Log to Round needed in BCrypt(do not change it if you do not know what's your doing)
bCryptLog2Round: 10
# phpBB prefix defined during phpbb installation process
phpbbTablePrefix: 'phpbb_'
# phpBB activated group id , 2 is default registered group defined by phpbb
phpbbActivatedGroupId: 2
# WordPress prefix defined during WordPress installation process
wordpressTablePrefix: 'wp_'
permission:
# Take care with this options, if you dont want
# to use Vault and Group Switching of
# AuthMe for unloggedIn players put true
# below, default is false.
EnablePermissionCheck: false
BackupSystem:
# Enable or Disable Automatic Backup
ActivateBackup: false
# set Backup at every start of Server
OnServerStart: false
# set Backup at every stop of Server
OnServerStop: true
# Windows only mysql installation Path
MysqlWindowsPath: 'C:\\Program Files\\MySQL\\MySQL Server 5.1\\'
Security:
SQLProblem:
# Stop the server if we can't contact the sql database
# Take care with this, if you set that to false,
# AuthMe automatically disable and the server is not protected!
stopServer: true
ReloadCommand:
# /reload support
useReloadCommandSupport: true
console:
# Remove spam console
noConsoleSpam: false
# Replace passwords in the console when player type a command like /login
removePassword: true
captcha:
# Player need to put a captcha when he fails too lot the password
useCaptcha: false
# Max allowed tries before request a captcha
maxLoginTry: 5
# Captcha length
captchaLength: 5
Converter:
Rakamak:
# Rakamak file name
fileName: users.rak
# Rakamak use ip ?
useIP: false
# IP file name for rakamak
ipFileName: UsersIp.rak
CrazyLogin:
# CrazyLogin database file
fileName: accounts.db
Email:
# Email SMTP server host
mailSMTP: smtp.gmail.com
# Email SMTP server port
mailPort: 465
# Email account that send the mail
mailAccount: ''
# Email account password
mailPassword: ''
# Custom SenderName, that replace the mailAccount name in the email
mailSenderName: ''
# Random password length
RecoveryPasswordLength: 8
# Email subject of password get
mailSubject: 'Your new AuthMe Password'
# Email text here
mailText: 'Dear <playername>, <br /><br /> This is your new AuthMe password for the server <br /><br /> <servername> : <br /><br /> <generatedpass><br /><br />Do not forget to change password after login! <br /> /changepassword <generatedpass> newPassword'
# Like maxRegPerIp but with email
maxRegPerEmail: 1
# Recall players to add an email ?
recallPlayers: false
# Delay in minute for the recall scheduler
delayRecall: 5
# Blacklist these domains for emails
emailBlacklisted:
- 10minutemail.com
# WhiteList only these domains for emails
emailWhitelisted: []
# Do we need to send new password draw in an image ?
generateImage: false
Hooks:
# Do we need to hook with multiverse for spawn checking?
multiverse: true
# Do we need to hook with BungeeCord for get the real Player ip ?
bungeecord: false
# Do we need to disable Essentials SocialSpy on join ?
disableSocialSpy: true
# Do we need to force /motd Essentials command on join ?
useEssentialsMotd: false
# Do we need to cache custom Attributes ?
customAttributes: false
Purge:
# On Enable , does AuthMe need to purge automatically old accounts unused ?
useAutoPurge: false
# Number of Days an account become Unused
daysBeforeRemovePlayer: 60
# Do we need to remove the player.dat file during purge process ?
removePlayerDat: false
# Do we need to remove the Essentials/users/player.yml file during purge process ?
removeEssentialsFile: false
# World where are players.dat stores
defaultWorld: 'world'
# Do we need to remove LimitedCreative/inventories/player.yml , player_creative.yml files during purge process ?
removeLimitedCreativesInventories: false
# Do we need to remove the AntiXRayData/PlayerData/player file during purge process ?
removeAntiXRayFile: false
# Do we need to remove permissions ?
removePermissions: false
Protection:
# Enable some servers protection ( country based login, antibot )
enableProtection: false
# Countries allowed to join the server and register, see http://dev.bukkit.org/bukkit-plugins/authme-reloaded/pages/countries-codes/ for countries' codes
countries:
- US
- GB
# Countries blacklisted automatically ( without any needed to enable protection )
countriesBlacklist:
- A1
# Do we need to enable automatic antibot system?
enableAntiBot: false
# Max number of player allowed to login in 5 secs before enable AntiBot system automatically
antiBotSensibility: 5
# Duration in minutes of the antibot automatic system
antiBotDuration: 10
VeryGames:
# These features are only available on VeryGames Server Provider
enableIpCheck: false

View File

@ -1,34 +1,9 @@
package fr.xephi.authme; package fr.xephi.authme;
import java.io.IOException;
import java.net.URL;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import org.mcstats.Metrics;
import org.mcstats.Metrics.Graph;
import com.earth2me.essentials.Essentials; import com.earth2me.essentials.Essentials;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import com.onarandombox.MultiverseCore.MultiverseCore; import com.onarandombox.MultiverseCore.MultiverseCore;
import fr.xephi.authme.api.API; import fr.xephi.authme.api.API;
import fr.xephi.authme.api.NewAPI; import fr.xephi.authme.api.NewAPI;
import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerAuth;
@ -73,11 +48,38 @@ import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.OtherAccounts; import fr.xephi.authme.settings.OtherAccounts;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.Spawn; import fr.xephi.authme.settings.Spawn;
import fr.xephi.authme.settings.custom.NewSetting;
import fr.xephi.authme.util.GeoLiteAPI; import fr.xephi.authme.util.GeoLiteAPI;
import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.Utils;
import fr.xephi.authme.util.Wrapper; import fr.xephi.authme.util.Wrapper;
import net.minelink.ctplus.CombatTagPlus; import net.minelink.ctplus.CombatTagPlus;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import org.mcstats.Metrics;
import org.mcstats.Metrics.Graph;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* The AuthMe main class. * The AuthMe main class.
@ -99,6 +101,7 @@ public class AuthMe extends JavaPlugin {
private CommandHandler commandHandler = null; private CommandHandler commandHandler = null;
private PermissionsManager permsMan = null; private PermissionsManager permsMan = null;
private Settings settings; private Settings settings;
private NewSetting newSettings;
private Messages messages; private Messages messages;
private JsonCache playerBackup; private JsonCache playerBackup;
private ModuleManager moduleManager; private ModuleManager moduleManager;
@ -215,6 +218,7 @@ public class AuthMe extends JavaPlugin {
setEnabled(false); setEnabled(false);
return; return;
} }
newSettings = createNewSetting();
// Set up messages & password security // Set up messages & password security
messages = Messages.getInstance(); messages = Messages.getInstance();
@ -235,7 +239,7 @@ public class AuthMe extends JavaPlugin {
// Set up the permissions manager and command handler // Set up the permissions manager and command handler
permsMan = initializePermissionsManager(); permsMan = initializePermissionsManager();
commandHandler = initializeCommandHandler(permsMan, messages, passwordSecurity); commandHandler = initializeCommandHandler(permsMan, messages, passwordSecurity, newSettings);
// Set up the module manager // Set up the module manager
setupModuleManager(); setupModuleManager();
@ -417,11 +421,12 @@ public class AuthMe extends JavaPlugin {
} }
private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages, private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages,
PasswordSecurity passwordSecurity) { PasswordSecurity passwordSecurity, NewSetting settings) {
HelpProvider helpProvider = new HelpProvider(permissionsManager); HelpProvider helpProvider = new HelpProvider(permissionsManager);
Set<CommandDescription> baseCommands = CommandInitializer.buildCommands(); Set<CommandDescription> baseCommands = CommandInitializer.buildCommands();
CommandMapper mapper = new CommandMapper(baseCommands, messages, permissionsManager, helpProvider); CommandMapper mapper = new CommandMapper(baseCommands, permissionsManager);
CommandService commandService = new CommandService(this, mapper, helpProvider, messages, passwordSecurity); CommandService commandService = new CommandService(
this, mapper, helpProvider, messages, passwordSecurity, permissionsManager, settings);
return new CommandHandler(commandService); return new CommandHandler(commandService);
} }
@ -443,19 +448,24 @@ public class AuthMe extends JavaPlugin {
* @return True on success, false on failure. * @return True on success, false on failure.
*/ */
private boolean loadSettings() { private boolean loadSettings() {
// TODO: new configuration style (more files)
try { try {
settings = new Settings(this); settings = new Settings(this);
Settings.reload(); Settings.reload();
} catch (Exception e) { } catch (Exception e) {
ConsoleLogger.writeStackTrace(e); ConsoleLogger.writeStackTrace(e);
ConsoleLogger.showError("Can't load the configuration file... Something went wrong, to avoid security issues the server will shutdown!"); ConsoleLogger.showError("Can't load the configuration file... Something went wrong. "
+ "To avoid security issues the server will shut down!");
server.shutdown(); server.shutdown();
return true; return true;
} }
return false; return false;
} }
private NewSetting createNewSetting() {
File configFile = new File(getDataFolder() + "config.yml");
return new NewSetting(getConfig(), configFile);
}
/** /**
* Set up the console filter. * Set up the console filter.
*/ */
@ -527,6 +537,40 @@ public class AuthMe extends JavaPlugin {
moduleManager.unloadModules(); moduleManager.unloadModules();
} }
List<BukkitTask> pendingTasks = getServer().getScheduler().getPendingTasks();
for (Iterator<BukkitTask> iterator = pendingTasks.iterator(); iterator.hasNext();) {
BukkitTask pendingTask = iterator.next();
if (!pendingTask.getOwner().equals(this) || pendingTask.isSync()) {
//remove all unrelevant tasks
iterator.remove();
}
}
getLogger().log(Level.INFO, "Waiting for {0} tasks to finish", pendingTasks.size());
int progress = 0;
try {
for (BukkitTask pendingTask : pendingTasks) {
int maxTries = 5;
int taskId = pendingTask.getTaskId();
while (getServer().getScheduler().isCurrentlyRunning(taskId)) {
if (maxTries <= 0) {
getLogger().log(Level.INFO, "Async task {0} times out after to many tries", taskId);
break;
}
//one second
Thread.sleep(1000);
maxTries--;
}
progress++;
getLogger().log(Level.INFO, "Progress: {0} / {1}", new Object[]{progress, pendingTasks.size()});
}
} catch (InterruptedException interruptedException) {
}
// Close the database // Close the database
if (database != null) { if (database != null) {
database.close(); database.close();

View File

@ -3,6 +3,9 @@ package fr.xephi.authme.command;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.help.HelpProvider;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.StringUtils;
@ -13,6 +16,12 @@ import fr.xephi.authme.util.StringUtils;
*/ */
public class CommandHandler { public class CommandHandler {
/**
* The threshold for suggesting a similar command. If the difference is below this value, we will
* ask the player whether he meant the similar command.
*/
private static final double SUGGEST_COMMAND_THRESHOLD = 0.75;
private final CommandService commandService; private final CommandService commandService;
/** /**
@ -40,14 +49,32 @@ public class CommandHandler {
parts.add(0, bukkitCommandLabel); parts.add(0, bukkitCommandLabel);
FoundCommandResult result = commandService.mapPartsToCommand(sender, parts); FoundCommandResult result = commandService.mapPartsToCommand(sender, parts);
if (FoundResultStatus.SUCCESS.equals(result.getResultStatus())) { handleCommandResult(sender, result);
executeCommand(sender, result);
} else {
commandService.outputMappingError(sender, result);
}
return !FoundResultStatus.MISSING_BASE_COMMAND.equals(result.getResultStatus()); return !FoundResultStatus.MISSING_BASE_COMMAND.equals(result.getResultStatus());
} }
private void handleCommandResult(CommandSender sender, FoundCommandResult result) {
switch (result.getResultStatus()) {
case SUCCESS:
executeCommand(sender, result);
break;
case MISSING_BASE_COMMAND:
sender.sendMessage(ChatColor.DARK_RED + "Failed to parse " + AuthMe.getPluginName() + " command!");
break;
case INCORRECT_ARGUMENTS:
sendImproperArgumentsMessage(sender, result);
break;
case UNKNOWN_LABEL:
sendUnknownCommandMessage(sender, result);
break;
case NO_PERMISSION:
sendPermissionDeniedError(sender);
break;
default:
throw new IllegalStateException("Unknown result status '" + result.getResultStatus() + "'");
}
}
/** /**
* Execute the command for the given command sender. * Execute the command for the given command sender.
* *
@ -76,6 +103,46 @@ public class CommandHandler {
return cleanArguments; return cleanArguments;
} }
/**
* Show an "unknown command" message to the user and suggest an existing command if its similarity is within
* the defined threshold.
*
* @param sender The command sender
* @param result The command that was found during the mapping process
*/
private static void sendUnknownCommandMessage(CommandSender sender, FoundCommandResult result) {
sender.sendMessage(ChatColor.DARK_RED + "Unknown command!");
// Show a command suggestion if available and the difference isn't too big
if (result.getDifference() <= SUGGEST_COMMAND_THRESHOLD && result.getCommandDescription() != null) {
sender.sendMessage(ChatColor.YELLOW + "Did you mean " + ChatColor.GOLD
+ CommandUtils.constructCommandPath(result.getCommandDescription()) + ChatColor.YELLOW + "?");
}
sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + result.getLabels().get(0)
+ " help" + ChatColor.YELLOW + " to view help.");
}
private void sendImproperArgumentsMessage(CommandSender sender, FoundCommandResult result) {
CommandDescription command = result.getCommandDescription();
if (!commandService.getPermissionsManager().hasPermission(sender, command)) {
sendPermissionDeniedError(sender);
return;
}
// Show the command argument help
sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!");
commandService.outputHelp(sender, result, HelpProvider.SHOW_ARGUMENTS);
List<String> labels = result.getLabels();
String childLabel = labels.size() >= 2 ? labels.get(1) : "";
sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE
+ "/" + labels.get(0) + " help " + childLabel);
}
// TODO ljacqu 20151212: Remove me once I am a MessageKey
private static void sendPermissionDeniedError(CommandSender sender) {
sender.sendMessage(ChatColor.DARK_RED + "You don't have permission to use this command!");
}
} }

View File

@ -1,13 +1,11 @@
package fr.xephi.authme.command; package fr.xephi.authme.command;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.executable.HelpCommand; import fr.xephi.authme.command.executable.HelpCommand;
import fr.xephi.authme.command.help.HelpProvider; import fr.xephi.authme.command.help.HelpProvider;
import fr.xephi.authme.output.Messages; import fr.xephi.authme.output.Messages;
import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.util.CollectionUtils; import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.StringUtils;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.ArrayList; import java.util.ArrayList;
@ -19,101 +17,24 @@ import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND;
import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL; import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL;
/** /**
* The AuthMe command handler, responsible for mapping incoming command parts to the correct {@link CommandDescription} * The AuthMe command handler, responsible for mapping incoming
* or to display help messages for erroneous invocations (unknown command, no permission, etc.). * command parts to the correct {@link CommandDescription}.
*/ */
public class CommandMapper { public class CommandMapper {
/**
* The threshold for suggesting a similar command. If the difference is below this value, we will
* ask the player whether he meant the similar command.
*/
private static final double SUGGEST_COMMAND_THRESHOLD = 0.75;
/** /**
* The class of the help command, to which the base label should also be passed in the arguments. * The class of the help command, to which the base label should also be passed in the arguments.
*/ */
private static final Class<? extends ExecutableCommand> HELP_COMMAND_CLASS = HelpCommand.class; private static final Class<? extends ExecutableCommand> HELP_COMMAND_CLASS = HelpCommand.class;
private final Set<CommandDescription> baseCommands; private final Set<CommandDescription> baseCommands;
private final Messages messages;
private final PermissionsManager permissionsManager; private final PermissionsManager permissionsManager;
private final HelpProvider helpProvider;
public CommandMapper(Set<CommandDescription> baseCommands, Messages messages, public CommandMapper(Set<CommandDescription> baseCommands, PermissionsManager permissionsManager) {
PermissionsManager permissionsManager, HelpProvider helpProvider) {
this.baseCommands = baseCommands; this.baseCommands = baseCommands;
this.messages = messages;
this.permissionsManager = permissionsManager; this.permissionsManager = permissionsManager;
this.helpProvider = helpProvider;
} }
public void outputStandardError(CommandSender sender, FoundCommandResult result) {
switch (result.getResultStatus()) {
case SUCCESS:
// Successful mapping, so no error to output
break;
case MISSING_BASE_COMMAND:
sender.sendMessage(ChatColor.DARK_RED + "Failed to parse " + AuthMe.getPluginName() + " command!");
break;
case INCORRECT_ARGUMENTS:
sendImproperArgumentsMessage(sender, result);
break;
case UNKNOWN_LABEL:
sendUnknownCommandMessage(sender, result);
break;
case NO_PERMISSION:
sendPermissionDeniedError(sender);
break;
default:
throw new IllegalStateException("Unknown result status '" + result.getResultStatus() + "'");
}
}
/**
* Show an "unknown command" message to the user and suggest an existing command if its similarity is within
* the defined threshold.
*
* @param sender The command sender
* @param result The command that was found during the mapping process
*/
private static void sendUnknownCommandMessage(CommandSender sender, FoundCommandResult result) {
sender.sendMessage(ChatColor.DARK_RED + "Unknown command!");
// Show a command suggestion if available and the difference isn't too big
if (result.getDifference() <= SUGGEST_COMMAND_THRESHOLD && result.getCommandDescription() != null) {
sender.sendMessage(ChatColor.YELLOW + "Did you mean " + ChatColor.GOLD
+ CommandUtils.constructCommandPath(result.getCommandDescription()) + ChatColor.YELLOW + "?");
}
sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + result.getLabels().get(0)
+ " help" + ChatColor.YELLOW + " to view help.");
}
private void sendImproperArgumentsMessage(CommandSender sender, FoundCommandResult result) {
CommandDescription command = result.getCommandDescription();
if (!permissionsManager.hasPermission(sender, command)) {
sendPermissionDeniedError(sender);
return;
}
// Show the command argument help
sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!");
List<String> lines = helpProvider.printHelp(sender, result, HelpProvider.SHOW_ARGUMENTS);
for (String line : lines) {
sender.sendMessage(line);
}
List<String> labels = result.getLabels();
String childLabel = labels.size() >= 2 ? labels.get(1) : "";
sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE
+ "/" + labels.get(0) + " help " + childLabel);
}
// TODO ljacqu 20151212: Remove me once I am a MessageKey
private static void sendPermissionDeniedError(CommandSender sender) {
sender.sendMessage(ChatColor.DARK_RED + "You don't have permission to use this command!");
}
/** /**
* Map incoming command parts to a command. This processes all parts and distinguishes the labels from arguments. * Map incoming command parts to a command. This processes all parts and distinguishes the labels from arguments.

View File

@ -1,9 +1,5 @@
package fr.xephi.authme.command; package fr.xephi.authme.command;
import java.util.List;
import org.bukkit.command.CommandSender;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.help.HelpProvider; import fr.xephi.authme.command.help.HelpProvider;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
@ -12,6 +8,11 @@ import fr.xephi.authme.output.Messages;
import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.Management; import fr.xephi.authme.process.Management;
import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.settings.custom.NewSetting;
import fr.xephi.authme.settings.domain.Property;
import org.bukkit.command.CommandSender;
import java.util.List;
/** /**
* Service for implementations of {@link ExecutableCommand} to execute some common tasks. * Service for implementations of {@link ExecutableCommand} to execute some common tasks.
@ -24,6 +25,8 @@ public class CommandService {
private final HelpProvider helpProvider; private final HelpProvider helpProvider;
private final CommandMapper commandMapper; private final CommandMapper commandMapper;
private final PasswordSecurity passwordSecurity; private final PasswordSecurity passwordSecurity;
private final PermissionsManager permissionsManager;
private final NewSetting settings;
/** /**
* Constructor. * Constructor.
@ -33,14 +36,19 @@ public class CommandService {
* @param helpProvider Help provider * @param helpProvider Help provider
* @param messages Messages instance * @param messages Messages instance
* @param passwordSecurity The Password Security instance * @param passwordSecurity The Password Security instance
* @param permissionsManager The permissions manager
* @param settings The settings manager
*/ */
public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages, public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages,
PasswordSecurity passwordSecurity) { PasswordSecurity passwordSecurity, PermissionsManager permissionsManager,
NewSetting settings) {
this.authMe = authMe; this.authMe = authMe;
this.messages = messages; this.messages = messages;
this.helpProvider = helpProvider; this.helpProvider = helpProvider;
this.commandMapper = commandMapper; this.commandMapper = commandMapper;
this.passwordSecurity = passwordSecurity; this.passwordSecurity = passwordSecurity;
this.permissionsManager = permissionsManager;
this.settings = settings;
} }
/** /**
@ -75,17 +83,6 @@ public class CommandService {
return commandMapper.mapPartsToCommand(sender, commandParts); return commandMapper.mapPartsToCommand(sender, commandParts);
} }
/**
* Output the standard error message for the status in the provided {@link FoundCommandResult} object.
* Does not output anything for successful mappings.
*
* @param sender The sender to output the error to
* @param result The mapping result to process
*/
public void outputMappingError(CommandSender sender, FoundCommandResult result) {
commandMapper.outputStandardError(sender, result);
}
/** /**
* Run the given task asynchronously with the Bukkit scheduler. * Run the given task asynchronously with the Bukkit scheduler.
* *
@ -142,8 +139,7 @@ public class CommandService {
* @return the permissions manager * @return the permissions manager
*/ */
public PermissionsManager getPermissionsManager() { public PermissionsManager getPermissionsManager() {
// TODO ljacqu 20151226: Might be nicer to pass the perm manager via constructor return permissionsManager;
return authMe.getPermissionsManager();
} }
/** /**
@ -156,4 +152,15 @@ public class CommandService {
return messages.retrieve(key); return messages.retrieve(key);
} }
/**
* Retrieve the given property's value.
*
* @param property The property to retrieve
* @param <T> The type of the property
* @return The property's value
*/
public <T> T getProperty(Property<T> property) {
return settings.getProperty(property);
}
} }

View File

@ -8,7 +8,8 @@ import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.custom.RestrictionSettings;
import fr.xephi.authme.settings.custom.SecuritySettings;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.List; import java.util.List;
@ -27,7 +28,7 @@ public class ChangePasswordAdminCommand implements ExecutableCommand {
// Validate the password // Validate the password
String playerPassLowerCase = playerPass.toLowerCase(); String playerPassLowerCase = playerPass.toLowerCase();
if (!playerPassLowerCase.matches(Settings.getPassRegex)) { if (!playerPassLowerCase.matches(commandService.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX))) {
commandService.send(sender, MessageKey.PASSWORD_MATCH_ERROR); commandService.send(sender, MessageKey.PASSWORD_MATCH_ERROR);
return; return;
} }
@ -35,12 +36,12 @@ public class ChangePasswordAdminCommand implements ExecutableCommand {
commandService.send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR); commandService.send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR);
return; return;
} }
if (playerPassLowerCase.length() < Settings.getPasswordMinLen if (playerPassLowerCase.length() < commandService.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)
|| playerPassLowerCase.length() > Settings.passwordMaxLength) { || playerPassLowerCase.length() > commandService.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)) {
commandService.send(sender, MessageKey.INVALID_PASSWORD_LENGTH); commandService.send(sender, MessageKey.INVALID_PASSWORD_LENGTH);
return; return;
} }
if (!Settings.unsafePasswords.isEmpty() && Settings.unsafePasswords.contains(playerPassLowerCase)) { if (commandService.getProperty(SecuritySettings.UNSAFE_PASSWORDS).contains(playerPassLowerCase)) {
commandService.send(sender, MessageKey.PASSWORD_UNSAFE_ERROR); commandService.send(sender, MessageKey.PASSWORD_UNSAFE_ERROR);
return; return;
} }

View File

@ -7,6 +7,7 @@ import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.custom.SecuritySettings;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -35,8 +36,8 @@ public class RegisterAdminCommand implements ExecutableCommand {
commandService.send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR); commandService.send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR);
return; return;
} }
if (playerPassLowerCase.length() < Settings.getPasswordMinLen if (playerPassLowerCase.length() < commandService.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)
|| playerPassLowerCase.length() > Settings.passwordMaxLength) { || playerPassLowerCase.length() > commandService.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)) {
commandService.send(sender, MessageKey.INVALID_PASSWORD_LENGTH); commandService.send(sender, MessageKey.INVALID_PASSWORD_LENGTH);
return; return;
} }

View File

@ -6,7 +6,7 @@ import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.security.RandomString; import fr.xephi.authme.security.RandomString;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.custom.SecuritySettings;
import fr.xephi.authme.util.Wrapper; import fr.xephi.authme.util.Wrapper;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -28,20 +28,20 @@ public class CaptchaCommand extends PlayerCommand {
return; return;
} }
if (!Settings.useCaptcha) { if (!commandService.getProperty(SecuritySettings.USE_CAPTCHA)) {
commandService.send(player, MessageKey.USAGE_LOGIN); commandService.send(player, MessageKey.USAGE_LOGIN);
return; return;
} }
if (!plugin.cap.containsKey(playerNameLowerCase)) { if (!plugin.cap.containsKey(playerNameLowerCase)) {
commandService.send(player, MessageKey.USAGE_LOGIN); commandService.send(player, MessageKey.USAGE_LOGIN);
return; return;
} }
if (Settings.useCaptcha && !captcha.equals(plugin.cap.get(playerNameLowerCase))) { if (!captcha.equals(plugin.cap.get(playerNameLowerCase))) {
plugin.cap.remove(playerNameLowerCase); plugin.cap.remove(playerNameLowerCase);
String randStr = RandomString.generate(Settings.captchaLength); int captchaLength = commandService.getProperty(SecuritySettings.CAPTCHA_LENGTH);
String randStr = RandomString.generate(captchaLength);
plugin.cap.put(playerNameLowerCase, randStr); plugin.cap.put(playerNameLowerCase, randStr);
commandService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, plugin.cap.get(playerNameLowerCase)); commandService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, plugin.cap.get(playerNameLowerCase));
return; return;

View File

@ -5,7 +5,8 @@ import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.custom.RestrictionSettings;
import fr.xephi.authme.settings.custom.SecuritySettings;
import fr.xephi.authme.task.ChangePasswordTask; import fr.xephi.authme.task.ChangePasswordTask;
import fr.xephi.authme.util.Wrapper; import fr.xephi.authme.util.Wrapper;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -32,7 +33,7 @@ public class ChangePasswordCommand extends PlayerCommand {
// Make sure the password is allowed // Make sure the password is allowed
String playerPassLowerCase = newPassword.toLowerCase(); String playerPassLowerCase = newPassword.toLowerCase();
if (!playerPassLowerCase.matches(Settings.getPassRegex)) { if (!playerPassLowerCase.matches(commandService.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX))) {
commandService.send(player, MessageKey.PASSWORD_MATCH_ERROR); commandService.send(player, MessageKey.PASSWORD_MATCH_ERROR);
return; return;
} }
@ -40,17 +41,18 @@ public class ChangePasswordCommand extends PlayerCommand {
commandService.send(player, MessageKey.PASSWORD_IS_USERNAME_ERROR); commandService.send(player, MessageKey.PASSWORD_IS_USERNAME_ERROR);
return; return;
} }
if (playerPassLowerCase.length() < Settings.getPasswordMinLen if (playerPassLowerCase.length() < commandService.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)
|| playerPassLowerCase.length() > Settings.passwordMaxLength) { || playerPassLowerCase.length() > commandService.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)) {
commandService.send(player, MessageKey.INVALID_PASSWORD_LENGTH); commandService.send(player, MessageKey.INVALID_PASSWORD_LENGTH);
return; return;
} }
if (!Settings.unsafePasswords.isEmpty() && Settings.unsafePasswords.contains(playerPassLowerCase)) { if (commandService.getProperty(SecuritySettings.UNSAFE_PASSWORDS).contains(playerPassLowerCase)) {
commandService.send(player, MessageKey.PASSWORD_UNSAFE_ERROR); commandService.send(player, MessageKey.PASSWORD_UNSAFE_ERROR);
return; return;
} }
AuthMe plugin = AuthMe.getInstance(); AuthMe plugin = AuthMe.getInstance();
// TODO ljacqu 20160117: Call async task via Management
commandService.runTaskAsynchronously(new ChangePasswordTask(plugin, player, oldPassword, newPassword)); commandService.runTaskAsynchronously(new ChangePasswordTask(plugin, player, oldPassword, newPassword));
} }
} }

View File

@ -43,6 +43,6 @@ public class RegisterCommand extends PlayerCommand {
@Override @Override
public String getAlternativeCommand() { public String getAlternativeCommand() {
return "authme register <playername> <password>"; return "/authme register <playername> <password>";
} }
} }

View File

@ -110,6 +110,12 @@ public class MySQL implements DataSource {
ds.addDataSourceProperty("cachePrepStmts", "true"); ds.addDataSourceProperty("cachePrepStmts", "true");
ds.addDataSourceProperty("prepStmtCacheSize", "250"); ds.addDataSourceProperty("prepStmtCacheSize", "250");
ds.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); ds.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
//set utf-8 as default encoding
ds.addDataSourceProperty("characterEncoding", "utf8");
ds.addDataSourceProperty("encoding","UTF-8");
ds.addDataSourceProperty("useUnicode", "true");
ds.setUsername(this.username); ds.setUsername(this.username);
ds.setPassword(this.password); ds.setPassword(this.password);
ds.setInitializationFailFast(true); // Don't start the plugin if the database is unavailable ds.setInitializationFailFast(true); // Don't start the plugin if the database is unavailable

View File

@ -41,15 +41,8 @@ public class PasswordSecurity {
public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) { public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) {
EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName);
// User is not in data source, so the result will invariably be wrong because an encryption
// method with hasSeparateSalt() == true NEEDS the salt to evaluate the password
String salt = hashedPassword.getSalt();
if (method.hasSeparateSalt() && salt == null) {
return false;
}
String playerLowerCase = playerName.toLowerCase(); String playerLowerCase = playerName.toLowerCase();
return method.comparePassword(password, hashedPassword, playerLowerCase) return methodMatches(method, password, hashedPassword, playerLowerCase)
|| supportOldAlgorithm && compareWithAllEncryptionMethods(password, hashedPassword, playerLowerCase); || supportOldAlgorithm && compareWithAllEncryptionMethods(password, hashedPassword, playerLowerCase);
} }
@ -62,14 +55,14 @@ public class PasswordSecurity {
* @param hashedPassword The encrypted password to test the clear-text password against * @param hashedPassword The encrypted password to test the clear-text password against
* @param playerName The name of the player * @param playerName The name of the player
* *
* @return True if the * @return True if there was a password match with another encryption method, false otherwise
*/ */
private boolean compareWithAllEncryptionMethods(String password, HashedPassword hashedPassword, private boolean compareWithAllEncryptionMethods(String password, HashedPassword hashedPassword,
String playerName) { String playerName) {
for (HashAlgorithm algorithm : HashAlgorithm.values()) { for (HashAlgorithm algorithm : HashAlgorithm.values()) {
if (!HashAlgorithm.CUSTOM.equals(algorithm)) { if (!HashAlgorithm.CUSTOM.equals(algorithm)) {
EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm); EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm);
if (method != null && method.comparePassword(password, hashedPassword, playerName)) { if (methodMatches(method, password, hashedPassword, playerName)) {
hashPasswordForNewAlgorithm(password, playerName); hashPasswordForNewAlgorithm(password, playerName);
return true; return true;
} }
@ -78,6 +71,22 @@ public class PasswordSecurity {
return false; return false;
} }
/**
* Verify with the given encryption method whether the password matches the hash after checking that
* the method can be called safely with the given data.
*
* @param method The encryption method to use
* @param password The password to check
* @param hashedPassword The hash to check against
* @param playerName The name of the player
* @return True if the password matched, false otherwise
*/
private static boolean methodMatches(EncryptionMethod method, String password,
HashedPassword hashedPassword, String playerName) {
return method != null && (!method.hasSeparateSalt() || hashedPassword.getSalt() != null)
&& method.comparePassword(password, hashedPassword, playerName);
}
/** /**
* Get the encryption method from the given {@link HashAlgorithm} value and emit a * Get the encryption method from the given {@link HashAlgorithm} value and emit a
* {@link PasswordEncryptionEvent}. The encryption method from the event is then returned, * {@link PasswordEncryptionEvent}. The encryption method from the event is then returned,

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,7 @@ public class BCRYPT2Y extends HexSaltedMethod {
if (salt.length() == 22) { if (salt.length() == 22) {
salt = "$2y$10$" + salt; salt = "$2y$10$" + salt;
} }
return BCRYPT.hashpw(password, salt); return BCryptService.hashpw(password, salt);
} }
@Override @Override

View File

@ -0,0 +1,780 @@
package fr.xephi.authme.security.crypts;
// Copyright (c) 2006 Damien Miller <djm@mindrot.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import java.io.UnsupportedEncodingException;
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.
* <p>
* 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.
* <p>
* Usage is really simple. To hash a password for the first time,
* call the hashpw method with a random salt, like this:
* <p>
* <code>
* String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); <br>
* </code>
* <p>
* To check whether a plaintext password matches one that has been
* hashed previously, use the checkpw method:
* <p>
* <code>
* if (BCrypt.checkpw(candidate_password, stored_hash))<br>
* &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It matches");<br>
* else<br>
* &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It does not match");<br>
* </code>
* <p>
* The gensalt() method takes an optional parameter (log_rounds)
* that determines the computational complexity of the hashing:
* <p>
* <code>
* String strong_salt = BCrypt.gensalt(10)<br>
* String stronger_salt = BCrypt.gensalt(12)<br>
* </code>
* <p>
* 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 30.
*
* @author Damien Miller
* @version 0.4
*/
public class BCryptService {
// BCrypt parameters
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
private static final int BCRYPT_SALT_LEN = 16;
// Blowfish parameters
private static final int BLOWFISH_NUM_ROUNDS = 16;
// Initial contents of key schedule
private static final int P_orig[] = {
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
0x9216d5d9, 0x8979fb1b
};
private static final int S_orig[] = {
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
};
// bcrypt IV: "OrpheanBeholderScryDoubt". The C implementation calls
// this "ciphertext", but it is really plaintext or an IV. We keep
// the name to make code comparison easier.
static private final int bf_crypt_ciphertext[] = {
0x4f727068, 0x65616e42, 0x65686f6c,
0x64657253, 0x63727944, 0x6f756274
};
// Table for Base64 encoding
static private final char base64_code[] = {
'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9'
};
// Table for Base64 decoding
static private final byte index_64[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
-1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
-1, -1, -1, -1, -1, -1, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, -1, -1, -1, -1, -1
};
// Expanded Blowfish key
private int P[];
private int S[];
/**
* Encode a byte array using bcrypt's slightly-modified base64
* encoding scheme. Note that this is *not* compatible with
* the standard MIME-base64 encoding.
*
* @param d the byte array to encode
* @param len the number of bytes to encode
* @return base64-encoded string
* @exception IllegalArgumentException if the length is invalid
*/
private static String encode_base64(byte d[], int len)
throws IllegalArgumentException {
int off = 0;
StringBuffer rs = new StringBuffer();
int c1, c2;
if (len <= 0 || len > d.length)
throw new IllegalArgumentException ("Invalid len");
while (off < len) {
c1 = d[off++] & 0xff;
rs.append(base64_code[(c1 >> 2) & 0x3f]);
c1 = (c1 & 0x03) << 4;
if (off >= len) {
rs.append(base64_code[c1 & 0x3f]);
break;
}
c2 = d[off++] & 0xff;
c1 |= (c2 >> 4) & 0x0f;
rs.append(base64_code[c1 & 0x3f]);
c1 = (c2 & 0x0f) << 2;
if (off >= len) {
rs.append(base64_code[c1 & 0x3f]);
break;
}
c2 = d[off++] & 0xff;
c1 |= (c2 >> 6) & 0x03;
rs.append(base64_code[c1 & 0x3f]);
rs.append(base64_code[c2 & 0x3f]);
}
return rs.toString();
}
/**
* Look up the 3 bits base64-encoded by the specified character,
* range-checking againt conversion table
* @param x the base64-encoded value
* @return the decoded value of x
*/
private static byte char64(char x) {
if ((int)x < 0 || (int)x > index_64.length)
return -1;
return index_64[(int)x];
}
/**
* Decode a string encoded using bcrypt's base64 scheme to a
* byte array. Note that this is *not* compatible with
* the standard MIME-base64 encoding.
* @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
*/
private static byte[] decode_base64(String s, int maxolen)
throws IllegalArgumentException {
StringBuffer rs = new StringBuffer();
int off = 0, slen = s.length(), olen = 0;
byte ret[];
byte c1, c2, c3, c4, o;
if (maxolen <= 0)
throw new IllegalArgumentException ("Invalid maxolen");
while (off < slen - 1 && olen < maxolen) {
c1 = char64(s.charAt(off++));
c2 = char64(s.charAt(off++));
if (c1 == -1 || c2 == -1)
break;
o = (byte)(c1 << 2);
o |= (c2 & 0x30) >> 4;
rs.append((char)o);
if (++olen >= maxolen || off >= slen)
break;
c3 = char64(s.charAt(off++));
if (c3 == -1)
break;
o = (byte)((c2 & 0x0f) << 4);
o |= (c3 & 0x3c) >> 2;
rs.append((char)o);
if (++olen >= maxolen || off >= slen)
break;
c4 = char64(s.charAt(off++));
o = (byte)((c3 & 0x03) << 6);
o |= c4;
rs.append((char)o);
++olen;
}
ret = new byte[olen];
for (off = 0; off < olen; off++)
ret[off] = (byte)rs.charAt(off);
return ret;
}
/**
* Blowfish encipher a single 64-bit block encoded as
* two 32-bit halves
* @param lr an array containing the two 32-bit half blocks
* @param off the position in the array of the blocks
*/
private final void encipher(int lr[], int off) {
int i, n, l = lr[off], r = lr[off + 1];
l ^= P[0];
for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) {
// Feistel substitution on left word
n = S[(l >> 24) & 0xff];
n += S[0x100 | ((l >> 16) & 0xff)];
n ^= S[0x200 | ((l >> 8) & 0xff)];
n += S[0x300 | (l & 0xff)];
r ^= n ^ P[++i];
// Feistel substitution on right word
n = S[(r >> 24) & 0xff];
n += S[0x100 | ((r >> 16) & 0xff)];
n ^= S[0x200 | ((r >> 8) & 0xff)];
n += S[0x300 | (r & 0xff)];
l ^= n ^ P[++i];
}
lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1];
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
*/
private void init_key() {
P = (int[])P_orig.clone();
S = (int[])S_orig.clone();
}
/**
* Key the Blowfish cipher
* @param key an array containing the key
*/
private void key(byte key[]) {
int i;
int koffp[] = { 0 };
int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++)
P[i] = P[i] ^ streamtoword(key, koffp);
for (i = 0; i < plen; i += 2) {
encipher(lr, 0);
P[i] = lr[0];
P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2) {
encipher(lr, 0);
S[i] = lr[0];
S[i + 1] = lr[1];
}
}
/**
* Perform the "enhanced key schedule" step described by
* Provos and Mazieres in "A Future-Adaptable Password Scheme"
* http://www.openbsd.org/papers/bcrypt-paper.ps
* @param data salt information
* @param key password information
*/
private void ekskey(byte data[], byte key[]) {
int i;
int koffp[] = { 0 }, doffp[] = { 0 };
int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++)
P[i] = P[i] ^ streamtoword(key, koffp);
for (i = 0; i < plen; i += 2) {
lr[0] ^= streamtoword(data, doffp);
lr[1] ^= streamtoword(data, doffp);
encipher(lr, 0);
P[i] = lr[0];
P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2) {
lr[0] ^= streamtoword(data, doffp);
lr[1] ^= streamtoword(data, doffp);
encipher(lr, 0);
S[i] = lr[0];
S[i + 1] = lr[1];
}
}
/**
* Perform the central password hashing step in the
* bcrypt scheme
* @param password the password to hash
* @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
* @param cdata the plaintext to encrypt
* @return an array containing the binary hashed password
*/
public byte[] crypt_raw(byte password[], byte salt[], int log_rounds,
int cdata[]) {
int rounds, i, j;
int clen = cdata.length;
byte ret[];
if (log_rounds < 4 || log_rounds > 30)
throw new IllegalArgumentException ("Bad number of rounds");
rounds = 1 << log_rounds;
if (salt.length != BCRYPT_SALT_LEN)
throw new IllegalArgumentException ("Bad salt length");
init_key();
ekskey(salt, password);
for (i = 0; i != rounds; i++) {
key(password);
key(salt);
}
for (i = 0; i < 64; i++) {
for (j = 0; j < (clen >> 1); j++)
encipher(cdata, j << 1);
}
ret = new byte[clen * 4];
for (i = 0, j = 0; i < clen; i++) {
ret[j++] = (byte)((cdata[i] >> 24) & 0xff);
ret[j++] = (byte)((cdata[i] >> 16) & 0xff);
ret[j++] = (byte)((cdata[i] >> 8) & 0xff);
ret[j++] = (byte)(cdata[i] & 0xff);
}
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) {
BCryptService 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);
// Note ljacqu 20160118: Added check to also allow minor version 'y'
// cf. https://security.stackexchange.com/questions/20541/insecure-versions-of-crypt-hashes
if ((minor != 'a' && minor != 'y') || 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 BCryptService();
hashed = B.crypt_raw(passwordb, saltb, rounds,
(int[])bf_crypt_ciphertext.clone());
rs.append("$2");
if (minor >= 'a')
rs.append(minor);
rs.append("$");
if (rounds < 10)
rs.append("0");
if (rounds > 30) {
throw new IllegalArgumentException(
"rounds exceeds maximum (30)");
}
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");
if (log_rounds > 30) {
throw new IllegalArgumentException(
"log_rounds exceeds maximum (30)");
}
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) {
byte hashed_bytes[];
byte try_bytes[];
try {
String try_pw = hashpw(plaintext, hashed);
hashed_bytes = hashed.getBytes("UTF-8");
try_bytes = try_pw.getBytes("UTF-8");
} catch (UnsupportedEncodingException uee) {
return false;
}
if (hashed_bytes.length != try_bytes.length)
return false;
byte ret = 0;
for (int i = 0; i < try_bytes.length; i++)
ret |= hashed_bytes[i] ^ try_bytes[i];
return ret == 0;
}
}

View File

@ -3,22 +3,28 @@ package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.security.crypts.description.Usage;
@Recommendation(Usage.DOES_NOT_WORK) import static fr.xephi.authme.security.crypts.BCryptService.hashpw;
@Recommendation(Usage.RECOMMENDED)
public class WBB4 extends HexSaltedMethod { public class WBB4 extends HexSaltedMethod {
@Override @Override
public String computeHash(String password, String salt, String name) { public String computeHash(String password, String salt, String name) {
return BCRYPT.getDoubleHash(password, salt); return hashpw(hashpw(password, salt), salt);
} }
@Override @Override
public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) { public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) {
return BCRYPT.checkpw(password, hashedPassword.getHash(), 2); if (hashedPassword.getHash().length() != 60) {
return false;
}
String salt = hashedPassword.getHash().substring(0, 29);
return computeHash(password, salt, null).equals(hashedPassword.getHash());
} }
@Override @Override
public String generateSalt() { public String generateSalt() {
return BCRYPT.gensalt(8); return BCryptService.gensalt(8);
} }
/** /**

View File

@ -1,5 +1,7 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.MessageDigestAlgorithm;
import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.SaltType;
@ -7,12 +9,10 @@ import fr.xephi.authme.security.crypts.description.Usage;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Arrays; import java.util.Arrays;
// TODO #391: Wordpress algorithm fails sometimes. Fix it and change the Recommendation to "ACCEPTABLE" if appropriate @Recommendation(Usage.ACCEPTABLE)
@Recommendation(Usage.DO_NOT_USE)
@HasSalt(value = SaltType.TEXT, length = 9) @HasSalt(value = SaltType.TEXT, length = 9)
// Note ljacqu 20151228: Wordpress is actually a salted algorithm but salt generation is handled internally // Note ljacqu 20151228: Wordpress is actually a salted algorithm but salt generation is handled internally
// and isn't exposed to the outside, so we treat it as an unsalted implementation // and isn't exposed to the outside, so we treat it as an unsalted implementation
@ -30,6 +30,7 @@ public class WORDPRESS extends UnsaltedMethod {
byte[] t = new byte[count]; byte[] t = new byte[count];
System.arraycopy(src, 0, t, 0, src.length); System.arraycopy(src, 0, t, 0, src.length);
Arrays.fill(t, src.length, count - 1, (byte) 0); Arrays.fill(t, src.length, count - 1, (byte) 0);
src = t;
} }
do { do {
@ -73,13 +74,7 @@ public class WORDPRESS extends UnsaltedMethod {
if (salt.length() != 8) { if (salt.length() != 8) {
return output; return output;
} }
MessageDigest md; MessageDigest md = HashUtils.getDigest(MessageDigestAlgorithm.MD5);
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return output;
}
byte[] pass = stringToUtf8(password); byte[] pass = stringToUtf8(password);
byte[] hash = md.digest(stringToUtf8(salt + password)); byte[] hash = md.digest(stringToUtf8(salt + password));
do { do {

View File

@ -1,15 +1,44 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.util.StringUtils;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class XFBCRYPT extends BCRYPT { public class XFBCRYPT implements EncryptionMethod {
public static final String SCHEME_CLASS = "XenForo_Authentication_Core12"; public static final String SCHEME_CLASS = "XenForo_Authentication_Core12";
private static final Pattern HASH_PATTERN = Pattern.compile("\"hash\";s.*\"(.*)?\""); private static final Pattern HASH_PATTERN = Pattern.compile("\"hash\";s.*\"(.*)?\"");
@Override @Override
public String generateSalt() { public String generateSalt() {
return BCRYPT.gensalt(); return BCryptService.gensalt();
}
@Override
public String computeHash(String password, String salt, String name) {
return BCryptService.hashpw(password, salt);
}
@Override
public HashedPassword computeHash(String password, String name) {
String salt = generateSalt();
return new HashedPassword(BCryptService.hashpw(password, salt), null);
}
@Override
public boolean comparePassword(String password, HashedPassword hash, String salt) {
try {
return hash.getHash().length() > 3 && BCryptService.checkpw(password, hash.getHash());
} catch (IllegalArgumentException e) {
ConsoleLogger.showError("XfBCrypt checkpw() returned " + StringUtils.formatException(e));
}
return false;
}
@Override
public boolean hasSeparateSalt() {
return false;
} }
public static String getHashFromBlob(byte[] blob) { public static String getHashFromBlob(byte[] blob) {

View File

@ -7,6 +7,7 @@ import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.DataSource.DataSourceType; import fr.xephi.authme.datasource.DataSource.DataSourceType;
import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Wrapper; import fr.xephi.authme.util.Wrapper;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
@ -283,7 +284,7 @@ public final class Settings {
getMaxLoginPerIp = configFile.getInt("settings.restrictions.maxLoginPerIp", 0); getMaxLoginPerIp = configFile.getInt("settings.restrictions.maxLoginPerIp", 0);
getMaxJoinPerIp = configFile.getInt("settings.restrictions.maxJoinPerIp", 0); getMaxJoinPerIp = configFile.getInt("settings.restrictions.maxJoinPerIp", 0);
checkVeryGames = configFile.getBoolean("VeryGames.enableIpCheck", false); checkVeryGames = configFile.getBoolean("VeryGames.enableIpCheck", false);
delayJoinLeaveMessages = configFile.getBoolean("settings.delayJoinLeaveMessage", false); delayJoinLeaveMessages = configFile.getBoolean("settings.delayJoinLeaveMessages", false);
noTeleport = configFile.getBoolean("settings.restrictions.noTeleport", false); noTeleport = configFile.getBoolean("settings.restrictions.noTeleport", false);
crazyloginFileName = configFile.getString("Converter.CrazyLogin.fileName", "accounts.db"); crazyloginFileName = configFile.getString("Converter.CrazyLogin.fileName", "accounts.db");
getPassRegex = configFile.getString("settings.restrictions.allowedPasswordCharacters", "[\\x21-\\x7E]*"); getPassRegex = configFile.getString("settings.restrictions.allowedPasswordCharacters", "[\\x21-\\x7E]*");
@ -311,7 +312,7 @@ public final class Settings {
try { try {
return Files.toString(EMAIL_FILE, Charsets.UTF_8); return Files.toString(EMAIL_FILE, Charsets.UTF_8);
} catch (IOException e) { } catch (IOException e) {
ConsoleLogger.showError(e.getMessage()); ConsoleLogger.showError("Error loading email text: " + StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e); ConsoleLogger.writeStackTrace(e);
return ""; return "";
} }
@ -726,7 +727,7 @@ public final class Settings {
if (!contains("Email.emailOauth2Token")) if (!contains("Email.emailOauth2Token"))
set("Email.emailOauth2Token", ""); set("Email.emailOauth2Token", "");
if (!contains("Hook.sendPlayerTo")) { if (!contains("Hooks.sendPlayerTo")) {
set("Hooks.sendPlayerTo", ""); set("Hooks.sendPlayerTo", "");
changes = true; changes = true;
} }

View File

@ -0,0 +1,29 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import static fr.xephi.authme.settings.domain.Property.newProperty;
public class BackupSettings implements SettingsClass {
@Comment("Enable or disable automatic backup")
public static final Property<Boolean> ENABLED =
newProperty("BackupSystem.ActivateBackup", false);
@Comment("Set backup at every start of server")
public static final Property<Boolean> ON_SERVER_START =
newProperty("BackupSystem.OnServerStart", false);
@Comment("Set backup at every stop of server")
public static final Property<Boolean> ON_SERVER_STOP =
newProperty("BackupSystem.OnServerStop", true);
@Comment(" Windows only mysql installation Path")
public static final Property<String> MYSQL_WINDOWS_PATH =
newProperty("BackupSystem.MysqlWindowsPath", "C:\\Program Files\\MySQL\\MySQL Server 5.1\\");
private BackupSettings() {
}
}

View File

@ -0,0 +1,32 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static fr.xephi.authme.settings.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.domain.PropertyType.STRING;
public class ConverterSettings implements SettingsClass {
@Comment("Rakamak file name")
public static final Property<String> RAKAMAK_FILE_NAME =
newProperty(STRING, "Converter.Rakamak.fileName", "users.rak");
@Comment("Rakamak use IP?")
public static final Property<Boolean> RAKAMAK_USE_IP =
newProperty(BOOLEAN, "Converter.Rakamak.useIP", false);
@Comment("Rakamak IP file name")
public static final Property<String> RAKAMAK_IP_FILE_NAME =
newProperty(STRING, "Converter.Rakamak.ipFileName", "UsersIp.rak");
@Comment("CrazyLogin database file name")
public static final Property<String> CRAZYLOGIN_FILE_NAME =
newProperty(STRING, "Converter.CrazyLogin.fileName", "accounts.db");
private ConverterSettings() {
}
}

View File

@ -0,0 +1,108 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import static fr.xephi.authme.settings.domain.Property.newProperty;
public class DatabaseSettings implements SettingsClass {
@Comment({"What type of database do you want to use?",
"Valid values: sqlite, mysql"})
public static final Property<DataSource.DataSourceType> BACKEND =
newProperty(DataSource.DataSourceType.class, "DataSource.backend", DataSource.DataSourceType.SQLITE);
@Comment("Enable database caching, should improve database performance")
public static final Property<Boolean> USE_CACHING =
newProperty("DataSource.caching", true);
@Comment("Database host address")
public static final Property<String> MYSQL_HOST =
newProperty("DataSource.mySQLHost", "127.0.0.1");
@Comment("Database port")
public static final Property<String> MYSQL_PORT =
newProperty("DataSource.mySQLPort", "3306");
@Comment("Username about Database Connection Infos")
public static final Property<String> MYSQL_USERNAME =
newProperty("DataSource.mySQLUsername", "authme");
@Comment("Password about Database Connection Infos")
public static final Property<String> MYSQL_PASSWORD =
newProperty("DataSource.mySQLPassword", "123456");
@Comment("Database Name, use with converters or as SQLITE database name")
public static final Property<String> MYSQL_DATABASE =
newProperty("DataSource.mySQLDatabase", "authme");
@Comment("Table of the database")
public static final Property<String> MYSQL_TABLE =
newProperty("DataSource.mySQLTablename", "authme");
@Comment("Column of IDs to sort data")
public static final Property<String> MYSQL_COL_ID =
newProperty("DataSource.mySQLColumnId", "id");
@Comment("Column for storing or checking players nickname")
public static final Property<String> MYSQL_COL_NAME =
newProperty("DataSource.mySQLColumnName", "username");
@Comment("Column for storing or checking players RealName ")
public static final Property<String> MYSQL_COL_REALNAME =
newProperty("DataSource.mySQLRealName", "realname");
@Comment("Column for storing players passwords")
public static final Property<String> MYSQL_COL_PASSWORD =
newProperty("DataSource.mySQLColumnPassword", "password");
@Comment("Column for storing players passwords salts")
public static final Property<String> MYSQL_COL_SALT =
newProperty("ExternalBoardOptions.mySQLColumnSalt", "");
@Comment("Column for storing players emails")
public static final Property<String> MYSQL_COL_EMAIL =
newProperty("DataSource.mySQLColumnEmail", "email");
@Comment("Column for storing if a player is logged in or not")
public static final Property<String> MYSQL_COL_ISLOGGED =
newProperty("DataSource.mySQLColumnLogged", "isLogged");
@Comment("Column for storing players ips")
public static final Property<String> MYSQL_COL_IP =
newProperty("DataSource.mySQLColumnIp", "ip");
@Comment("Column for storing players lastlogins")
public static final Property<String> MYSQL_COL_LASTLOGIN =
newProperty("DataSource.mySQLColumnLastLogin", "lastlogin");
@Comment("Column for storing player LastLocation - X")
public static final Property<String> MYSQL_COL_LASTLOC_X =
newProperty("DataSource.mySQLlastlocX", "x");
@Comment("Column for storing player LastLocation - Y")
public static final Property<String> MYSQL_COL_LASTLOC_Y =
newProperty("DataSource.mySQLlastlocY", "y");
@Comment("Column for storing player LastLocation - Z")
public static final Property<String> MYSQL_COL_LASTLOC_Z =
newProperty("DataSource.mySQLlastlocZ", "z");
@Comment("Column for storing player LastLocation - World Name")
public static final Property<String> MYSQL_COL_LASTLOC_WORLD =
newProperty("DataSource.mySQLlastlocWorld", "world");
@Comment("Column for storing players groups")
public static final Property<String> MYSQL_COL_GROUP =
newProperty("ExternalBoardOptions.mySQLColumnGroup", "");
@Comment("Enable this when you allow registration through a website")
public static final Property<Boolean> MYSQL_WEBSITE =
newProperty("DataSource.mySQLWebsite", false);
private DatabaseSettings() {
}
}

View File

@ -0,0 +1,76 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static fr.xephi.authme.settings.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.domain.PropertyType.INTEGER;
import static fr.xephi.authme.settings.domain.PropertyType.STRING;
import static fr.xephi.authme.settings.domain.PropertyType.STRING_LIST;
public class EmailSettings implements SettingsClass {
@Comment("Email SMTP server host")
public static final Property<String> SMTP_HOST =
newProperty(STRING, "Email.mailSMTP", "smtp.gmail.com");
@Comment("Email SMTP server port")
public static final Property<Integer> SMTP_PORT =
newProperty(INTEGER, "Email.mailPort", 465);
@Comment("Email account which sends the mails")
public static final Property<String> MAIL_ACCOUNT =
newProperty(STRING, "Email.mailAccount", "");
@Comment("Email account password")
public static final Property<String> MAIL_PASSWORD =
newProperty(STRING, "Email.mailPassword", "");
@Comment("Custom sender name, replacing the mailAccount name in the email")
public static final Property<String> MAIL_SENDER_NAME =
newProperty("Email.mailSenderName", "");
@Comment("Recovery password length")
public static final Property<Integer> RECOVERY_PASSWORD_LENGTH =
newProperty(INTEGER, "Email.RecoveryPasswordLength", 8);
@Comment("Mail Subject")
public static final Property<String> RECOVERY_MAIL_SUBJECT =
newProperty(STRING, "Email.mailSubject", "Your new AuthMe password");
@Comment("Like maxRegPerIP but with email")
public static final Property<Integer> MAX_REG_PER_EMAIL =
newProperty(INTEGER, "Email.maxRegPerEmail", 1);
@Comment("Recall players to add an email?")
public static final Property<Boolean> RECALL_PLAYERS =
newProperty(BOOLEAN, "Email.recallPlayers", false);
@Comment("Delay in minute for the recall scheduler")
public static final Property<Integer> DELAY_RECALL =
newProperty(INTEGER, "Email.delayRecall", 5);
@Comment("Blacklist these domains for emails")
public static final Property<List<String>> DOMAIN_BLACKLIST =
newProperty(STRING_LIST, "Email.emailBlacklisted", "10minutemail.com");
@Comment("Whitelist ONLY these domains for emails")
public static final Property<List<String>> DOMAIN_WHITELIST =
newProperty(STRING_LIST, "Email.emailWhitelisted");
@Comment("Send the new password drawn in an image?")
public static final Property<Boolean> PASSWORD_AS_IMAGE =
newProperty(BOOLEAN, "Email.generateImage", false);
@Comment("The OAuth2 token")
public static final Property<String> OAUTH2_TOKEN =
newProperty(STRING, "Email.emailOauth2Token", "");
private EmailSettings() {
}
}

View File

@ -0,0 +1,72 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.PropertyType;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
public class HooksSettings implements SettingsClass {
@Comment("Do we need to hook with multiverse for spawn checking?")
public static final Property<Boolean> MULTIVERSE =
newProperty("Hooks.multiverse", true);
@Comment("Do we need to hook with BungeeCord?")
public static final Property<Boolean> BUNGEECORD =
newProperty("Hooks.bungeecord", false);
@Comment("Send player to this BungeeCord server after register/login")
public static final Property<String> BUNGEECORD_SERVER =
newProperty("Hooks.sendPlayerTo", "");
@Comment("Do we need to disable Essentials SocialSpy on join?")
public static final Property<Boolean> DISABLE_SOCIAL_SPY =
newProperty("Hooks.disableSocialSpy", false);
@Comment("Do we need to force /motd Essentials command on join?")
public static final Property<Boolean> USE_ESSENTIALS_MOTD =
newProperty("Hooks.useEssentialsMotd", false);
@Comment("Do we need to cache custom Attributes?")
public static final Property<Boolean> CACHE_CUSTOM_ATTRIBUTES =
newProperty("Hooks.customAttributes", false);
@Comment("These features are only available on VeryGames Server Provider")
public static final Property<Boolean> ENABLE_VERYGAMES_IP_CHECK =
newProperty("VeryGames.enableIpCheck", false);
@Comment({
"-1 means disabled. If you want that only activated players",
"can log into your server, you can set here the group number",
"of unactivated users, needed for some forum/CMS support"})
public static final Property<Integer> NON_ACTIVATED_USERS_GROUP =
newProperty("ExternalBoardOptions.nonActivedUserGroup", -1);
@Comment("Other MySQL columns where we need to put the username (case-sensitive)")
public static final Property<List<String>> MYSQL_OTHER_USERNAME_COLS =
newProperty(PropertyType.STRING_LIST, "ExternalBoardOptions.mySQLOtherUsernameColumns");
@Comment("How much log2 rounds needed in BCrypt (do not change if you do not know what it does)")
public static final Property<Integer> BCRYPT_LOG2_ROUND =
newProperty("ExternalBoardOptions.bCryptLog2Round", 10);
@Comment("phpBB table prefix defined during the phpBB installation process")
public static final Property<String> PHPBB_TABLE_PREFIX =
newProperty("ExternalBoardOptions.phpbbTablePrefix", "phpbb_");
@Comment("phpBB activated group ID; 2 is the default registered group defined by phpBB")
public static final Property<Integer> PHPBB_ACTIVATED_GROUP_ID =
newProperty("ExternalBoardOptions.phpbbActivatedGroupId", 2);
@Comment("Wordpress prefix defined during WordPress installation")
public static final Property<String> WORDPRESS_TABLE_PREFIX =
newProperty("ExternalBoardOptions.wordpressTablePrefix", "wp_");
private HooksSettings() {
}
}

View File

@ -0,0 +1,156 @@
package fr.xephi.authme.settings.custom;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.configuration.file.FileConfiguration;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* The new settings manager.
*/
public class NewSetting {
private File file;
private FileConfiguration configuration;
/**
* Constructor.
* Loads the file as YAML and checks its integrity.
*
* @param configuration The configuration to interact with
* @param file The configuration file
*/
public NewSetting(FileConfiguration configuration, File file) {
this.configuration = configuration;
this.file = file;
// TODO ljacqu 20160109: Ensure that save() works as desired (i.e. that it always produces valid YAML)
// and then uncomment the lines below. Once this is uncommented, the checks in the old Settings.java should
// be removed as we should check to rewrite the config.yml file only at one place
// --------
// PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields();
// if (!containsAllSettings(propertyMap)) {
// save(propertyMap);
// }
}
/**
* Constructor for testing purposes, allowing more options.
*
* @param configuration The FileConfiguration object to use
* @param file The file to write to
* @param propertyMap The property map whose properties should be verified for presence, or null to skip this
*/
@VisibleForTesting
NewSetting(FileConfiguration configuration, File file, PropertyMap propertyMap) {
this.configuration = configuration;
this.file = file;
if (propertyMap != null && !containsAllSettings(propertyMap)) {
save(propertyMap);
}
}
/**
* Get the given property from the configuration.
*
* @param property The property to retrieve
* @param <T> The property's type
* @return The property's value
*/
public <T> T getProperty(Property<T> property) {
return property.getFromFile(configuration);
}
public void save() {
save(SettingsFieldRetriever.getAllPropertyFields());
}
public void save(PropertyMap propertyMap) {
try (FileWriter writer = new FileWriter(file)) {
writer.write("");
// Contains all but the last node of the setting, e.g. [DataSource, mysql] for "DataSource.mysql.username"
List<String> currentPath = new ArrayList<>();
for (Map.Entry<Property<?>, String[]> entry : propertyMap.entrySet()) {
Property<?> property = entry.getKey();
// Handle properties
List<String> propertyPath = Arrays.asList(property.getPath().split("\\."));
List<String> commonPathParts = CollectionUtils.filterCommonStart(
currentPath, propertyPath.subList(0, propertyPath.size() - 1));
List<String> newPathParts = CollectionUtils.getRange(propertyPath, commonPathParts.size());
if (commonPathParts.isEmpty()) {
writer.append("\n");
}
int indentationLevel = commonPathParts.size();
if (newPathParts.size() > 1) {
for (String path : newPathParts.subList(0, newPathParts.size() - 1)) {
writer.append("\n")
.append(indent(indentationLevel))
.append(path)
.append(": ");
++indentationLevel;
}
}
for (String comment : entry.getValue()) {
writer.append("\n")
.append(indent(indentationLevel))
.append("# ")
.append(comment);
}
writer.append("\n")
.append(indent(indentationLevel))
.append(CollectionUtils.getRange(newPathParts, newPathParts.size() - 1).get(0))
.append(": ");
List<String> yamlLines = property.formatValueAsYaml(configuration);
String delim = "";
for (String yamlLine : yamlLines) {
writer.append(delim).append(yamlLine);
delim = "\n" + indent(indentationLevel);
}
currentPath = propertyPath.subList(0, propertyPath.size() - 1);
}
writer.flush();
writer.close();
} catch (IOException e) {
ConsoleLogger.showError("Could not save config file - " + StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
}
}
@VisibleForTesting
boolean containsAllSettings(PropertyMap propertyMap) {
for (Property<?> property : propertyMap.keySet()) {
if (!property.isPresent(configuration)) {
return false;
}
}
return true;
}
private static String indent(int level) {
// YAML uses indentation of 4 spaces
StringBuilder sb = new StringBuilder(level * 4);
for (int i = 0; i < level; ++i) {
sb.append(" ");
}
return sb.toString();
}
}

View File

@ -0,0 +1,60 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import static fr.xephi.authme.settings.domain.Property.newProperty;
public class PluginSettings implements SettingsClass {
@Comment("The name shown in the help messages")
public static final Property<String> HELP_HEADER =
newProperty("settings.helpHeader", "AuthMeReloaded");
@Comment({
"Do you want to enable the session feature?",
"If enabled, when a player authenticates successfully,",
"his IP and his nickname is saved.",
"The next time the player joins the server, if his IP",
"is the same as last time and the timeout hasn't",
"expired, he will not need to authenticate."
})
public static final Property<Boolean> SESSIONS_ENABLED =
newProperty("settings.sessions.enabled", false);
@Comment({
"After how many minutes should a session expire?",
"0 for unlimited time (Very dangerous, use it at your own risk!)",
"Remember that sessions will end only after the timeout, and",
"if the player's IP has changed but the timeout hasn't expired,",
"the player will be kicked from the server due to invalid session"
})
public static final Property<Integer> SESSIONS_TIMEOUT =
newProperty("settings.sessions.timeout", 10);
@Comment({
"Should the session expire if the player tries to log in with",
"another IP address?"
})
public static final Property<Boolean> SESSIONS_EXPIRE_ON_IP_CHANGE =
newProperty("settings.sessions.sessionExpireOnIpChange", true);
@Comment("Message language, available: en, de, br, cz, pl, fr, ru, hu, sk, es, zhtw, fi, zhcn, lt, it, ko, pt")
public static final Property<String> MESSAGES_LANGUAGE =
newProperty("settings.messagesLanguage", "en");
@Comment({
"Take care with this option; if you don't want",
"to use Vault and group switching of AuthMe",
"for unloggedIn players, set this setting to true.",
"Default is false."
})
public static final Property<Boolean> ENABLE_PERMISSION_CHECK =
newProperty("permission.EnablePermissionCheck", false);
private PluginSettings() {
}
}

View File

@ -0,0 +1,46 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static fr.xephi.authme.settings.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.domain.PropertyType.INTEGER;
import static fr.xephi.authme.settings.domain.PropertyType.STRING_LIST;
public class ProtectionSettings implements SettingsClass {
@Comment("Enable some servers protection (country based login, antibot)")
public static final Property<Boolean> ENABLE_PROTECTION =
newProperty(BOOLEAN, "Protection.enableProtection", false);
@Comment({"Countries allowed to join the server and register, see http://dev.bukkit.org/bukkit-plugins/authme-reloaded/pages/countries-codes/ for countries' codes",
"PLEASE USE QUOTES!"})
public static final Property<List<String>> COUNTRIES_WHITELIST =
newProperty(STRING_LIST, "Protection.countries", "US", "GB", "A1");
@Comment({"Countries not allowed to join the server and register",
"PLEASE USE QUOTES!"})
public static final Property<List<String>> COUNTRIES_BLACKLIST =
newProperty(STRING_LIST, "Protection.countriesBlacklist");
@Comment("Do we need to enable automatic antibot system?")
public static final Property<Boolean> ENABLE_ANTIBOT =
newProperty(BOOLEAN, "Protection.enableAntiBot", false);
@Comment("Max number of player allowed to login in 5 secs before enable AntiBot system automatically")
public static final Property<Integer> ANTIBOT_SENSIBILITY =
newProperty(INTEGER, "Protection.antiBotSensibility", 5);
@Comment("Duration in minutes of the antibot automatic system")
public static final Property<Integer> ANTIBOT_DURATION =
newProperty(INTEGER, "Protection.antiBotDuration", 10);
private ProtectionSettings() {
}
}

View File

@ -0,0 +1,49 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static fr.xephi.authme.settings.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.domain.PropertyType.INTEGER;
import static fr.xephi.authme.settings.domain.PropertyType.STRING;
public class PurgeSettings implements SettingsClass {
@Comment("If enabled, AuthMe automatically purges old, unused accounts")
public static final Property<Boolean> USE_AUTO_PURGE =
newProperty(BOOLEAN, "Purge.useAutoPurge", false);
@Comment("Number of Days an account become Unused")
public static final Property<Integer> DAYS_BEFORE_REMOVE_PLAYER =
newProperty(INTEGER, "Purge.daysBeforeRemovePlayer", 60);
@Comment("Do we need to remove the player.dat file during purge process?")
public static final Property<Boolean> REMOVE_PLAYER_DAT =
newProperty(BOOLEAN, "Purge.removePlayerDat", false);
@Comment("Do we need to remove the Essentials/users/player.yml file during purge process?")
public static final Property<Boolean> REMOVE_ESSENTIALS_FILES =
newProperty(BOOLEAN, "Purge.removeEssentialsFile", false);
@Comment("World where are players.dat stores")
public static final Property<String> DEFAULT_WORLD =
newProperty(STRING, "Purge.defaultWorld", "world");
@Comment("Do we need to remove LimitedCreative/inventories/player.yml, player_creative.yml files during purge process ?")
public static final Property<Boolean> REMOVE_LIMITED_CREATIVE_INVENTORIES =
newProperty(BOOLEAN, "Purge.removeLimitedCreativesInventories", false);
@Comment("Do we need to remove the AntiXRayData/PlayerData/player file during purge process?")
public static final Property<Boolean> REMOVE_ANTI_XRAY_FILE =
newProperty(BOOLEAN, "Purge.removeAntiXRayFile", false);
@Comment("Do we need to remove permissions?")
public static final Property<Boolean> REMOVE_PERMISSIONS =
newProperty(BOOLEAN, "Purge.removePermissions", false);
private PurgeSettings() {
}
}

View File

@ -0,0 +1,101 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.PropertyType;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
public class RegistrationSettings implements SettingsClass {
@Comment("Enable registration on the server?")
public static final Property<Boolean> IS_ENABLED =
newProperty("settings.registration.enabled", true);
@Comment({
"Send every X seconds a message to a player to",
"remind him that he has to login/register"})
public static final Property<Integer> MESSAGE_INTERVAL =
newProperty("settings.registration.messageInterval", 5);
@Comment({
"Only registered and logged in players can play.",
"See restrictions for exceptions"})
public static final Property<Boolean> FORCE =
newProperty("settings.registration.force", true);
@Comment("Do we replace password registration by an email registration method?")
public static final Property<Boolean> USE_EMAIL_REGISTRATION =
newProperty("settings.registration.enableEmailRegistrationSystem", false);
@Comment({
"Enable double check of email when you register",
"when it's true, registration requires that kind of command:",
"/register <email> <confirmEmail>"})
public static final Property<Boolean> ENABLE_CONFIRM_EMAIL =
newProperty("settings.registration.doubleEmailCheck", false);
@Comment({
"Do we force kicking player after a successful registration?",
"Do not use with login feature below"})
public static final Property<Boolean> FORCE_KICK_AFTER_REGISTER =
newProperty("settings.registration.forceKickAfterRegister", false);
@Comment("Does AuthMe need to enforce a /login after a successful registration?")
public static final Property<Boolean> FORCE_LOGIN_AFTER_REGISTER =
newProperty("settings.registration.forceLoginAfterRegister", false);
@Comment("Force these commands after /login, without any '/', use %p to replace with player name")
public static final Property<List<String>> FORCE_COMMANDS =
newProperty(PropertyType.STRING_LIST, "settings.forceCommands");
@Comment("Force these commands after /login as service console, without any '/'. "
+ "Use %p to replace with player name")
public static final Property<List<String>> FORCE_COMMANDS_AS_CONSOLE =
newProperty(PropertyType.STRING_LIST, "settings.forceCommandsAsConsole");
@Comment("Force these commands after /register, without any '/', use %p to replace with player name")
public static final Property<List<String>> FORCE_REGISTER_COMMANDS =
newProperty(PropertyType.STRING_LIST, "settings.forceRegisterCommands");
@Comment("Force these commands after /register as a server console, without any '/'. "
+ "Use %p to replace with player name")
public static final Property<List<String>> FORCE_REGISTER_COMMANDS_AS_CONSOLE =
newProperty(PropertyType.STRING_LIST, "settings.forceRegisterCommandsAsConsole");
@Comment({
"Enable to display the welcome message (welcome.txt) after a registration or a login",
"You can use colors in this welcome.txt + some replaced strings:",
"{PLAYER}: player name, {ONLINE}: display number of online players, {MAXPLAYERS}: display server slots,",
"{IP}: player ip, {LOGINS}: number of players logged, {WORLD}: player current world, {SERVER}: server name",
"{VERSION}: get current bukkit version, {COUNTRY}: player country"})
public static final Property<Boolean> USE_WELCOME_MESSAGE =
newProperty("settings.useWelcomeMessage", true);
@Comment("Do we need to broadcast the welcome message to all server or only to the player? set true for "
+ "server or false for player")
public static final Property<Boolean> BROADCAST_WELCOME_MESSAGE =
newProperty("settings.broadcastWelcomeMessage", false);
@Comment("Do we need to delay the join/leave message to be displayed only when the player is authenticated?")
public static final Property<Boolean> DELAY_JOIN_LEAVE_MESSAGES =
newProperty("settings.delayJoinLeaveMessages", true);
@Comment("Do we need to add potion effect Blinding before login/reigster?")
public static final Property<Boolean> APPLY_BLIND_EFFECT =
newProperty("settings.applyBlindEffect", false);
@Comment({
"Do we need to prevent people to login with another case?",
"If Xephi is registered, then Xephi can login, but not XEPHI/xephi/XePhI"})
public static final Property<Boolean> PREVENT_OTHER_CASE =
newProperty("settings.preventOtherCase", false);
private RegistrationSettings() {
}
}

View File

@ -0,0 +1,184 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.PropertyType;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
public class RestrictionSettings implements SettingsClass {
@Comment({
"Can not authenticated players chat and see the chat log?",
"Keep in mind that this feature also blocks all commands not",
"listed in the list below."})
public static final Property<Boolean> ALLOW_CHAT =
newProperty("settings.restrictions.allowChat", false);
@Comment("Allowed commands for unauthenticated players")
public static final Property<List<String>> ALLOW_COMMANDS =
newProperty(PropertyType.STRING_LIST, "settings.restrictions.allowCommands",
"login", "register", "l", "reg", "email", "captcha");
@Comment("Max number of allowed registrations per IP")
// TODO ljacqu 20160109: If 0 == unlimited, add this fact ot the comment
public static final Property<Integer> MAX_REGISTRATION_PER_IP =
newProperty("settings.restrictions.maxRegPerIp", 1);
@Comment("Minimum allowed username length")
public static final Property<Integer> MIN_NICKNAME_LENGTH =
newProperty("settings.restrictions.minNicknameLength", 4);
@Comment("Maximum allowed username length")
public static final Property<Integer> MAX_NICKNAME_LENGTH =
newProperty("settings.restrictions.maxNicknameLength", 16);
@Comment({
"When this setting is enabled, online players can't be kicked out",
"due to \"Logged in from another Location\"",
"This setting will prevent potential security exploits."})
public static final Property<Boolean> FORCE_SINGLE_SESSION =
newProperty("settings.restrictions.ForceSingleSession", true);
@Comment({
"If enabled, every player will be teleported to the world spawnpoint",
"after successful authentication.",
"The quit location of the player will be overwritten.",
"This is different from \"teleportUnAuthedToSpawn\" that teleport player",
"back to his quit location after the authentication."})
public static final Property<Boolean> FORCE_SPAWN_LOCATION_AFTER_LOGIN =
newProperty("settings.restrictions.ForceSpawnLocOnJoinEnabled", false);
@Comment("This option will save the quit location of the players.")
public static final Property<Boolean> SAVE_QUIT_LOCATION =
newProperty("settings.restrictions.SaveQuitLocation", false);
@Comment({
"To activate the restricted user feature you need",
"to enable this option and configure the AllowedRestrctedUser field."})
public static final Property<Boolean> ENABLE_RESTRICTED_USERS =
newProperty("settings.restrictions.AllowRestrictedUser", false);
@Comment({
"The restricted user feature will kick players listed below",
"if they don't match the defined IP address.",
"Example:",
" AllowedRestrictedUser:",
" - playername;127.0.0.1"})
public static final Property<List<String>> ALLOWED_RESTRICTED_USERS =
newProperty(PropertyType.STRING_LIST, "settings.restrictions.AllowedRestrictedUser");
@Comment("Should unregistered players be kicked immediately?")
public static final Property<Boolean> KICK_NON_REGISTERED =
newProperty("settings.restrictions.kickNonRegistered", false);
@Comment("Should players be kicked on wrong password?")
public static final Property<Boolean> KICK_ON_WRONG_PASSWORD =
newProperty("settings.restrictions.kickOnWrongPassword", false);
@Comment({
"Should not logged in players be teleported to the spawn?",
"After the authentication they will be teleported back to",
"their normal position."})
public static final Property<Boolean> TELEPORT_UNAUTHED_TO_SPAWN =
newProperty("settings.restrictions.teleportUnAuthedToSpawn", false);
@Comment("Can unregistered players walk around?")
public static final Property<Boolean> ALLOW_UNAUTHED_MOVEMENT =
newProperty("settings.restrictions.allowMovement", false);
@Comment({
"Should not authenticated players have speed = 0?",
"This will reset the fly/walk speed to default value after the login."})
public static final Property<Boolean> REMOVE_SPEED =
newProperty("settings.restrictions.removeSpeed", true);
@Comment({
"After how many seconds should players who fail to login or register",
"be kicked? Set to 0 to disable."})
public static final Property<Integer> TIMEOUT =
newProperty("settings.restrictions.timeout", 30);
@Comment("Regex syntax of allowed characters in the player name.")
public static final Property<String> ALLOWED_NICKNAME_CHARACTERS =
newProperty("settings.restrictions.allowedNicknameCharacters", "[a-zA-Z0-9_]*");
@Comment({
"How far can unregistered players walk?",
"Set to 0 for unlimited radius"
})
public static final Property<Integer> ALLOWED_MOVEMENT_RADIUS =
newProperty("settings.restrictions.allowedMovementRadius", 100);
@Comment({
"Enable double check of password when you register",
"when it's true, registration requires that kind of command:",
"/register <password> <confirmPassword>"})
public static final Property<Boolean> ENABLE_PASSWORD_CONFIRMATION =
newProperty("settings.restrictions.enablePasswordConfirmation", true);
@Comment("Should we protect the player inventory before logging in?")
public static final Property<Boolean> PROTECT_INVENTORY_BEFORE_LOGIN =
newProperty("settings.restrictions.ProtectInventoryBeforeLogIn", true);
@Comment({
"Should we display all other accounts from a player when he joins?",
"permission: /authme.admin.accounts"})
public static final Property<Boolean> DISPLAY_OTHER_ACCOUNTS =
newProperty("settings.restrictions.displayOtherAccounts", true);
@Comment({
"WorldNames where we need to force the spawn location for ForceSpawnLocOnJoinEnabled",
"Case-sensitive!"})
public static final Property<List<String>> FORCE_SPAWN_ON_WORLDS =
newProperty(PropertyType.STRING_LIST, "settings.restrictions.ForceSpawnOnTheseWorlds",
"world", "world_nether", "world_the_end");
@Comment("Ban ip when the ip is not the ip registered in database")
public static final Property<Boolean> BAN_UNKNOWN_IP =
newProperty("settings.restrictions.banUnsafedIP", false);
@Comment("Spawn priority; values: authme, essentials, multiverse, default")
public static final Property<String> SPAWN_PRIORITY =
newProperty("settings.restrictions.spawnPriority", "authme,essentials,multiverse,default");
@Comment("Maximum Login authorized by IP")
public static final Property<Integer> MAX_LOGIN_PER_IP =
newProperty("settings.restrictions.maxLoginPerIp", 0);
@Comment("Maximum Join authorized by IP")
public static final Property<Integer> MAX_JOIN_PER_IP =
newProperty("settings.restrictions.maxJoinPerIp", 0);
@Comment("AuthMe will NEVER teleport players if set to true!")
public static final Property<Boolean> NO_TELEPORT =
newProperty("settings.restrictions.noTeleport", false);
@Comment("Regex syntax for allowed chars in passwords")
public static final Property<String> ALLOWED_PASSWORD_REGEX =
newProperty("settings.restrictions.allowedPasswordCharacters", "[\\x21-\\x7E]*");
@Comment("Force survival gamemode when player joins?")
public static final Property<Boolean> FORCE_SURVIVAL_MODE =
newProperty("settings.GameMode.ForceSurvivalMode", false);
@Comment({
"Below you can list all account names that",
"AuthMe will ignore for registration or login, configure it",
"at your own risk!! Remember that if you are going to add",
"nickname with [], you have to delimit name with ' '.",
"this option add compatibility with BuildCraft and some",
"other mods.",
"It is case-sensitive!"
})
public static final Property<List<String>> UNRESTRICTED_NAMES =
newProperty(PropertyType.STRING_LIST, "settings.unrestrictions.UnrestrictedName");
private RestrictionSettings() {
}
}

View File

@ -0,0 +1,103 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static fr.xephi.authme.settings.domain.PropertyType.STRING_LIST;
public class SecuritySettings implements SettingsClass {
@Comment({"Stop the server if we can't contact the sql database",
"Take care with this, if you set this to false,",
"AuthMe will automatically disable and the server won't be protected!"})
public static final Property<Boolean> STOP_SERVER_ON_PROBLEM =
newProperty("Security.SQLProblem.stopServer", true);
@Comment("/reload support")
public static final Property<Boolean> USE_RELOAD_COMMAND_SUPPORT =
newProperty("Security.ReloadCommand.useReloadCommandSupport", true);
@Comment("Remove spam from console?")
public static final Property<Boolean> REMOVE_SPAM_FROM_CONSOLE =
newProperty("Security.console.noConsoleSpam", false);
@Comment("Remove passwords from console?")
public static final Property<Boolean> REMOVE_PASSWORD_FROM_CONSOLE =
newProperty("Security.console.removePassword", true);
@Comment("Player need to put a captcha when he fails too lot the password")
public static final Property<Boolean> USE_CAPTCHA =
newProperty("Security.captcha.useCaptcha", false);
@Comment("Max allowed tries before request a captcha")
public static final Property<Integer> MAX_LOGIN_TRIES_BEFORE_CAPTCHA =
newProperty("Security.captcha.maxLoginTry", 5);
@Comment("Captcha length")
public static final Property<Integer> CAPTCHA_LENGTH =
newProperty("Security.captcha.captchaLength", 5);
@Comment({"Kick players before stopping the server, that allow us to save position of players",
"and all needed information correctly without any corruption."})
public static final Property<Boolean> KICK_PLAYERS_BEFORE_STOPPING =
newProperty("Security.stop.kickPlayersBeforeStopping", true);
@Comment("Minimum length of password")
public static final Property<Integer> MIN_PASSWORD_LENGTH =
newProperty("settings.security.minPasswordLength", 5);
@Comment("Maximum length of password")
public static final Property<Integer> MAX_PASSWORD_LENGTH =
newProperty("settings.security.passwordMaxLength", 30);
@Comment({
"This is a very important option: every time a player joins the server,",
"if they are registered, AuthMe will switch him to unLoggedInGroup.",
"This should prevent all major exploits.",
"You can set up your permission plugin with this special group to have no permissions,",
"or only permission to chat (or permission to send private messages etc.).",
"The better way is to set up this group with few permissions, so if a player",
"tries to exploit an account they can do only what you've defined for the group.",
"After, a logged in player will be moved to his correct permissions group!",
"Please note that the group name is case-sensitive, so 'admin' is different from 'Admin'",
"Otherwise your group will be wiped and the player will join in the default group []!",
"Example unLoggedinGroup: NotLogged"
})
public static final Property<String> UNLOGGEDIN_GROUP =
newProperty("settings.security.unLoggedinGroup", "unLoggedinGroup");
@Comment({
"Possible values: MD5, SHA1, SHA256, WHIRLPOOL, XAUTH, MD5VB, PHPBB,",
"MYBB, IPB3, PHPFUSION, SMF, XENFORO, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512,",
"DOUBLEMD5, PBKDF2, PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (for developers only)"
})
public static final Property<HashAlgorithm> PASSWORD_HASH =
newProperty(HashAlgorithm.class, "settings.security.passwordHash", HashAlgorithm.SHA256);
@Comment("Salt length for the SALTED2MD5 MD5(MD5(password)+salt)")
public static final Property<Integer> DOUBLE_MD5_SALT_LENGTH =
newProperty("settings.security.doubleMD5SaltLength", 8);
@Comment({"If password checking return false, do we need to check with all",
"other password algorithm to check an old password?",
"AuthMe will update the password to the new password hash"})
public static final Property<Boolean> SUPPORT_OLD_PASSWORD_HASH =
newProperty("settings.security.supportOldPasswordHash", false);
@Comment({"Prevent unsafe passwords from being used; put them in lowercase!",
"unsafePasswords:",
"- '123456'",
"- 'password'"})
public static final Property<List<String>> UNSAFE_PASSWORDS =
newProperty(STRING_LIST, "settings.security.unsafePasswords",
"123456", "password", "qwerty", "12345", "54321");
private SecuritySettings() {
}
}

View File

@ -0,0 +1,69 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import fr.xephi.authme.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
/**
* Utility class responsible for the retrieval of all {@link Property} fields via reflections.
*/
final class SettingsFieldRetriever {
/** The classes to scan for properties. */
private static final List<Class<? extends SettingsClass>> CONFIGURATION_CLASSES = Arrays.asList(
ConverterSettings.class, PluginSettings.class, RestrictionSettings.class,
DatabaseSettings.class, EmailSettings.class, HooksSettings.class,
ProtectionSettings.class, PurgeSettings.class, SecuritySettings.class,
RegistrationSettings.class, BackupSettings.class);
private SettingsFieldRetriever() {
}
/**
* Scan all given classes for their properties and return the generated {@link PropertyMap}.
*
* @return PropertyMap containing all found properties and their associated comments
* @see #CONFIGURATION_CLASSES
*/
public static PropertyMap getAllPropertyFields() {
PropertyMap properties = new PropertyMap();
for (Class<?> clazz : CONFIGURATION_CLASSES) {
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
Property property = getFieldIfRelevant(field);
if (property != null) {
properties.put(property, getCommentsForField(field));
}
}
}
return properties;
}
private static String[] getCommentsForField(Field field) {
if (field.isAnnotationPresent(Comment.class)) {
return field.getAnnotation(Comment.class).value();
}
return new String[0];
}
private static Property<?> getFieldIfRelevant(Field field) {
field.setAccessible(true);
if (field.isAccessible() && Property.class.equals(field.getType()) && Modifier.isStatic(field.getModifiers())) {
try {
return (Property) field.get(null);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not fetch field '" + field.getName() + "' from class '"
+ field.getDeclaringClass().getSimpleName() + "': " + StringUtils.formatException(e));
}
}
return null;
}
}

View File

@ -0,0 +1,17 @@
package fr.xephi.authme.settings.domain;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Comment for properties which are also included in the YAML file upon saving.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Comment {
String[] value();
}

View File

@ -0,0 +1,50 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.List;
import static java.util.Arrays.asList;
/**
* Enum property type.
* @param <E> The enum class
*/
class EnumPropertyType<E extends Enum<E>> extends PropertyType<E> {
private Class<E> clazz;
public EnumPropertyType(Class<E> clazz) {
this.clazz = clazz;
}
@Override
public E getFromFile(Property<E> property, FileConfiguration configuration) {
String textValue = configuration.getString(property.getPath());
if (textValue == null) {
return property.getDefaultValue();
}
E mappedValue = mapToEnum(textValue);
return mappedValue != null ? mappedValue : property.getDefaultValue();
}
@Override
protected List<String> asYaml(E value) {
return asList("'" + value + "'");
}
@Override
public boolean contains(Property<E> property, FileConfiguration configuration) {
return super.contains(property, configuration)
&& mapToEnum(configuration.getString(property.getPath())) != null;
}
private E mapToEnum(String value) {
for (E entry : clazz.getEnumConstants()) {
if (entry.name().equalsIgnoreCase(value)) {
return entry;
}
}
return null;
}
}

View File

@ -0,0 +1,114 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Properties (i.e. a <i>setting</i> that is read from the config.yml file).
*/
public class Property<T> {
private final PropertyType<T> type;
private final String path;
private final T defaultValue;
private Property(PropertyType<T> type, String path, T defaultValue) {
Objects.requireNonNull(defaultValue);
this.type = type;
this.path = path;
this.defaultValue = defaultValue;
}
public static <T> Property<T> newProperty(PropertyType<T> type, String path, T defaultValue) {
return new Property<>(type, path, defaultValue);
}
@SafeVarargs
public static <U> Property<List<U>> newProperty(PropertyType<List<U>> type, String path, U... defaultValues) {
return new Property<>(type, path, Arrays.asList(defaultValues));
}
public static <E extends Enum<E>> Property<E> newProperty(Class<E> clazz, String path, E defaultValue) {
return new Property<>(new EnumPropertyType<>(clazz), path, defaultValue);
}
// -----
// Overloaded convenience methods for specific types
// -----
public static Property<Boolean> newProperty(String path, boolean defaultValue) {
return new Property<>(PropertyType.BOOLEAN, path, defaultValue);
}
public static Property<Integer> newProperty(String path, int defaultValue) {
return new Property<>(PropertyType.INTEGER, path, defaultValue);
}
public static Property<String> newProperty(String path, String defaultValue) {
return new Property<>(PropertyType.STRING, path, defaultValue);
}
// -----
// Hooks to the PropertyType methods
// -----
/**
* Get the property value from the given configuration.
*
* @param configuration The configuration to read the value from
* @return The value, or default if not present
*/
public T getFromFile(FileConfiguration configuration) {
return type.getFromFile(this, configuration);
}
/**
* Format the property value as YAML.
*
* @param configuration The configuration to read the value from
* @return The property value as YAML
*/
public List<String> formatValueAsYaml(FileConfiguration configuration) {
return type.asYaml(this, configuration);
}
/**
* Return whether or not the given configuration file contains the property.
*
* @param configuration The configuration file to verify
* @return True if the property is present, false otherwise
*/
public boolean isPresent(FileConfiguration configuration) {
return type.contains(this, configuration);
}
// -----
// Trivial getters
// -----
/**
* Return the default value of the property.
*
* @return The default value
*/
public T getDefaultValue() {
return defaultValue;
}
/**
* Return the property path (i.e. the node at which this property is located in the YAML file).
*
* @return The path
*/
public String getPath() {
return path;
}
@Override
public String toString() {
return "Property '" + path + "'";
}
}

View File

@ -0,0 +1,162 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.ArrayList;
import java.util.List;
import static java.util.Arrays.asList;
/**
* Handles a certain property type and provides type-specific functionality.
*
* @param <T> The value of the property
* @see Property
*/
public abstract class PropertyType<T> {
public static final PropertyType<Boolean> BOOLEAN = new BooleanProperty();
public static final PropertyType<Double> DOUBLE = new DoubleProperty();
public static final PropertyType<Integer> INTEGER = new IntegerProperty();
public static final PropertyType<String> STRING = new StringProperty();
public static final PropertyType<List<String>> STRING_LIST = new StringListProperty();
/**
* Get the property's value from the given YAML configuration.
*
* @param property The property to retrieve
* @param configuration The YAML configuration to read from
* @return The read value, or the default value if absent
*/
public abstract T getFromFile(Property<T> property, FileConfiguration configuration);
/**
* Return the property's value (or its default) as YAML.
*
* @param property The property to transform
* @param configuration The YAML configuration to read from
* @return The read value or its default in YAML format
*/
public List<String> asYaml(Property<T> property, FileConfiguration configuration) {
return asYaml(getFromFile(property, configuration));
}
/**
* Return whether the property is present in the given configuration.
*
* @param property The property to search for
* @param configuration The configuration to verify
* @return True if the property is present, false otherwise
*/
public boolean contains(Property<T> property, FileConfiguration configuration) {
return configuration.contains(property.getPath());
}
/**
* Transform the given value to YAML.
*
* @param value The value to transform
* @return The value as YAML
*/
protected abstract List<String> asYaml(T value);
/**
* Boolean property.
*/
private static final class BooleanProperty extends PropertyType<Boolean> {
@Override
public Boolean getFromFile(Property<Boolean> property, FileConfiguration configuration) {
return configuration.getBoolean(property.getPath(), property.getDefaultValue());
}
@Override
protected List<String> asYaml(Boolean value) {
return asList(value ? "true" : "false");
}
}
/**
* Double property.
*/
private static final class DoubleProperty extends PropertyType<Double> {
@Override
public Double getFromFile(Property<Double> property, FileConfiguration configuration) {
return configuration.getDouble(property.getPath(), property.getDefaultValue());
}
@Override
protected List<String> asYaml(Double value) {
return asList(String.valueOf(value));
}
}
/**
* Integer property.
*/
private static final class IntegerProperty extends PropertyType<Integer> {
@Override
public Integer getFromFile(Property<Integer> property, FileConfiguration configuration) {
return configuration.getInt(property.getPath(), property.getDefaultValue());
}
@Override
protected List<String> asYaml(Integer value) {
return asList(String.valueOf(value));
}
}
/**
* String property.
*/
private static final class StringProperty extends PropertyType<String> {
@Override
public String getFromFile(Property<String> property, FileConfiguration configuration) {
return configuration.getString(property.getPath(), property.getDefaultValue());
}
@Override
protected List<String> asYaml(String value) {
return asList(toYamlLiteral(value));
}
public static String toYamlLiteral(String str) {
// TODO: Need to handle new lines properly
return "'" + str.replace("'", "''") + "'";
}
}
/**
* String list property.
*/
private static final class StringListProperty extends PropertyType<List<String>> {
@Override
public List<String> getFromFile(Property<List<String>> property, FileConfiguration configuration) {
if (!configuration.isList(property.getPath())) {
return property.getDefaultValue();
}
return configuration.getStringList(property.getPath());
}
@Override
protected List<String> asYaml(List<String> value) {
if (value.isEmpty()) {
return asList("[]");
}
List<String> resultLines = new ArrayList<>();
resultLines.add(""); // add
for (String entry : value) {
// TODO: StringProperty#toYamlLiteral will return List<String>...
resultLines.add(" - " + StringProperty.toYamlLiteral(entry));
}
return resultLines;
}
@Override
public boolean contains(Property<List<String>> property, FileConfiguration configuration) {
return configuration.contains(property.getPath()) && configuration.isList(property.getPath());
}
}
}

View File

@ -0,0 +1,7 @@
package fr.xephi.authme.settings.domain;
/**
* Marker for classes that define {@link Property} fields.
*/
public interface SettingsClass {
}

View File

@ -0,0 +1,121 @@
package fr.xephi.authme.settings.propertymap;
import java.util.ArrayList;
import java.util.List;
/**
* Node class for building a tree from supplied String paths, ordered by insertion.
* <p>
* For instance, consider a tree to which the following paths are inserted (in the given order):
* "animal.bird.duck", "color.yellow", "animal.rodent.rat", "animal.rodent.rabbit", "color.red".
* For such a tree:<ul>
* <li>"animal" (or any of its children) is sorted before "color" (or any of its children)</li>
* <li>"animal.bird" or any child thereof is sorted before "animal.rodent"</li>
* <li>"animal.rodent.rat" comes before "animal.rodent.rabbit"</li>
* </ul>
*
* @see PropertyMapComparator
*/
final class Node {
private final String name;
private final List<Node> children;
private Node(String name) {
this.name = name;
this.children = new ArrayList<>();
}
/**
* Create a root node, i.e. the starting node for a new tree. Call this method to create
* a new tree and always pass this root to other methods.
*
* @return The generated root node.
*/
public static Node createRoot() {
return new Node(null);
}
/**
* Add a node to the root, creating any intermediary children that don't exist.
*
* @param root The root to add the path to
* @param fullPath The entire path of the node to add, separate by periods
*/
public static void addNode(Node root, String fullPath) {
String[] pathParts = fullPath.split("\\.");
Node parent = root;
for (String part : pathParts) {
Node child = parent.getChild(part);
if (child == null) {
child = new Node(part);
parent.children.add(child);
}
parent = child;
}
}
/**
* Compare two nodes by this class' sorting behavior (insertion order).
* Note that this method assumes that both supplied paths exist in the tree.
*
* @param root The root of the tree
* @param fullPath1 The full path to the first node
* @param fullPath2 The full path to the second node
* @return The comparison result, in the same format as {@link Comparable#compareTo}
*/
public static int compare(Node root, String fullPath1, String fullPath2) {
String[] path1 = fullPath1.split("\\.");
String[] path2 = fullPath2.split("\\.");
int commonCount = 0;
Node commonNode = root;
while (commonCount < path1.length && commonCount < path2.length
&& path1[commonCount].equals(path2[commonCount]) && commonNode != null) {
commonNode = commonNode.getChild(path1[commonCount]);
++commonCount;
}
if (commonNode == null) {
System.err.println("Could not find common node for '" + fullPath1 + "' at index " + commonCount);
return fullPath1.compareTo(fullPath2); // fallback
} else if (commonCount >= path1.length || commonCount >= path2.length) {
return Integer.compare(path1.length, path2.length);
}
int child1Index = commonNode.getChildIndex(path1[commonCount]);
int child2Index = commonNode.getChildIndex(path2[commonCount]);
return Integer.compare(child1Index, child2Index);
}
private Node getChild(String name) {
for (Node child : children) {
if (child.name.equals(name)) {
return child;
}
}
return null;
}
/**
* Return the child's index, i.e. the position at which it was inserted to its parent.
*
* @param name The name of the node
* @return The insertion index
*/
private int getChildIndex(String name) {
int i = 0;
for (Node child : children) {
if (child.name.equals(name)) {
return i;
}
++i;
}
return -1;
}
@Override
public String toString() {
return "Node '" + name + "'";
}
}

View File

@ -0,0 +1,66 @@
package fr.xephi.authme.settings.propertymap;
import fr.xephi.authme.settings.domain.Property;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* Class wrapping a {@code Map<Property, String[]>} for storing properties and their associated
* comments with custom ordering.
*
* @see PropertyMapComparator for details about the map's order
*/
public class PropertyMap {
private Map<Property<?>, String[]> map;
private PropertyMapComparator comparator;
/**
* Create a new property map.
*/
public PropertyMap() {
comparator = new PropertyMapComparator();
map = new TreeMap<>(comparator);
}
/**
* Add a new property to the map.
*
* @param property The property to add
* @param comments The comments associated to the property
*/
public void put(Property property, String[] comments) {
comparator.add(property);
map.put(property, comments);
}
/**
* Return the entry set of the map.
*
* @return The entry set
*/
public Set<Map.Entry<Property<?>, String[]>> entrySet() {
return map.entrySet();
}
/**
* Return the key set of the map, i.e. all property objects it holds.
*
* @return The key set
*/
public Set<Property<?>> keySet() {
return map.keySet();
}
/**
* Return the size of the map.
*
* @return The size
*/
public int size() {
return map.size();
}
}

View File

@ -0,0 +1,39 @@
package fr.xephi.authme.settings.propertymap;
import fr.xephi.authme.settings.domain.Property;
import java.util.Comparator;
/**
* Custom comparator for {@link PropertyMap}. It guarantees that the map's entries:
* <ul>
* <li>are grouped by path, e.g. all "DataSource.mysql" properties are together, and "DataSource.mysql" properties
* are within the broader "DataSource" group.</li>
* <li>are ordered by insertion, e.g. if the first "DataSource" property is inserted before the first "security"
* property, then "DataSource" properties will come before the "security" ones.</li>
* </ul>
*/
final class PropertyMapComparator implements Comparator<Property> {
private Node parent = Node.createRoot();
/**
* Method to call when adding a new property to the map (as to retain its insertion time).
*
* @param property The property that is being added
*/
public void add(Property property) {
Node.addNode(parent, property.getPath());
}
@Override
public int compare(Property p1, Property p2) {
return Node.compare(parent, p1.getPath(), p2.getPath());
}
@Override
public boolean equals(Object obj) {
return this == obj;
}
}

View File

@ -3,6 +3,7 @@ package fr.xephi.authme.util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* Utils class for collections. * Utils class for collections.
@ -58,4 +59,15 @@ public final class CollectionUtils {
public static <T> boolean isEmpty(Collection<T> coll) { public static <T> boolean isEmpty(Collection<T> coll) {
return coll == null || coll.isEmpty(); return coll == null || coll.isEmpty();
} }
public static <T> List<T> filterCommonStart(List<T> list1, List<T> list2) {
List<T> commonStart = new ArrayList<>();
int minSize = Math.min(list1.size(), list2.size());
int i = 0;
while (i < minSize && Objects.equals(list1.get(i), list2.get(i))) {
commonStart.add(list1.get(i));
++i;
}
return commonStart;
}
} }

View File

@ -156,8 +156,10 @@ settings:
# ForceSurvivalMode to player when join ? # ForceSurvivalMode to player when join ?
ForceSurvivalMode: false ForceSurvivalMode: false
security: security:
# minimum Length of password # Minimum length of password
minPasswordLength: 5 minPasswordLength: 5
# Maximum length of password
passwordMaxLength: 30
# this is very important options, # this is very important options,
# every time player join the server, # every time player join the server,
# if they are registered, AuthMe will switch him # if they are registered, AuthMe will switch him
@ -209,7 +211,7 @@ settings:
# Only registered and logged in players can play. # Only registered and logged in players can play.
# See restrictions for exceptions # See restrictions for exceptions
force: true force: true
# Does we replace password registration by an Email registration method? # Do we replace password registration by an email registration method?
enableEmailRegistrationSystem: false enableEmailRegistrationSystem: false
# Enable double check of email when you register # Enable double check of email when you register
# when it's true, registration require that kind of command: # when it's true, registration require that kind of command:
@ -340,8 +342,6 @@ Email:
RecoveryPasswordLength: 8 RecoveryPasswordLength: 8
# Email subject of password get # Email subject of password get
mailSubject: 'Your new AuthMe Password' mailSubject: 'Your new AuthMe Password'
# Email text here
mailText: 'Dear <playername>, <br /><br /> This is your new AuthMe password for the server <br /><br /> <servername> : <br /><br /> <generatedpass><br /><image><br />Do not forget to change password after login! <br /> /changepassword <generatedpass> newPassword'
# Like maxRegPerIp but with email # Like maxRegPerIp but with email
maxRegPerEmail: 1 maxRegPerEmail: 1
# Recall players to add an email? # Recall players to add an email?
@ -355,6 +355,8 @@ Email:
emailWhitelisted: [] emailWhitelisted: []
# Do we need to send new password draw in an image? # Do we need to send new password draw in an image?
generateImage: false generateImage: false
# The email OAuth 2 token (leave empty if not used)
emailOauth2Token: ''
Hooks: Hooks:
# Do we need to hook with multiverse for spawn checking? # Do we need to hook with multiverse for spawn checking?
multiverse: true multiverse: true

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.command; package fr.xephi.authme.command;
import fr.xephi.authme.permission.PermissionsManager;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -7,20 +8,27 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor; import org.mockito.Captor;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.util.Collections;
import java.util.List; import java.util.List;
import static fr.xephi.authme.command.FoundResultStatus.INCORRECT_ARGUMENTS;
import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND;
import static fr.xephi.authme.command.FoundResultStatus.NO_PERMISSION; import static fr.xephi.authme.command.FoundResultStatus.NO_PERMISSION;
import static fr.xephi.authme.command.FoundResultStatus.SUCCESS; import static fr.xephi.authme.command.FoundResultStatus.SUCCESS;
import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyListOf; import static org.mockito.Matchers.anyListOf;
import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
/** /**
@ -89,7 +97,138 @@ public class CommandHandlerTest {
assertThat(captor.getValue(), contains("unreg", "testPlayer")); assertThat(captor.getValue(), contains("unreg", "testPlayer"));
verify(command, never()).getExecutableCommand(); verify(command, never()).getExecutableCommand();
verify(serviceMock).outputMappingError(eq(sender), any(FoundCommandResult.class)); ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender).sendMessage(captor.capture());
assertThat(captor.getValue(), containsString("don't have permission"));
}
@Test
public void shouldNotCallExecutableForWrongArguments() {
// given
String bukkitLabel = "unreg";
String[] bukkitArgs = {"testPlayer"};
CommandSender sender = mock(CommandSender.class);
CommandDescription command = mock(CommandDescription.class);
given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn(
new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.0, INCORRECT_ARGUMENTS));
PermissionsManager permissionsManager = mock(PermissionsManager.class);
given(permissionsManager.hasPermission(sender, command)).willReturn(true);
given(serviceMock.getPermissionsManager()).willReturn(permissionsManager);
// when
handler.processCommand(sender, bukkitLabel, bukkitArgs);
// then
verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture());
assertThat(captor.getValue(), contains("unreg", "testPlayer"));
verify(command, never()).getExecutableCommand();
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender, atLeastOnce()).sendMessage(captor.capture());
assertThat(captor.getAllValues().get(0), containsString("Incorrect command arguments"));
}
@Test
public void shouldNotCallExecutableForWrongArgumentsAndPermissionDenied() {
// given
String bukkitLabel = "unreg";
String[] bukkitArgs = {"testPlayer"};
CommandSender sender = mock(CommandSender.class);
CommandDescription command = mock(CommandDescription.class);
given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn(
new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.0, INCORRECT_ARGUMENTS));
PermissionsManager permissionsManager = mock(PermissionsManager.class);
given(permissionsManager.hasPermission(sender, command)).willReturn(false);
given(serviceMock.getPermissionsManager()).willReturn(permissionsManager);
// when
handler.processCommand(sender, bukkitLabel, bukkitArgs);
// then
verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture());
assertThat(captor.getValue(), contains("unreg", "testPlayer"));
verify(command, never()).getExecutableCommand();
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender).sendMessage(captor.capture());
assertThat(captor.getValue(), containsString("You don't have permission"));
}
@Test
public void shouldNotCallExecutableForFailedParsing() {
// given
String bukkitLabel = "unreg";
String[] bukkitArgs = {"testPlayer"};
CommandSender sender = mock(CommandSender.class);
CommandDescription command = mock(CommandDescription.class);
given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn(
new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.0, MISSING_BASE_COMMAND));
// when
handler.processCommand(sender, bukkitLabel, bukkitArgs);
// then
verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture());
assertThat(captor.getValue(), contains("unreg", "testPlayer"));
verify(command, never()).getExecutableCommand();
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender).sendMessage(captor.capture());
assertThat(captor.getValue(), containsString("Failed to parse"));
}
@Test
public void shouldNotCallExecutableForUnknownLabelAndHaveSuggestion() {
// given
String bukkitLabel = "unreg";
String[] bukkitArgs = {"testPlayer"};
CommandSender sender = mock(CommandSender.class);
CommandDescription command = mock(CommandDescription.class);
given(command.getLabels()).willReturn(Collections.singletonList("test_cmd"));
given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn(
new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.01, UNKNOWN_LABEL));
// when
handler.processCommand(sender, bukkitLabel, bukkitArgs);
// then
verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture());
assertThat(captor.getValue(), contains("unreg", "testPlayer"));
verify(command, never()).getExecutableCommand();
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender, times(3)).sendMessage(captor.capture());
assertThat(captor.getAllValues().get(0), containsString("Unknown command"));
assertThat(captor.getAllValues().get(1), containsString("Did you mean"));
assertThat(captor.getAllValues().get(1), containsString("/test_cmd"));
assertThat(captor.getAllValues().get(2), containsString("Use the command"));
assertThat(captor.getAllValues().get(2), containsString("to view help"));
}
@Test
public void shouldNotCallExecutableForUnknownLabelAndNotSuggestCommand() {
// given
String bukkitLabel = "unreg";
String[] bukkitArgs = {"testPlayer"};
CommandSender sender = mock(CommandSender.class);
CommandDescription command = mock(CommandDescription.class);
given(command.getLabels()).willReturn(Collections.singletonList("test_cmd"));
given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn(
new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 1.0, UNKNOWN_LABEL));
// when
handler.processCommand(sender, bukkitLabel, bukkitArgs);
// then
verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture());
assertThat(captor.getValue(), contains("unreg", "testPlayer"));
verify(command, never()).getExecutableCommand();
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender, times(2)).sendMessage(captor.capture());
assertThat(captor.getAllValues().get(0), containsString("Unknown command"));
assertThat(captor.getAllValues().get(1), containsString("Use the command"));
assertThat(captor.getAllValues().get(1), containsString("to view help"));
} }
@Test @Test

View File

@ -1,7 +1,5 @@
package fr.xephi.authme.command; package fr.xephi.authme.command;
import fr.xephi.authme.command.help.HelpProvider;
import fr.xephi.authme.output.Messages;
import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsManager;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.junit.Before; import org.junit.Before;
@ -43,7 +41,7 @@ public class CommandMapperTest {
@Before @Before
public void setUpMocks() { public void setUpMocks() {
permissionsManagerMock = mock(PermissionsManager.class); permissionsManagerMock = mock(PermissionsManager.class);
mapper = new CommandMapper(commands, mock(Messages.class), permissionsManagerMock, mock(HelpProvider.class)); mapper = new CommandMapper(commands, permissionsManagerMock);
} }
// ----------- // -----------
@ -256,4 +254,23 @@ public class CommandMapperTest {
assertThat(result.getDifference(), equalTo(0.0)); assertThat(result.getDifference(), equalTo(0.0));
} }
@Test
public void shouldRecognizeMissingPermissionForCommand() {
// given
List<String> parts = Arrays.asList("authme", "login", "test1");
CommandSender sender = mock(CommandSender.class);
given(permissionsManagerMock.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(false);
// when
FoundCommandResult result = mapper.mapPartsToCommand(sender, parts);
// then
assertThat(result.getCommandDescription(), equalTo(getCommandWithLabel(commands, "authme", "login")));
assertThat(result.getResultStatus(), equalTo(FoundResultStatus.NO_PERMISSION));
assertThat(result.getArguments(), contains("test1"));
assertThat(result.getDifference(), equalTo(0.0));
assertThat(result.getLabels(), equalTo(parts.subList(0, 2)));
assertThat(result.getArguments(), contains(parts.get(2)));
}
} }

View File

@ -8,6 +8,9 @@ import fr.xephi.authme.output.Messages;
import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.Management; import fr.xephi.authme.process.Management;
import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.settings.custom.NewSetting;
import fr.xephi.authme.settings.custom.SecuritySettings;
import fr.xephi.authme.settings.domain.Property;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.junit.Before; import org.junit.Before;
@ -36,6 +39,8 @@ public class CommandServiceTest {
private Messages messages; private Messages messages;
private PasswordSecurity passwordSecurity; private PasswordSecurity passwordSecurity;
private CommandService commandService; private CommandService commandService;
private PermissionsManager permissionsManager;
private NewSetting settings;
@Before @Before
public void setUpService() { public void setUpService() {
@ -44,7 +49,10 @@ public class CommandServiceTest {
helpProvider = mock(HelpProvider.class); helpProvider = mock(HelpProvider.class);
messages = mock(Messages.class); messages = mock(Messages.class);
passwordSecurity = mock(PasswordSecurity.class); passwordSecurity = mock(PasswordSecurity.class);
commandService = new CommandService(authMe, commandMapper, helpProvider, messages, passwordSecurity); permissionsManager = mock(PermissionsManager.class);
settings = mock(NewSetting.class);
commandService = new CommandService(
authMe, commandMapper, helpProvider, messages, passwordSecurity, permissionsManager, settings);
} }
@Test @Test
@ -87,19 +95,6 @@ public class CommandServiceTest {
verify(commandMapper).mapPartsToCommand(sender, commandParts); verify(commandMapper).mapPartsToCommand(sender, commandParts);
} }
@Test
public void shouldOutputMappingError() {
// given
CommandSender sender = mock(CommandSender.class);
FoundCommandResult result = mock(FoundCommandResult.class);
// when
commandService.outputMappingError(sender, result);
// then
verify(commandMapper).outputStandardError(sender, result);
}
@Test @Test
@Ignore @Ignore
public void shouldRunTaskInAsync() { public void shouldRunTaskInAsync() {
@ -169,16 +164,11 @@ public class CommandServiceTest {
@Test @Test
public void shouldReturnPermissionsManager() { public void shouldReturnPermissionsManager() {
// given // given / when
PermissionsManager manager = mock(PermissionsManager.class);
given(authMe.getPermissionsManager()).willReturn(manager);
// when
PermissionsManager result = commandService.getPermissionsManager(); PermissionsManager result = commandService.getPermissionsManager();
// then // then
assertThat(result, equalTo(manager)); assertThat(result, equalTo(permissionsManager));
verify(authMe).getPermissionsManager();
} }
@Test @Test
@ -195,4 +185,18 @@ public class CommandServiceTest {
assertThat(result, equalTo(givenMessages)); assertThat(result, equalTo(givenMessages));
verify(messages).retrieve(key); verify(messages).retrieve(key);
} }
@Test
public void shouldRetrieveProperty() {
// given
Property<Integer> property = SecuritySettings.CAPTCHA_LENGTH;
given(settings.getProperty(property)).willReturn(7);
// when
int result = settings.getProperty(property);
// then
assertThat(result, equalTo(7));
verify(settings).getProperty(property);
}
} }

View File

@ -5,13 +5,12 @@ import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.output.Messages; import fr.xephi.authme.output.Messages;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.custom.SecuritySettings;
import fr.xephi.authme.util.WrapperMock; import fr.xephi.authme.util.WrapperMock;
import org.bukkit.command.BlockCommandSender; import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
@ -20,6 +19,7 @@ import java.util.Collections;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -35,8 +35,8 @@ public class CaptchaCommandTest {
@Before @Before
public void setUpWrapperMock() { public void setUpWrapperMock() {
wrapperMock = WrapperMock.createInstance(); wrapperMock = WrapperMock.createInstance();
Settings.useCaptcha = true;
commandService = mock(CommandService.class); commandService = mock(CommandService.class);
given(commandService.getProperty(SecuritySettings.USE_CAPTCHA)).willReturn(true);
} }
@Test @Test
@ -46,7 +46,7 @@ public class CaptchaCommandTest {
ExecutableCommand command = new CaptchaCommand(); ExecutableCommand command = new CaptchaCommand();
// when // when
command.executeCommand(sender, new ArrayList<String>(), mock(CommandService.class)); command.executeCommand(sender, new ArrayList<String>(), commandService);
// then // then
assertThat(wrapperMock.wasMockCalled(AuthMe.class), equalTo(false)); assertThat(wrapperMock.wasMockCalled(AuthMe.class), equalTo(false));
@ -54,14 +54,14 @@ public class CaptchaCommandTest {
} }
@Test @Test
@Ignore
public void shouldRejectIfCaptchaIsNotUsed() { public void shouldRejectIfCaptchaIsNotUsed() {
// given // given
Player player = mockPlayerWithName("testplayer"); Player player = mockPlayerWithName("testplayer");
ExecutableCommand command = new CaptchaCommand(); ExecutableCommand command = new CaptchaCommand();
given(commandService.getProperty(SecuritySettings.USE_CAPTCHA)).willReturn(false);
// when // when
command.executeCommand(player, Collections.singletonList("1234"), mock(CommandService.class)); command.executeCommand(player, Collections.singletonList("1234"), commandService);
// then // then
verify(commandService).send(player, MessageKey.USAGE_LOGIN); verify(commandService).send(player, MessageKey.USAGE_LOGIN);

View File

@ -4,7 +4,8 @@ import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.custom.RestrictionSettings;
import fr.xephi.authme.settings.custom.SecuritySettings;
import fr.xephi.authme.task.ChangePasswordTask; import fr.xephi.authme.task.ChangePasswordTask;
import fr.xephi.authme.util.WrapperMock; import fr.xephi.authme.util.WrapperMock;
import org.bukkit.Server; import org.bukkit.Server;
@ -19,9 +20,9 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import static java.util.Arrays.asList;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq; import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -44,11 +45,11 @@ public class ChangePasswordCommandTest {
cacheMock = wrapperMock.getPlayerCache(); cacheMock = wrapperMock.getPlayerCache();
commandService = mock(CommandService.class); commandService = mock(CommandService.class);
when(commandService.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)).thenReturn(2);
when(commandService.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)).thenReturn(50);
// Only allow passwords with alphanumerical characters for the test // Only allow passwords with alphanumerical characters for the test
Settings.getPassRegex = "[a-zA-Z0-9]+"; when(commandService.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX)).thenReturn("[a-zA-Z0-9]+");
Settings.getPasswordMinLen = 2; when(commandService.getProperty(SecuritySettings.UNSAFE_PASSWORDS)).thenReturn(Collections.EMPTY_LIST);
Settings.passwordMaxLength = 50;
Settings.unsafePasswords = Collections.EMPTY_LIST;
} }
@Test @Test
@ -112,7 +113,7 @@ public class ChangePasswordCommandTest {
// given // given
CommandSender sender = initPlayerWithName("abc12", true); CommandSender sender = initPlayerWithName("abc12", true);
ChangePasswordCommand command = new ChangePasswordCommand(); ChangePasswordCommand command = new ChangePasswordCommand();
Settings.passwordMaxLength = 3; given(commandService.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)).willReturn(3);
// when // when
command.executeCommand(sender, Arrays.asList("12", "test"), commandService); command.executeCommand(sender, Arrays.asList("12", "test"), commandService);
@ -127,7 +128,7 @@ public class ChangePasswordCommandTest {
// given // given
CommandSender sender = initPlayerWithName("abc12", true); CommandSender sender = initPlayerWithName("abc12", true);
ChangePasswordCommand command = new ChangePasswordCommand(); ChangePasswordCommand command = new ChangePasswordCommand();
Settings.getPasswordMinLen = 7; given(commandService.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)).willReturn(7);
// when // when
command.executeCommand(sender, Arrays.asList("oldverylongpassword", "tester"), commandService); command.executeCommand(sender, Arrays.asList("oldverylongpassword", "tester"), commandService);
@ -142,7 +143,8 @@ public class ChangePasswordCommandTest {
// given // given
CommandSender sender = initPlayerWithName("player", true); CommandSender sender = initPlayerWithName("player", true);
ChangePasswordCommand command = new ChangePasswordCommand(); ChangePasswordCommand command = new ChangePasswordCommand();
Settings.unsafePasswords = asList("test", "abc123"); given(commandService.getProperty(SecuritySettings.UNSAFE_PASSWORDS))
.willReturn(Arrays.asList("test", "abc123"));
// when // when
command.executeCommand(sender, Arrays.asList("oldpw", "abc123"), commandService); command.executeCommand(sender, Arrays.asList("oldpw", "abc123"), commandService);

View File

@ -7,6 +7,7 @@ import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.security.crypts.JOOMLA; import fr.xephi.authme.security.crypts.JOOMLA;
import fr.xephi.authme.security.crypts.PHPBB; import fr.xephi.authme.security.crypts.PHPBB;
import fr.xephi.authme.util.WrapperMock;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.junit.Before; import org.junit.Before;
@ -42,6 +43,7 @@ public class PasswordSecurityTest {
@Before @Before
public void setUpMocks() { public void setUpMocks() {
WrapperMock.createInstance();
pluginManager = mock(PluginManager.class); pluginManager = mock(PluginManager.class);
dataSource = mock(DataSource.class); dataSource = mock(DataSource.class);
method = mock(EncryptionMethod.class); method = mock(EncryptionMethod.class);
@ -209,7 +211,7 @@ public class PasswordSecurityTest {
HashedPassword hashedPassword = new HashedPassword("~T!est#Hash"); HashedPassword hashedPassword = new HashedPassword("~T!est#Hash");
given(method.computeHash(password, username)).willReturn(hashedPassword); given(method.computeHash(password, username)).willReturn(hashedPassword);
given(method.hasSeparateSalt()).willReturn(true); given(method.hasSeparateSalt()).willReturn(true);
PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.XAUTH, pluginManager, true); PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.XAUTH, pluginManager, false);
// when // when
boolean result = security.comparePassword(password, hashedPassword, username); boolean result = security.comparePassword(password, hashedPassword, username);

View File

@ -26,8 +26,8 @@ public abstract class AbstractEncryptionMethodTest {
*/ */
public static final String[] GIVEN_PASSWORDS = {"password", "PassWord1", "&^%te$t?Pw@_", "âË_3(íù*"}; public static final String[] GIVEN_PASSWORDS = {"password", "PassWord1", "&^%te$t?Pw@_", "âË_3(íù*"};
/** /**
* List of passwords that are hashed at runtime and then tested against; this verifies that hashes that are * List of passwords that are hashed at runtime and then tested against; this verifies that newly generated hashes
* generated are valid. * are valid.
*/ */
private static final List<String> INTERNAL_PASSWORDS = private static final List<String> INTERNAL_PASSWORDS =
ImmutableList.of("test1234", "Ab_C73", "(!#&$~`_-Aa0", "Ûïé1&?+A"); ImmutableList.of("test1234", "Ab_C73", "(!#&$~`_-Aa0", "Ûïé1&?+A");

View File

@ -1,20 +1,16 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import org.junit.Ignore;
/** /**
* Test for {@link WBB4}. * Test for {@link WBB4}.
*/ */
@Ignore
// TODO #369: Fix WBB4 hash and un-ignore this test
public class WBB4Test extends AbstractEncryptionMethodTest { public class WBB4Test extends AbstractEncryptionMethodTest {
public WBB4Test() { public WBB4Test() {
super(new WBB4(), super(new WBB4(),
"$2a$08$GktrHRoOk0EHrl3ONsFmieIbjq7EIzBx8dhsWiCmn6sWwO3b3DoRO", // password "$2a$08$7DGr.wROqEPe0Z3XJS7n5.k.QWehovLHbpI.UkdfRb4ns268WsR6C", // password
"$2a$08$ouvtovnHgPWz6YHuOhyct.I2/j1xTOLG8OTuEn1/YqtkiRJYUV7lq", // PassWord1 "$2a$08$yWWVUA4PB4mqW.0wyIvV3OdoH492HuLk5L3iaqUrpRK2.2zn08d/K", // PassWord1
"$2a$08$z.qWFh7k0qvIu5.qiq/Wuu2HDCNH7LNlMDNhN61F1ISsV8wZRKD0.", // &^%te$t?Pw@_ "$2a$08$EHXUFt7bTT9Fnsu22KWvF.QDssiosV8YzH8CyWqulB/ckOA7qioJG", // &^%te$t?Pw@_
"$2a$08$OU8e9dncXyz8UP5Z.gWP8Os1IK89pspCS4FPzj8hBjgCWmjbLVcO2"); // âË_3(íù* "$2a$08$ZZu5YH4zwpk0cr2dOYZpF.CkTKMvCBOAtTbAH7AwnOiL.n0mWkgDC"); // âË_3(íù*
} }
} }

View File

@ -0,0 +1,92 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.configuration.MemorySection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.fail;
/**
* Test for {@link NewSetting} and the project's config.yml,
* verifying that no settings are missing from the file.
*/
public class ConfigFileConsistencyTest {
/** The file name of the project's sample config file. */
private static final String CONFIG_FILE = "/config.yml";
@Test
public void shouldHaveAllConfigs() throws IOException {
// given
URL url = this.getClass().getResource(CONFIG_FILE);
File configFile = new File(url.getFile());
NewSetting settings = new NewSetting(YamlConfiguration.loadConfiguration(configFile), new File("bogus"), null);
// when
boolean result = settings.containsAllSettings(SettingsFieldRetriever.getAllPropertyFields());
// then
if (!result) {
FileConfiguration configuration =
(FileConfiguration) ReflectionTestUtils.getFieldValue(NewSetting.class, settings, "configuration");
Set<String> knownProperties = getAllKnownPropertyPaths();
List<String> missingProperties = new ArrayList<>();
for (String path : knownProperties) {
if (!configuration.contains(path)) {
missingProperties.add(path);
}
}
fail("Found missing properties!\n-" + StringUtils.join("\n-", missingProperties));
}
}
@Test
public void shouldNotHaveUnknownConfigs() {
// given
URL url = this.getClass().getResource(CONFIG_FILE);
File configFile = new File(url.getFile());
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(configFile);
Map<String, Object> allReadProperties = configuration.getValues(true);
Set<String> knownKeys = getAllKnownPropertyPaths();
// when
List<String> unknownPaths = new ArrayList<>();
for (Map.Entry<String, Object> entry : allReadProperties.entrySet()) {
// The value being a MemorySection means it's a parent node
if (!(entry.getValue() instanceof MemorySection) && !knownKeys.contains(entry.getKey())) {
unknownPaths.add(entry.getKey());
}
}
// then
if (!unknownPaths.isEmpty()) {
fail("Found " + unknownPaths.size() + " unknown property paths in the project's config.yml: \n- "
+ StringUtils.join("\n- ", unknownPaths));
}
}
private static Set<String> getAllKnownPropertyPaths() {
PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields();
Set<String> paths = new HashSet<>(propertyMap.size());
for (Property<?> property : propertyMap.keySet()) {
paths.add(property.getPath());
}
return paths;
}
}

View File

@ -0,0 +1,115 @@
package fr.xephi.authme.settings.custom;
import com.google.common.collect.ImmutableMap;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assume.assumeThat;
/**
* Integration test for {@link NewSetting}.
*/
public class NewSettingIntegrationTest {
/** File name of the sample config including all {@link TestConfiguration} values. */
private static final String COMPLETE_FILE = "config-sample-values.yml";
/** File name of the sample config missing certain {@link TestConfiguration} values. */
private static final String INCOMPLETE_FILE = "config-incomplete-sample.yml";
private static PropertyMap propertyMap;
@BeforeClass
public static void generatePropertyMap() {
propertyMap = new PropertyMap();
for (Field field : TestConfiguration.class.getDeclaredFields()) {
Object fieldValue = ReflectionTestUtils.getFieldValue(TestConfiguration.class, null, field.getName());
if (fieldValue instanceof Property<?>) {
Property<?> property = (Property<?>) fieldValue;
String[] comments = new String[]{"Comment for '" + property.getPath() + "'"};
propertyMap.put(property, comments);
}
}
}
@Test
public void shouldLoadAndReadAllProperties() {
// given
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(getConfigFile(COMPLETE_FILE));
File file = new File("unused");
assumeThat(file.exists(), equalTo(false));
// when / then
NewSetting settings = new NewSetting(configuration, file, propertyMap);
Map<Property<?>, Object> expectedValues = ImmutableMap.<Property<?>, Object>builder()
.put(TestConfiguration.DURATION_IN_SECONDS, 22)
.put(TestConfiguration.SYSTEM_NAME, "Custom sys name")
.put(TestConfiguration.RATIO_LIMIT, -4.1)
.put(TestConfiguration.RATIO_FIELDS, Arrays.asList("Australia", "Burundi", "Colombia"))
.put(TestConfiguration.VERSION_NUMBER, 2492)
.put(TestConfiguration.SKIP_BORING_FEATURES, false)
.put(TestConfiguration.BORING_COLORS, Arrays.asList("beige", "gray"))
.put(TestConfiguration.DUST_LEVEL, 0.81)
.put(TestConfiguration.USE_COOL_FEATURES, true)
.put(TestConfiguration.COOL_OPTIONS, Arrays.asList("Dinosaurs", "Explosions", "Big trucks"))
.build();
for (Map.Entry<Property<?>, Object> entry : expectedValues.entrySet()) {
assertThat("Property '" + entry.getKey().getPath() + "' has expected value",
settings.getProperty(entry.getKey()), equalTo(entry.getValue()));
}
assertThat(file.exists(), equalTo(false));
}
@Test
public void shouldWriteMissingProperties() {
// given/when
File file = getConfigFile(INCOMPLETE_FILE);
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file);
assumeThat(configuration.contains(TestConfiguration.BORING_COLORS.getPath()), equalTo(false));
// Expectation: File is rewritten to since it does not have all configurations
new NewSetting(configuration, file, propertyMap);
// Load the settings again -> checks that what we wrote can be loaded again
configuration = YamlConfiguration.loadConfiguration(file);
// then
NewSetting settings = new NewSetting(configuration, file, propertyMap);
Map<Property<?>, Object> expectedValues = ImmutableMap.<Property<?>, Object>builder()
.put(TestConfiguration.DURATION_IN_SECONDS, 22)
.put(TestConfiguration.SYSTEM_NAME, "[TestDefaultValue]")
.put(TestConfiguration.RATIO_LIMIT, 3.0)
.put(TestConfiguration.RATIO_FIELDS, Arrays.asList("Australia", "Burundi", "Colombia"))
.put(TestConfiguration.VERSION_NUMBER, 32046)
.put(TestConfiguration.SKIP_BORING_FEATURES, false)
.put(TestConfiguration.BORING_COLORS, Collections.EMPTY_LIST)
.put(TestConfiguration.DUST_LEVEL, 0.2)
.put(TestConfiguration.USE_COOL_FEATURES, false)
.put(TestConfiguration.COOL_OPTIONS, Arrays.asList("Dinosaurs", "Explosions", "Big trucks"))
.build();
for (Map.Entry<Property<?>, Object> entry : expectedValues.entrySet()) {
assertThat("Property '" + entry.getKey().getPath() + "' has expected value",
settings.getProperty(entry.getKey()), equalTo(entry.getValue()));
}
}
private File getConfigFile(String file) {
URL url = getClass().getClassLoader().getResource(file);
if (url == null) {
throw new IllegalStateException("File '" + file + "' could not be loaded");
}
return new File(url.getFile());
}
}

View File

@ -0,0 +1,84 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Property;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.File;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyDouble;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link NewSetting}.
*/
public class NewSettingTest {
@Test
public void shouldLoadAllConfigs() {
// given
YamlConfiguration file = mock(YamlConfiguration.class);
given(file.getString(anyString(), anyString())).willAnswer(withDefaultArgument());
given(file.getBoolean(anyString(), anyBoolean())).willAnswer(withDefaultArgument());
given(file.getDouble(anyString(), anyDouble())).willAnswer(withDefaultArgument());
given(file.getInt(anyString(), anyInt())).willAnswer(withDefaultArgument());
setReturnValue(file, TestConfiguration.VERSION_NUMBER, 20);
setReturnValue(file, TestConfiguration.SKIP_BORING_FEATURES, true);
setReturnValue(file, TestConfiguration.RATIO_LIMIT, 4.25);
setReturnValue(file, TestConfiguration.SYSTEM_NAME, "myTestSys");
// when / then
NewSetting settings = new NewSetting(file, new File("conf.txt"), null);
assertThat(settings.getProperty(TestConfiguration.VERSION_NUMBER), equalTo(20));
assertThat(settings.getProperty(TestConfiguration.SKIP_BORING_FEATURES), equalTo(true));
assertThat(settings.getProperty(TestConfiguration.RATIO_LIMIT), equalTo(4.25));
assertThat(settings.getProperty(TestConfiguration.SYSTEM_NAME), equalTo("myTestSys"));
assertDefaultValue(TestConfiguration.DURATION_IN_SECONDS, settings);
assertDefaultValue(TestConfiguration.DUST_LEVEL, settings);
assertDefaultValue(TestConfiguration.COOL_OPTIONS, settings);
}
private static <T> void setReturnValue(YamlConfiguration config, Property<T> property, T value) {
if (value instanceof String) {
when(config.getString(eq(property.getPath()), anyString())).thenReturn((String) value);
} else if (value instanceof Integer) {
when(config.getInt(eq(property.getPath()), anyInt())).thenReturn((Integer) value);
} else if (value instanceof Boolean) {
when(config.getBoolean(eq(property.getPath()), anyBoolean())).thenReturn((Boolean) value);
} else if (value instanceof Double) {
when(config.getDouble(eq(property.getPath()), anyDouble())).thenReturn((Double) value);
} else {
throw new UnsupportedOperationException("Value has unsupported type '"
+ (value == null ? "null" : value.getClass().getSimpleName()) + "'");
}
}
private static void assertDefaultValue(Property<?> property, NewSetting setting) {
assertThat(property.getPath() + " has default value",
setting.getProperty(property).equals(property.getDefaultValue()), equalTo(true));
}
private static <T> Answer<T> withDefaultArgument() {
return new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
// Return the second parameter -> the default
return (T) invocation.getArguments()[1];
}
};
}
}

View File

@ -0,0 +1,119 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* Test for {@link SettingsClass} implementations.
*/
public class SettingsClassConsistencyTest {
private static final String SETTINGS_FOLDER = "src/main/java/fr/xephi/authme/settings/custom";
private static List<Class<? extends SettingsClass>> classes;
@BeforeClass
public static void scanForSettingsClasses() {
File settingsFolder = new File(SETTINGS_FOLDER);
File[] filesInFolder = settingsFolder.listFiles();
if (filesInFolder == null || filesInFolder.length == 0) {
throw new IllegalStateException("Could not read folder '" + SETTINGS_FOLDER + "'. Is it correct?");
}
classes = new ArrayList<>();
for (File file : filesInFolder) {
Class<? extends SettingsClass> clazz = getSettingsClassFromFile(file);
if (clazz != null) {
classes.add(clazz);
}
}
System.out.println("Found " + classes.size() + " SettingsClass implementations");
}
/**
* Make sure that all {@link Property} instances we define are in public, static, final fields.
*/
@Test
public void shouldHavePublicStaticFinalFields() {
for (Class<?> clazz : classes) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (Property.class.isAssignableFrom(field.getType())) {
String fieldName = "Field " + clazz.getSimpleName() + "#" + field.getName();
assertThat(fieldName + " should be public, static, and final",
isValidConstantField(field), equalTo(true));
}
}
}
}
/**
* Make sure that no properties use the same path.
*/
@Test
public void shouldHaveUniquePaths() {
Set<String> paths = new HashSet<>();
for (Class<?> clazz : classes) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (Property.class.isAssignableFrom(field.getType())) {
Property<?> property =
(Property<?>) ReflectionTestUtils.getFieldValue(clazz, null, field.getName());
if (paths.contains(property.getPath())) {
fail("Path '" + property.getPath() + "' should be used by only one constant");
}
paths.add(property.getPath());
}
}
}
}
@Test
public void shouldHaveHiddenDefaultConstructorOnly() {
for (Class<?> clazz : classes) {
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
assertThat(clazz + " should only have one constructor",
constructors, arrayWithSize(1));
assertThat("Constructor of " + clazz + " is private",
Modifier.isPrivate(constructors[0].getModifiers()), equalTo(true));
}
}
private static boolean isValidConstantField(Field field) {
int modifiers = field.getModifiers();
return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers);
}
private static Class<? extends SettingsClass> getSettingsClassFromFile(File file) {
String fileName = file.getPath();
String className = fileName
.substring("src/main/java/".length(), fileName.length() - ".java".length())
.replace(File.separator, ".");
try {
Class<?> clazz = SettingsClassConsistencyTest.class.getClassLoader().loadClass(className);
if (SettingsClass.class.isAssignableFrom(clazz)) {
return (Class<? extends SettingsClass>) clazz;
}
return null;
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Could not load class '" + className + "'", e);
}
}
}

View File

@ -0,0 +1,46 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.PropertyType;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
/**
* Sample properties for testing purposes.
*/
class TestConfiguration implements SettingsClass {
public static final Property<Integer> DURATION_IN_SECONDS =
newProperty("test.duration", 4);
public static final Property<String> SYSTEM_NAME =
newProperty("test.systemName", "[TestDefaultValue]");
public static final Property<Double> RATIO_LIMIT =
newProperty(PropertyType.DOUBLE, "sample.ratio.limit", 3.0);
public static final Property<List<String>> RATIO_FIELDS =
newProperty(PropertyType.STRING_LIST, "sample.ratio.fields", "a", "b", "c");
public static final Property<Integer> VERSION_NUMBER =
newProperty("version", 32046);
public static final Property<Boolean> SKIP_BORING_FEATURES =
newProperty("features.boring.skip", false);
public static final Property<List<String>> BORING_COLORS =
newProperty(PropertyType.STRING_LIST, "features.boring.colors");
public static final Property<Double> DUST_LEVEL =
newProperty(PropertyType.DOUBLE, "features.boring.dustLevel", 0.2);
public static final Property<Boolean> USE_COOL_FEATURES =
newProperty("features.cool.enabled", false);
public static final Property<List<String>> COOL_OPTIONS =
newProperty(PropertyType.STRING_LIST, "features.cool.options", "Sparks", "Sprinkles");
}

View File

@ -0,0 +1,118 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Test for {@link EnumPropertyType}.
*/
public class EnumPropertyTypeTest {
@Test
public void shouldReturnCorrectEnumValue() {
// given
PropertyType<TestEnum> propertyType = new EnumPropertyType<>(TestEnum.class);
Property<TestEnum> property = Property.newProperty(TestEnum.class, "enum.path", TestEnum.ENTRY_C);
YamlConfiguration configuration = mock(YamlConfiguration.class);
given(configuration.getString(property.getPath())).willReturn("Entry_B");
// when
TestEnum result = propertyType.getFromFile(property, configuration);
// then
assertThat(result, equalTo(TestEnum.ENTRY_B));
}
@Test
public void shouldFallBackToDefaultForInvalidValue() {
// given
PropertyType<TestEnum> propertyType = new EnumPropertyType<>(TestEnum.class);
Property<TestEnum> property = Property.newProperty(TestEnum.class, "enum.path", TestEnum.ENTRY_C);
YamlConfiguration configuration = mock(YamlConfiguration.class);
given(configuration.getString(property.getPath())).willReturn("Bogus");
// when
TestEnum result = propertyType.getFromFile(property, configuration);
// then
assertThat(result, equalTo(TestEnum.ENTRY_C));
}
@Test
public void shouldFallBackToDefaultForNonExistentValue() {
// given
PropertyType<TestEnum> propertyType = new EnumPropertyType<>(TestEnum.class);
Property<TestEnum> property = Property.newProperty(TestEnum.class, "enum.path", TestEnum.ENTRY_C);
YamlConfiguration configuration = mock(YamlConfiguration.class);
given(configuration.getString(property.getPath())).willReturn(null);
// when
TestEnum result = propertyType.getFromFile(property, configuration);
// then
assertThat(result, equalTo(TestEnum.ENTRY_C));
}
@Test
public void shouldReturnTrueForContainsCheck() {
// given
PropertyType<TestEnum> propertyType = new EnumPropertyType<>(TestEnum.class);
Property<TestEnum> property = Property.newProperty(TestEnum.class, "my.test.path", TestEnum.ENTRY_C);
YamlConfiguration configuration = mock(YamlConfiguration.class);
given(configuration.contains(property.getPath())).willReturn(true);
given(configuration.getString(property.getPath())).willReturn("ENTRY_B");
// when
boolean result = propertyType.contains(property, configuration);
// then
assertThat(result, equalTo(true));
}
@Test
public void shouldReturnFalseForFileWithoutConfig() {
// given
PropertyType<TestEnum> propertyType = new EnumPropertyType<>(TestEnum.class);
Property<TestEnum> property = Property.newProperty(TestEnum.class, "my.test.path", TestEnum.ENTRY_C);
YamlConfiguration configuration = mock(YamlConfiguration.class);
given(configuration.contains(property.getPath())).willReturn(false);
// when
boolean result = propertyType.contains(property, configuration);
// then
assertThat(result, equalTo(false));
}
@Test
public void shouldReturnFalseForUnknownValue() {
// given
PropertyType<TestEnum> propertyType = new EnumPropertyType<>(TestEnum.class);
Property<TestEnum> property = Property.newProperty(TestEnum.class, "my.test.path", TestEnum.ENTRY_C);
YamlConfiguration configuration = mock(YamlConfiguration.class);
given(configuration.contains(property.getPath())).willReturn(true);
given(configuration.getString(property.getPath())).willReturn("wrong value");
// when
boolean result = propertyType.contains(property, configuration);
// then
assertThat(result, equalTo(false));
}
private enum TestEnum {
ENTRY_A,
ENTRY_B,
ENTRY_C
}
}

View File

@ -0,0 +1,182 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Arrays;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyDouble;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link PropertyType} and the contained subtypes.
*/
public class PropertyTypeTest {
private static YamlConfiguration configuration;
@BeforeClass
public static void setUpYamlConfigurationMock() {
configuration = mock(YamlConfiguration.class);
when(configuration.getBoolean(eq("bool.path.test"), anyBoolean())).thenReturn(true);
when(configuration.getBoolean(eq("bool.path.wrong"), anyBoolean())).thenAnswer(secondParameter());
when(configuration.getDouble(eq("double.path.test"), anyDouble())).thenReturn(-6.4);
when(configuration.getDouble(eq("double.path.wrong"), anyDouble())).thenAnswer(secondParameter());
when(configuration.getInt(eq("int.path.test"), anyInt())).thenReturn(27);
when(configuration.getInt(eq("int.path.wrong"), anyInt())).thenAnswer(secondParameter());
when(configuration.getString(eq("str.path.test"), anyString())).thenReturn("Test value");
when(configuration.getString(eq("str.path.wrong"), anyString())).thenAnswer(secondParameter());
when(configuration.isList("list.path.test")).thenReturn(true);
when(configuration.getStringList("list.path.test")).thenReturn(Arrays.asList("test1", "Test2", "3rd test"));
when(configuration.isList("list.path.wrong")).thenReturn(false);
}
/* Boolean */
@Test
public void shouldGetBoolValue() {
// given
Property<Boolean> property = Property.newProperty("bool.path.test", false);
// when
boolean result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(true));
}
@Test
public void shouldGetBoolDefault() {
// given
Property<Boolean> property = Property.newProperty("bool.path.wrong", true);
// when
boolean result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(true));
}
/* Double */
@Test
public void shouldGetDoubleValue() {
// given
Property<Double> property = Property.newProperty(PropertyType.DOUBLE, "double.path.test", 3.8);
// when
double result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(-6.4));
}
@Test
public void shouldGetDoubleDefault() {
// given
Property<Double> property = Property.newProperty(PropertyType.DOUBLE, "double.path.wrong", 12.0);
// when
double result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(12.0));
}
/* Integer */
@Test
public void shouldGetIntValue() {
// given
Property<Integer> property = Property.newProperty("int.path.test", 3);
// when
int result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(27));
}
@Test
public void shouldGetIntDefault() {
// given
Property<Integer> property = Property.newProperty("int.path.wrong", -10);
// when
int result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(-10));
}
/* String */
@Test
public void shouldGetStringValue() {
// given
Property<String> property = Property.newProperty("str.path.test", "unused default");
// when
String result = property.getFromFile(configuration);
// then
assertThat(result, equalTo("Test value"));
}
@Test
public void shouldGetStringDefault() {
// given
Property<String> property = Property.newProperty("str.path.wrong", "given default value");
// when
String result = property.getFromFile(configuration);
// then
assertThat(result, equalTo("given default value"));
}
/* String list */
@Test
public void shouldGetStringListValue() {
// given
Property<List<String>> property = Property.newProperty(PropertyType.STRING_LIST, "list.path.test", "1", "b");
// when
List<String> result = property.getFromFile(configuration);
// then
assertThat(result, contains("test1", "Test2", "3rd test"));
}
@Test
public void shouldGetStringListDefault() {
// given
Property<List<String>> property =
Property.newProperty(PropertyType.STRING_LIST, "list.path.wrong", "default", "list", "elements");
// when
List<String> result = property.getFromFile(configuration);
// then
assertThat(result, contains("default", "list", "elements"));
}
private static <T> Answer<T> secondParameter() {
return new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
// Return the second parameter -> the default
return (T) invocation.getArguments()[1];
}
};
}
}

View File

@ -0,0 +1,53 @@
package fr.xephi.authme.settings.propertymap;
import fr.xephi.authme.settings.domain.Property;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.hamcrest.Matchers.contains;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link PropertyMap}.
*/
public class PropertyMapTest {
@Test
public void shouldKeepEntriesByInsertionAndGroup() {
// given
List<String> paths = Arrays.asList("japan", "indonesia.jakarta", "japan.tokyo", "china.shanghai", "egypt.cairo",
"china.shenzhen", "china", "indonesia.jakarta.tugu", "egypt", "japan.nagoya", "japan.tokyo.taito");
PropertyMap map = new PropertyMap();
// when
for (String path : paths) {
Property<?> property = createPropertyWithPath(path);
map.put(property, new String[0]);
}
// then
Set<Map.Entry<Property<?>, String[]>> entrySet = map.entrySet();
List<String> resultPaths = new ArrayList<>(entrySet.size());
for (Map.Entry<Property<?>, String[]> entry : entrySet) {
resultPaths.add(entry.getKey().getPath());
}
Assert.assertThat(resultPaths, contains("japan", "japan.tokyo", "japan.tokyo.taito", "japan.nagoya",
"indonesia.jakarta", "indonesia.jakarta.tugu", "china", "china.shanghai", "china.shenzhen",
"egypt", "egypt.cairo"));
}
private static Property<?> createPropertyWithPath(String path) {
Property<?> property = mock(Property.class);
when(property.getPath()).thenReturn(path);
return property;
}
}

View File

@ -0,0 +1,27 @@
# Test config file with missing options from TestConfiguration
# Notice the commented out lines!
test:
duration: 22
# systemName: 'Custom sys name'
sample:
ratio:
# limit: 3.0
fields:
- 'Australia'
- 'Burundi'
- 'Colombia'
#version: 2492
features:
# boring:
# skip: false
# colors:
# - 'beige'
# - 'gray'
# dustLevel: 0.81
cool:
# enabled: true
options:
- 'Dinosaurs'
- 'Explosions'
- 'Big trucks'

View File

@ -0,0 +1,27 @@
# Test config file with all options
# defined in the TestConfiguration class
test:
duration: 22
systemName: 'Custom sys name'
sample:
ratio:
limit: -4.1
fields:
- 'Australia'
- 'Burundi'
- 'Colombia'
version: 2492
features:
boring:
skip: false
colors:
- 'beige'
- 'gray'
dustLevel: 0.81
cool:
enabled: true
options:
- 'Dinosaurs'
- 'Explosions'
- 'Big trucks'