223 lines
8.9 KiB
Java
223 lines
8.9 KiB
Java
package fr.xephi.authme.listener;
|
|
|
|
import fr.xephi.authme.ConsoleLogger;
|
|
import fr.xephi.authme.data.auth.PlayerAuth;
|
|
import fr.xephi.authme.datasource.DataSource;
|
|
import fr.xephi.authme.initialization.Reloadable;
|
|
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
|
import fr.xephi.authme.message.MessageKey;
|
|
import fr.xephi.authme.message.Messages;
|
|
import fr.xephi.authme.permission.PermissionsManager;
|
|
import fr.xephi.authme.permission.PlayerStatePermission;
|
|
import fr.xephi.authme.service.AntiBotService;
|
|
import fr.xephi.authme.service.BukkitService;
|
|
import fr.xephi.authme.service.ValidationService;
|
|
import fr.xephi.authme.settings.Settings;
|
|
import fr.xephi.authme.settings.properties.ProtectionSettings;
|
|
import fr.xephi.authme.settings.properties.RegistrationSettings;
|
|
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
|
import fr.xephi.authme.util.StringUtils;
|
|
import fr.xephi.authme.util.Utils;
|
|
import org.bukkit.Server;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.event.player.PlayerLoginEvent;
|
|
|
|
import javax.annotation.PostConstruct;
|
|
import javax.inject.Inject;
|
|
import java.util.Collection;
|
|
import java.util.Locale;
|
|
import java.util.regex.Pattern;
|
|
|
|
/**
|
|
* Service for performing various verifications when a player joins.
|
|
*/
|
|
public class OnJoinVerifier implements Reloadable {
|
|
|
|
private final ConsoleLogger logger = ConsoleLoggerFactory.get(OnJoinVerifier.class);
|
|
|
|
@Inject
|
|
private Settings settings;
|
|
@Inject
|
|
private DataSource dataSource;
|
|
@Inject
|
|
private Messages messages;
|
|
@Inject
|
|
private PermissionsManager permissionsManager;
|
|
@Inject
|
|
private AntiBotService antiBotService;
|
|
@Inject
|
|
private ValidationService validationService;
|
|
@Inject
|
|
private BukkitService bukkitService;
|
|
@Inject
|
|
private Server server;
|
|
|
|
private Pattern nicknamePattern;
|
|
|
|
OnJoinVerifier() {
|
|
}
|
|
|
|
|
|
@PostConstruct
|
|
@Override
|
|
public void reload() {
|
|
String nickRegEx = settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS);
|
|
nicknamePattern = Utils.safePatternCompile(nickRegEx);
|
|
}
|
|
|
|
/**
|
|
* Checks if Antibot is enabled.
|
|
*
|
|
* @param name the joining player name to check
|
|
* @param isAuthAvailable whether or not the player is registered
|
|
* @throws FailedVerificationException if the verification fails
|
|
*/
|
|
public void checkAntibot(String name, boolean isAuthAvailable) throws FailedVerificationException {
|
|
if (isAuthAvailable || permissionsManager.hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT)) {
|
|
return;
|
|
}
|
|
if (antiBotService.shouldKick()) {
|
|
antiBotService.addPlayerKick(name);
|
|
throw new FailedVerificationException(MessageKey.KICK_ANTIBOT);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks whether non-registered players should be kicked, and if so, whether the player should be kicked.
|
|
*
|
|
* @param isAuthAvailable whether or not the player is registered
|
|
* @throws FailedVerificationException if the verification fails
|
|
*/
|
|
public void checkKickNonRegistered(boolean isAuthAvailable) throws FailedVerificationException {
|
|
if (!isAuthAvailable && settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)) {
|
|
throw new FailedVerificationException(MessageKey.MUST_REGISTER_MESSAGE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks that the name adheres to the configured username restrictions.
|
|
*
|
|
* @param name the name to verify
|
|
* @throws FailedVerificationException if the verification fails
|
|
*/
|
|
public void checkIsValidName(String name) throws FailedVerificationException {
|
|
if (name.length() > settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH)
|
|
|| name.length() < settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)) {
|
|
throw new FailedVerificationException(MessageKey.INVALID_NAME_LENGTH);
|
|
}
|
|
if (!nicknamePattern.matcher(name).matches()) {
|
|
throw new FailedVerificationException(MessageKey.INVALID_NAME_CHARACTERS, nicknamePattern.pattern());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles the case of a full server and verifies if the user's connection should really be refused
|
|
* by adjusting the event object accordingly. Attempts to kick a non-VIP player to make room if the
|
|
* joining player is a VIP.
|
|
*
|
|
* @param event the login event to verify
|
|
*
|
|
* @return true if the player's connection should be refused (i.e. the event does not need to be processed
|
|
* further), false if the player is not refused
|
|
*/
|
|
public boolean refusePlayerForFullServer(PlayerLoginEvent event) {
|
|
final Player player = event.getPlayer();
|
|
if (event.getResult() != PlayerLoginEvent.Result.KICK_FULL) {
|
|
// Server is not full, no need to do anything
|
|
return false;
|
|
} else if (!permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)) {
|
|
// Server is full and player is NOT VIP; set kick message and proceed with kick
|
|
event.setKickMessage(messages.retrieveSingle(player, MessageKey.KICK_FULL_SERVER));
|
|
return true;
|
|
}
|
|
|
|
// Server is full and player is VIP; attempt to kick a non-VIP player to make room
|
|
Collection<Player> onlinePlayers = bukkitService.getOnlinePlayers();
|
|
if (onlinePlayers.size() < server.getMaxPlayers()) {
|
|
event.allow();
|
|
return false;
|
|
}
|
|
Player nonVipPlayer = generateKickPlayer(onlinePlayers);
|
|
if (nonVipPlayer != null) {
|
|
nonVipPlayer.kickPlayer(messages.retrieveSingle(player, MessageKey.KICK_FOR_VIP));
|
|
event.allow();
|
|
return false;
|
|
} else {
|
|
logger.info("VIP player " + player.getName() + " tried to join, but the server was full");
|
|
event.setKickMessage(messages.retrieveSingle(player, MessageKey.KICK_FULL_SERVER));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks that the casing in the username corresponds to the one in the database, if so configured.
|
|
*
|
|
* @param connectingName the player name to verify
|
|
* @param auth the auth object associated with the player
|
|
* @throws FailedVerificationException if the verification fails
|
|
*/
|
|
public void checkNameCasing(String connectingName, PlayerAuth auth) throws FailedVerificationException {
|
|
if (auth != null && settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)) {
|
|
String realName = auth.getRealName(); // might be null or "Player"
|
|
|
|
if (StringUtils.isBlank(realName) || "Player".equals(realName)) {
|
|
dataSource.updateRealName(connectingName.toLowerCase(Locale.ROOT), connectingName);
|
|
} else if (!realName.equals(connectingName)) {
|
|
throw new FailedVerificationException(MessageKey.INVALID_NAME_CASE, realName, connectingName);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks that the player's country is admitted.
|
|
*
|
|
* @param name the joining player name to verify
|
|
* @param address the player address
|
|
* @param isAuthAvailable whether or not the user is registered
|
|
* @throws FailedVerificationException if the verification fails
|
|
*/
|
|
public void checkPlayerCountry(String name, String address,
|
|
boolean isAuthAvailable) throws FailedVerificationException {
|
|
if ((!isAuthAvailable || settings.getProperty(ProtectionSettings.ENABLE_PROTECTION_REGISTERED))
|
|
&& settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)
|
|
&& !permissionsManager.hasPermissionOffline(name, PlayerStatePermission.BYPASS_COUNTRY_CHECK)
|
|
&& !validationService.isCountryAdmitted(address)) {
|
|
throw new FailedVerificationException(MessageKey.COUNTRY_BANNED_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if a player with the same name (case-insensitive) is already playing and refuses the
|
|
* connection if so configured.
|
|
*
|
|
* @param name the player name to check
|
|
* @throws FailedVerificationException if the verification fails
|
|
*/
|
|
public void checkSingleSession(String name) throws FailedVerificationException {
|
|
if (!settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)) {
|
|
return;
|
|
}
|
|
|
|
Player onlinePlayer = bukkitService.getPlayerExact(name);
|
|
if (onlinePlayer != null) {
|
|
throw new FailedVerificationException(MessageKey.USERNAME_ALREADY_ONLINE_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Selects a non-VIP player to kick when a VIP player joins the server when full.
|
|
*
|
|
* @param onlinePlayers list of online players
|
|
*
|
|
* @return the player to kick, or null if none applicable
|
|
*/
|
|
private Player generateKickPlayer(Collection<Player> onlinePlayers) {
|
|
for (Player player : onlinePlayers) {
|
|
if (!permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)) {
|
|
return player;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|