diff --git a/docs/translations.md b/docs/translations.md index 70c26d8b..689c303d 100644 --- a/docs/translations.md +++ b/docs/translations.md @@ -1,5 +1,5 @@ - + # AuthMe Translations The following translations are available in AuthMe. Set `messagesLanguage` to the language code @@ -8,25 +8,25 @@ in your config.yml to use the language, or use another language code to start a Code | Language | Translated |   ---- | -------- | ---------: | ------ [en](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_en.yml) | English | 100% | bar -[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 61% | bar +[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 100% | bar [br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 89% | bar [cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 89% | bar [de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 89% | bar -[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 89% | bar +[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 100% | bar [eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 55% | bar [fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 59% | bar -[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 89% | bar +[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 100% | bar [gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 63% | bar [hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 88% | bar [id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 63% | bar -[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 89% | bar +[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 100% | bar [ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 64% | bar [lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 47% | bar [nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 89% | bar [pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 100% | bar -[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 77% | bar +[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 100% | bar [ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 88% | bar -[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 89% | bar +[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 100% | bar [sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 41% | bar [tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 100% | bar [uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 83% | bar @@ -36,7 +36,6 @@ Code | Language | Translated |   [zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 86% | bar [zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 72% | bar - --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Tue Feb 28 19:25:18 CET 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Mon Mar 13 20:34:31 CET 2017 diff --git a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java index eed9933f..53f4d8fd 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -15,6 +15,7 @@ import fr.xephi.authme.process.login.AsynchronousLogin; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.PluginHookService; +import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.PluginSettings; @@ -67,6 +68,9 @@ public class AsynchronousJoin implements AsynchronousProcess { @Inject private CommandManager commandManager; + @Inject + private ValidationService validationService; + AsynchronousJoin() { } @@ -87,7 +91,7 @@ public class AsynchronousJoin implements AsynchronousProcess { pluginHookService.setEssentialsSocialSpyStatus(player, false); } - if (isNameRestricted(name, ip, player.getAddress().getHostName())) { + if (!validationService.fulfillsNameRestrictions(player)) { bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() { @Override public void run() { @@ -163,36 +167,6 @@ public class AsynchronousJoin implements AsynchronousProcess { }); } - /** - * Returns whether the name is restricted based on the restriction settings. - * - * @param name The name to check - * @param ip The IP address of the player - * @param domain The hostname of the IP address - * - * @return True if the name is restricted (IP/domain is not allowed for the given name), - * false if the restrictions are met or if the name has no restrictions to it - */ - private boolean isNameRestricted(String name, String ip, String domain) { - if (!service.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)) { - return false; - } - - boolean nameFound = false; - for (String entry : service.getProperty(RestrictionSettings.ALLOWED_RESTRICTED_USERS)) { - String[] args = entry.split(";"); - String testName = args[0]; - String testIp = args[1]; - if (testName.equalsIgnoreCase(name)) { - nameFound = true; - if ((ip != null && testIp.equals(ip)) || (domain != null && testIp.equalsIgnoreCase(domain))) { - return false; - } - } - } - return nameFound; - } - /** * Checks whether the maximum number of accounts has been exceeded for the given IP address (according to * settings and permissions). If this is the case, the player is kicked. diff --git a/src/main/java/fr/xephi/authme/service/ValidationService.java b/src/main/java/fr/xephi/authme/service/ValidationService.java index dd1f6e51..9511c6e3 100644 --- a/src/main/java/fr/xephi/authme/service/ValidationService.java +++ b/src/main/java/fr/xephi/authme/service/ValidationService.java @@ -1,6 +1,8 @@ package fr.xephi.authme.service; import ch.jalu.configme.properties.Property; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.Reloadable; @@ -12,8 +14,10 @@ import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.ProtectionSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.Utils; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import javax.annotation.PostConstruct; import javax.inject.Inject; @@ -23,6 +27,8 @@ import java.util.List; import java.util.Set; import java.util.regex.Pattern; +import static fr.xephi.authme.util.StringUtils.isInsideString; + /** * Validation service. */ @@ -39,6 +45,7 @@ public class ValidationService implements Reloadable { private Pattern passwordRegex; private Set unrestrictedNames; + private Multimap restrictedNames; ValidationService() { } @@ -49,6 +56,9 @@ public class ValidationService implements Reloadable { passwordRegex = Utils.safePatternCompile(settings.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX)); // Use Set for more efficient contains() lookup unrestrictedNames = new HashSet<>(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)); + restrictedNames = settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS) + ? loadNameRestrictions(settings.getProperty(RestrictionSettings.RESTRICTED_USERS)) + : HashMultimap.create(); } /** @@ -132,6 +142,24 @@ public class ValidationService implements Reloadable { return unrestrictedNames.contains(name.toLowerCase()); } + /** + * Checks that the player meets any name restriction if present (IP/domain-based). + * + * @param player the player to check + * @return true if the player may join, false if the player does not satisfy the name restrictions + */ + public boolean fulfillsNameRestrictions(Player player) { + Collection restrictions = restrictedNames.get(player.getName().toLowerCase()); + if (Utils.isCollectionEmpty(restrictions)) { + return true; + } + + String ip = PlayerUtils.getPlayerIp(player); + String domain = player.getAddress().getHostName(); + return restrictions.stream() + .anyMatch(restriction -> ip.equals(restriction) || domain.equalsIgnoreCase(restriction)); + } + /** * Verifies whether the given value is allowed according to the given whitelist and blacklist settings. * Whitelist has precedence over blacklist: if a whitelist is set, the value is rejected if not present @@ -161,6 +189,26 @@ public class ValidationService implements Reloadable { return false; } + /** + * Loads the configured name restrictions into a Multimap by player name (all-lowercase). + * + * @param configuredRestrictions the restriction rules to convert to a map + * @return map of allowed IPs/domain names by player name + */ + private Multimap loadNameRestrictions(List configuredRestrictions) { + Multimap restrictions = HashMultimap.create(); + for (String restriction : configuredRestrictions) { + if (isInsideString(';', restriction)) { + String[] data = restriction.split(";"); + restrictions.put(data[0].toLowerCase(), data[1]); + } else { + ConsoleLogger.warning("Restricted user rule must have a ';' separating name from restriction," + + " but found: '" + restriction + "'"); + } + } + return restrictions; + } + public static final class ValidationResult { private final MessageKey messageKey; private final String[] args; diff --git a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java index 378bd198..1e4697bf 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java @@ -10,7 +10,7 @@ import static ch.jalu.configme.properties.PropertyInitializer.newProperty; public final class DatabaseSettings implements SettingsHolder { @Comment({"What type of database do you want to use?", - "Valid values: sqlite, mysql"}) + "Valid values: SQLITE, MYSQL"}) public static final Property BACKEND = newProperty(DataSourceType.class, "DataSource.backend", DataSourceType.SQLITE); diff --git a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java index 5a007b32..40de8ca6 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java @@ -81,7 +81,7 @@ public final class RestrictionSettings implements SettingsHolder { "Example:", " AllowedRestrictedUser:", " - playername;127.0.0.1"}) - public static final Property> ALLOWED_RESTRICTED_USERS = + public static final Property> RESTRICTED_USERS = newLowercaseListProperty("settings.restrictions.AllowedRestrictedUser"); @Comment("Ban unknown IPs trying to log in with a restricted username?") diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java index 5e0696af..1f200c0f 100644 --- a/src/main/java/fr/xephi/authme/util/StringUtils.java +++ b/src/main/java/fr/xephi/authme/util/StringUtils.java @@ -76,4 +76,20 @@ public final class StringUtils { public static String formatException(Throwable th) { return "[" + th.getClass().getSimpleName() + "]: " + th.getMessage(); } + + /** + * Check that the given needle is in the middle of the haystack, i.e. that the haystack + * contains the needle and that it is not at the very start or end. + * + * @param needle the needle to search for + * @param haystack the haystack to search in + * + * @return true if the needle is in the middle of the word, false otherwise + */ + // Note ljacqu 20170314: `needle` is restricted to char type intentionally because something like + // isInsideString("11", "2211") would unexpectedly return true... + public static boolean isInsideString(char needle, String haystack) { + int index = haystack.indexOf(needle); + return index > 0 && index < haystack.length() - 1; + } } diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml index fef35f74..dfc51087 100644 --- a/src/main/resources/messages/messages_pt.yml +++ b/src/main/resources/messages/messages_pt.yml @@ -1,24 +1,24 @@ # Registration -reg_msg: '&cPor favor registe-se com "/register password confirmePassword"' -usage_reg: '&cUse: /register seu@email.com seu@email.com' -reg_only: '&fApenas jogadores registados! Visite http://example.com para se registar' -# TODO kicked_admin_registered: 'An admin just registered you; please log in again' +reg_msg: '&cPor favor registe-se com "/register "' +usage_reg: '&cUse: /register ' +reg_only: '&fApenas jogadores registados podem entrar no servidor! Visite http://example.com para se registar' +kicked_admin_registered: 'Um administrador registou-te, por favor entre novamente' registered: '&cRegistado com sucesso!' -reg_disabled: '&cRegito de novos utilizadores desactivado' +reg_disabled: '&cRegisto de novos utilizadores desactivado' user_regged: '&cUtilizador já registado' # Password errors on registration password_error: '&fAs passwords não coincidem' password_error_nick: '&cNão pode o usar seu nome como senha, por favor, escolha outra ...' password_error_unsafe: '&cA senha escolhida não é segura, por favor, escolha outra ...' -password_error_chars: '&4Sua senha contém caracteres ilegais. caracteres permitidos: REG_EX' -pass_len: '&fPassword demasiado curta' +password_error_chars: '&4Sua senha contém caracteres ilegais. Caracteres permitidos: REG_EX' +pass_len: '&fPassword demasiado curta ou longa! Por favor escolhe outra outra!' # Login -usage_log: '&cUse: /login password' +usage_log: '&cUse: /login ' wrong_pwd: '&cPassword errada!' login: '&bAutenticado com sucesso!' -login_msg: '&cIdentifique-se com "/login password"' +login_msg: '&cIdentifique-se com "/login "' timeout: '&fExcedeu o tempo para autenticação' # Errors @@ -26,11 +26,11 @@ unknown_user: '&cUsername não registado' denied_command: '&cPara utilizar este comando é necessário estar logado!' denied_chat: '&cPara usar o chat deve estar logado!' not_logged_in: '&cNão autenticado!' -# TODO tempban_max_logins: '&cYou have been temporarily banned for failing to log in too many times.' +tempban_max_logins: '&cVocê foi temporariamente banido por falhar muitas vezes o login.' # TODO: Missing tags %reg_names max_reg: '&cAtingiu o numero máximo de %reg_count contas registas, maximo de contas %max_acc' no_perm: '&cSem Permissões' -error: '&fOcorreu um erro; Por favor contacte um admin' +error: '&fOcorreu um erro; Por favor contacte um administrador' unsafe_spawn: '&fA sua localização na saída não é segura, será tele-portado para a Spawn' kick_forvip: '&cUm jogador VIP entrou no servidor cheio!' @@ -41,18 +41,18 @@ antibot_auto_disabled: '[AuthMe] AntiBotMod desactivado automaticamente após %m # Other messages unregistered: '&cRegisto eliminado com sucesso!' -# TODO accounts_owned_self: 'You own %count accounts:' -# TODO accounts_owned_other: 'The player %name has %count accounts:' +accounts_owned_self: 'Você possui %count contas:' +accounts_owned_other: 'O jogador %name possui %count contas:' two_factor_create: '&2O seu código secreto é o %code. Você pode verificá-lo a partir daqui %url' -# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' +recovery_code_sent: 'O codigo para redefinir a senha foi enviado para o seu e-mail.' +recovery_code_incorrect: 'O codigo de recuperação está incorreto! Use "/email recovery [email]" para gerar um novo' vb_nonActiv: '&fA sua conta não foi ainda activada, verifique o seu email onde irá receber indicações para activação de conta. ' -usage_unreg: '&cUse: /unregister password' +usage_unreg: '&cUse: /unregister ' pwd_changed: '&cPassword alterada!' logged_in: '&cJá se encontra autenticado!' logout: '&cSaida com sucesso' reload: '&fConfiguração e base de dados foram recarregadas' -usage_changepassword: '&fUse: /changepassword passwordAntiga passwordNova' +usage_changepassword: '&fUse: /changepassword ' # Session messages invalid_session: '&fDados de sessão não correspondem. Por favor aguarde o fim da sessão' @@ -80,14 +80,14 @@ email_confirm: 'Confirme o seu email!' email_changed: 'Email alterado com sucesso!' email_send: 'Nova palavra-passe enviada para o seu email!' email_exists: '&cUm e-mail de recuperação já foi enviado! Pode descartá-lo e enviar um novo usando o comando abaixo:' -# TODO email_show: '&2Your current email address is: &f%email' -# TODO incomplete_email_settings: 'Error: not all required settings are set for sending emails. Please contact an admin.' +email_show: '&2O seu endereço de email atual é &f%email' +incomplete_email_settings: 'Erro: nem todas as definições necessarias para enviar email foram preenchidas. Por favor contate um administrador.' email_already_used: '&4O endereço de e-mail já está sendo usado' -# TODO email_send_failure: 'The email could not be sent. Please contact an administrator.' -# TODO show_no_email: '&2You currently don''t have email address associated with this account.' -add_email: '&cPor favor adicione o seu email com : /email add seuEmail confirmarSeuEmail' +email_send_failure: 'Não foi possivel enviar o email. Por favor contate um administrador.' +show_no_email: '&2Você atualmente não tem um endereço de email associado a essa conta.' +add_email: '&cPor favor adicione o seu email com : /email add ' recovery_email: '&cPerdeu a sua password? Para a recuperar escreva /email recovery ' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +email_cooldown_error: '&cUm email já foi enviado recentemente.Por favor, espere %time antes de enviar novamente' # Captcha usage_captcha: '&cPrecisa digitar um captcha, escreva: /captcha ' @@ -95,11 +95,11 @@ wrong_captcha: '&cCaptcha errado, por favor escreva: /captcha THE_CAPTCHA' valid_captcha: '&cO seu captcha é válido!' # Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +second: 'segundo' +seconds: 'segundos' +minute: 'minuto' +minutes: 'minutos' +hour: 'hora' +hours: 'horas' +day: 'dia' +days: 'dias' diff --git a/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java b/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java index 21ffa952..3953d4ef 100644 --- a/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java @@ -4,24 +4,30 @@ import ch.jalu.injector.testing.BeforeInjecting; import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import com.google.common.base.Strings; +import fr.xephi.authme.TestHelper; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; +import fr.xephi.authme.service.ValidationService.ValidationResult; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.ProtectionSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.service.ValidationService.ValidationResult; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import java.util.Arrays; import java.util.Collections; +import java.util.logging.Logger; import static java.util.Arrays.asList; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; @@ -55,6 +61,7 @@ public class ValidationServiceTest { .willReturn(asList("unsafe", "other-unsafe")); given(settings.getProperty(EmailSettings.MAX_REG_PER_EMAIL)).willReturn(3); given(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)).willReturn(asList("name01", "npc")); + given(settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)).willReturn(false); } @Test @@ -115,8 +122,8 @@ public class ValidationServiceTest { @Test public void shouldAcceptEmailWithEmptyLists() { // given - given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); - given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); // when boolean result = validationService.validateEmail("test@example.org"); @@ -130,7 +137,7 @@ public class ValidationServiceTest { // given given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)) .willReturn(asList("domain.tld", "example.com")); - given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); // when boolean result = validationService.validateEmail("TesT@Example.com"); @@ -144,7 +151,7 @@ public class ValidationServiceTest { // given given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)) .willReturn(asList("domain.tld", "example.com")); - given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); // when boolean result = validationService.validateEmail("email@other-domain.abc"); @@ -156,7 +163,7 @@ public class ValidationServiceTest { @Test public void shouldAcceptEmailNotInBlacklist() { // given - given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)) .willReturn(asList("Example.org", "a-test-name.tld")); @@ -170,7 +177,7 @@ public class ValidationServiceTest { @Test public void shouldRejectEmailInBlacklist() { // given - given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)) .willReturn(asList("Example.org", "a-test-name.tld")); @@ -263,8 +270,8 @@ public class ValidationServiceTest { @Test public void shouldNotInvokeGeoLiteApiIfCountryListsAreEmpty() { // given - given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); - given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); // when boolean result = validationService.isCountryAdmitted("addr"); @@ -278,7 +285,7 @@ public class ValidationServiceTest { public void shouldAcceptCountryInWhitelist() { // given given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(asList("ch", "it")); - given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); String ip = "127.0.0.1"; given(geoIpService.getCountryCode(ip)).willReturn("CH"); @@ -294,7 +301,7 @@ public class ValidationServiceTest { public void shouldRejectCountryMissingFromWhitelist() { // given given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(asList("ch", "it")); - given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); String ip = "123.45.67.89"; given(geoIpService.getCountryCode(ip)).willReturn("BR"); @@ -309,7 +316,7 @@ public class ValidationServiceTest { @Test public void shouldAcceptCountryAbsentFromBlacklist() { // given - given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(asList("ch", "it")); String ip = "127.0.0.1"; given(geoIpService.getCountryCode(ip)).willReturn("BR"); @@ -325,7 +332,7 @@ public class ValidationServiceTest { @Test public void shouldRejectCountryInBlacklist() { // given - given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(asList("ch", "it")); String ip = "123.45.67.89"; given(geoIpService.getCountryCode(ip)).willReturn("IT"); @@ -338,6 +345,54 @@ public class ValidationServiceTest { verify(geoIpService).getCountryCode(ip); } + @Test + public void shouldCheckNameRestrictions() { + // given + given(settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)).willReturn(true); + given(settings.getProperty(RestrictionSettings.RESTRICTED_USERS)) + .willReturn(Arrays.asList("Bobby;127.0.0.4", "Tamara;32.24.16.8")); + validationService.reload(); + + Player bobby = mockPlayer("bobby", "127.0.0.4"); + Player tamara = mockPlayer("taMARA", "8.8.8.8"); + Player notRestricted = mockPlayer("notRestricted", "0.0.0.0"); + + // when + boolean isBobbyAdmitted = validationService.fulfillsNameRestrictions(bobby); + boolean isTamaraAdmitted = validationService.fulfillsNameRestrictions(tamara); + boolean isNotRestrictedAdmitted = validationService.fulfillsNameRestrictions(notRestricted); + + // then + assertThat(isBobbyAdmitted, equalTo(true)); + assertThat(isTamaraAdmitted, equalTo(false)); + assertThat(isNotRestrictedAdmitted, equalTo(true)); + } + + @Test + public void shouldLogWarningForInvalidRestrictionRule() { + // given + Logger logger = TestHelper.setupLogger(); + given(settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)).willReturn(true); + given(settings.getProperty(RestrictionSettings.RESTRICTED_USERS)) + .willReturn(Arrays.asList("Bobby;127.0.0.4", "Tamara;")); + + // when + validationService.reload(); + + // then + ArgumentCaptor stringCaptor = ArgumentCaptor.forClass(String.class); + verify(logger).warning(stringCaptor.capture()); + assertThat(stringCaptor.getValue(), containsString("Tamara;")); + } + + private static Player mockPlayer(String name, String ip) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + TestHelper.mockPlayerIp(player, ip); + given(player.getAddress().getHostName()).willReturn("--"); + return player; + } + private static void assertErrorEquals(ValidationResult validationResult, MessageKey messageKey, String... args) { assertThat(validationResult.hasError(), equalTo(true)); assertThat(validationResult.getMessageKey(), equalTo(messageKey)); diff --git a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java index 7d6c53be..cddc0cab 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java @@ -3,16 +3,23 @@ package fr.xephi.authme.settings; import ch.jalu.configme.SectionComments; import ch.jalu.configme.SettingsHolder; import ch.jalu.configme.configurationdata.ConfigurationData; +import ch.jalu.configme.properties.EnumProperty; import ch.jalu.configme.properties.Property; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Multimap; import fr.xephi.authme.ClassCollector; import fr.xephi.authme.ReflectionTestUtils; +import fr.xephi.authme.datasource.DataSourceType; import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever; +import fr.xephi.authme.settings.properties.DatabaseSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; import org.junit.BeforeClass; import org.junit.Test; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -20,6 +27,7 @@ import java.util.Set; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkArgument; +import static fr.xephi.authme.ReflectionTestUtils.getFieldValue; import static org.junit.Assert.fail; /** @@ -32,6 +40,17 @@ public class SettingsConsistencyTest { */ private static final int MAX_COMMENT_LENGTH = 90; + /** + * Exclusions for the enum in comments check. Use {@link Exclude#ALL} + * to skip an entire property from being checked. + */ + private static final Multimap, Enum> EXCLUDED_ENUMS = + ImmutableSetMultimap., Enum>builder() + .put(DatabaseSettings.BACKEND, DataSourceType.FILE) + .put(SecuritySettings.PASSWORD_HASH, Exclude.ALL) + .put(SecuritySettings.LEGACY_HASHES, Exclude.ALL) + .build(); + private static ConfigurationData configurationData; @BeforeClass @@ -132,4 +151,63 @@ public class SettingsConsistencyTest { .flatMap(Arrays::stream) .collect(Collectors.toList()); } + + /** + * Checks that enum properties have all possible enum values listed in their comment + * so the user knows which values are available. + */ + @Test + public void shouldMentionAllEnumValues() { + // given + Map, Enum> invalidEnumProperties = new HashMap<>(); + + for (Property property : configurationData.getProperties()) { + // when + Class> enumClass = getEnumClass(property); + if (enumClass != null) { + String comments = String.join("\n", configurationData.getCommentsForSection(property.getPath())); + Arrays.stream(enumClass.getEnumConstants()) + .filter(e -> !comments.contains(e.name()) && !isExcluded(property, e)) + .findFirst() + .ifPresent(e -> invalidEnumProperties.put(property, e)); + } + } + + // then + if (!invalidEnumProperties.isEmpty()) { + String invalidEnums = invalidEnumProperties.entrySet().stream() + .map(e -> e.getKey() + " does not mention " + e.getValue() + " and possibly others") + .collect(Collectors.joining("\n- ")); + + fail("Found enum properties that do not list all entries in the comments:\n- " + invalidEnums); + } + } + + /** + * Returns the enum class the property holds values for, if applicable. + * + * @param property the property to get the enum class from + * @return the enum class, or null if not available + */ + private static Class> getEnumClass(Property property) { + if (property instanceof EnumProperty) { + return getFieldValue(EnumProperty.class, (EnumProperty) property, "clazz"); + } else if (property instanceof EnumSetProperty) { + return getFieldValue(EnumSetProperty.class, (EnumSetProperty) property, "enumClass"); + } + return null; + } + + private static boolean isExcluded(Property property, Enum enumValue) { + return EXCLUDED_ENUMS.get(property).contains(Exclude.ALL) + || EXCLUDED_ENUMS.get(property).contains(enumValue); + } + + /** + * Dummy enum to specify in the exclusion that all enum values + * should be skipped. See its usages. + */ + private enum Exclude { + ALL + } } diff --git a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java index 629adbd4..7111f81b 100644 --- a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java @@ -95,4 +95,14 @@ public class StringUtilsTest { public void shouldHaveHiddenConstructor() { TestHelper.validateHasOnlyPrivateEmptyConstructor(StringUtils.class); } + + @Test + public void shouldCheckIfHasNeedleInWord() { + // given/when/then + assertThat(StringUtils.isInsideString('@', "@hello"), equalTo(false)); + assertThat(StringUtils.isInsideString('?', "absent"), equalTo(false)); + assertThat(StringUtils.isInsideString('-', "abcd-"), equalTo(false)); + assertThat(StringUtils.isInsideString('@', "hello@example"), equalTo(true)); + assertThat(StringUtils.isInsideString('@', "D@Z"), equalTo(true)); + } }