AuthMeReloaded/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.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;
}
}