diff --git a/docs/commands.md b/docs/commands.md index 60c01dbac..ef8f28a8a 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,5 +1,5 @@ - + ## AuthMe Commands You can use the following commands to use the features of AuthMe. Mandatory arguments are marked with `< >` @@ -24,6 +24,10 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
Requires `authme.admin.changemail` - **/authme getip** <player>: Get the IP address of the specified online player.
Requires `authme.admin.getip` +- **/authme totp** <player>: Returns whether the specified player has enabled two-factor authentication +
Requires `authme.admin.totpviewstatus` +- **/authme disabletotp** <player>: Disable two-factor authentication for a player +
Requires `authme.admin.totpdisable` - **/authme spawn**: Teleport to the spawn.
Requires `authme.admin.spawn` - **/authme setspawn**: Change the player's spawn to your current position. @@ -104,4 +108,4 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`). --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Fri Apr 19 17:16:04 CEST 2019 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Fri Aug 02 16:25:51 CEST 2019 diff --git a/docs/permission_nodes.md b/docs/permission_nodes.md index 2ed9f36c5..c53953b55 100644 --- a/docs/permission_nodes.md +++ b/docs/permission_nodes.md @@ -1,5 +1,5 @@ - + ## AuthMe Permission Nodes The following are the permission nodes that are currently supported by the latest dev builds. @@ -28,6 +28,8 @@ The following are the permission nodes that are currently supported by the lates - **authme.admin.setspawn** – Administrator command to set the AuthMe spawn. - **authme.admin.spawn** – Administrator command to teleport to the AuthMe spawn. - **authme.admin.switchantibot** – Administrator command to toggle the AntiBot protection status. +- **authme.admin.totpdisable** – Administrator command to disable the two-factor auth of a user. +- **authme.admin.totpviewstatus** – Administrator command to see whether a player has enabled two-factor authentication. - **authme.admin.unregister** – Administrator command to unregister an existing user. - **authme.admin.updatemessages** – Permission to use the update messages command. - **authme.allowchatbeforelogin** – Permission to send chat messages before being logged in. @@ -71,4 +73,4 @@ The following are the permission nodes that are currently supported by the lates --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Fri Apr 19 17:16:06 CEST 2019 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Fri Aug 02 16:25:54 CEST 2019 diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java index ba48e0112..fcc3ce611 100644 --- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java +++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java @@ -24,6 +24,8 @@ import fr.xephi.authme.command.executable.authme.SetFirstSpawnCommand; import fr.xephi.authme.command.executable.authme.SetSpawnCommand; import fr.xephi.authme.command.executable.authme.SpawnCommand; import fr.xephi.authme.command.executable.authme.SwitchAntiBotCommand; +import fr.xephi.authme.command.executable.authme.TotpDisableAdminCommand; +import fr.xephi.authme.command.executable.authme.TotpViewStatusCommand; import fr.xephi.authme.command.executable.authme.UnregisterAdminCommand; import fr.xephi.authme.command.executable.authme.UpdateHelpMessagesCommand; import fr.xephi.authme.command.executable.authme.VersionCommand; @@ -290,6 +292,28 @@ public class CommandInitializer { .executableCommand(GetIpCommand.class) .register(); + // Register totp command + CommandDescription.builder() + .parent(authmeBase) + .labels("totp", "2fa") + .description("See if a player has enabled TOTP") + .detailedDescription("Returns whether the specified player has enabled two-factor authentication.") + .withArgument("player", "Player name", MANDATORY) + .permission(AdminPermission.VIEW_TOTP_STATUS) + .executableCommand(TotpViewStatusCommand.class) + .register(); + + // Register disable totp command + CommandDescription.builder() + .parent(authmeBase) + .labels("disabletotp", "disable2fa", "deletetotp", "delete2fa") + .description("Delete TOTP token of a player") + .detailedDescription("Disable two-factor authentication for a player.") + .withArgument("player", "Player name", MANDATORY) + .permission(AdminPermission.DISABLE_TOTP) + .executableCommand(TotpDisableAdminCommand.class) + .register(); + // Register the spawn command CommandDescription.builder() .parent(authmeBase) diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/TotpDisableAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/TotpDisableAdminCommand.java new file mode 100644 index 000000000..5c9b30f42 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/TotpDisableAdminCommand.java @@ -0,0 +1,58 @@ +package fr.xephi.authme.command.executable.authme; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.message.Messages; +import fr.xephi.authme.service.BukkitService; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.List; + +/** + * Command to disable two-factor authentication for a user. + */ +public class TotpDisableAdminCommand implements ExecutableCommand { + + @Inject + private DataSource dataSource; + + @Inject + private Messages messages; + + @Inject + private BukkitService bukkitService; + + @Override + public void executeCommand(CommandSender sender, List arguments) { + String player = arguments.get(0); + + PlayerAuth auth = dataSource.getAuth(player); + if (auth == null) { + messages.send(sender, MessageKey.UNKNOWN_USER); + } else if (auth.getTotpKey() == null) { + sender.sendMessage(ChatColor.RED + "Player '" + player + "' does not have two-factor auth enabled"); + } else { + removeTotpKey(sender, player); + } + } + + private void removeTotpKey(CommandSender sender, String player) { + if (dataSource.removeTotpKey(player)) { + sender.sendMessage("Disabled two-factor authentication successfully for '" + player + "'"); + ConsoleLogger.info(sender.getName() + " disable two-factor authentication for '" + player + "'"); + + Player onlinePlayer = bukkitService.getPlayerExact(player); + if (onlinePlayer != null) { + messages.send(onlinePlayer, MessageKey.TWO_FACTOR_REMOVED_SUCCESS); + } + } else { + messages.send(sender, MessageKey.ERROR); + } + } +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/TotpViewStatusCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/TotpViewStatusCommand.java new file mode 100644 index 000000000..d9b2c92c9 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/TotpViewStatusCommand.java @@ -0,0 +1,38 @@ +package fr.xephi.authme.command.executable.authme; + +import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.message.Messages; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; +import java.util.List; + +/** + * Command to see whether a user has enabled two-factor authentication. + */ +public class TotpViewStatusCommand implements ExecutableCommand { + + @Inject + private DataSource dataSource; + + @Inject + private Messages messages; + + @Override + public void executeCommand(CommandSender sender, List arguments) { + String player = arguments.get(0); + + PlayerAuth auth = dataSource.getAuth(player); + if (auth == null) { + messages.send(sender, MessageKey.UNKNOWN_USER); + } else if (auth.getTotpKey() == null) { + sender.sendMessage(ChatColor.RED + "Player '" + player + "' does NOT have two-factor auth enabled"); + } else { + sender.sendMessage(ChatColor.DARK_GREEN + "Player '" + player + "' has enabled two-factor authentication"); + } + } +} diff --git a/src/main/java/fr/xephi/authme/permission/AdminPermission.java b/src/main/java/fr/xephi/authme/permission/AdminPermission.java index 7664e1436..20e27f2fc 100644 --- a/src/main/java/fr/xephi/authme/permission/AdminPermission.java +++ b/src/main/java/fr/xephi/authme/permission/AdminPermission.java @@ -45,6 +45,16 @@ public enum AdminPermission implements PermissionNode { */ CHANGE_EMAIL("authme.admin.changemail"), + /** + * Administrator command to see whether a player has enabled two-factor authentication. + */ + VIEW_TOTP_STATUS("authme.admin.totpviewstatus"), + + /** + * Administrator command to disable the two-factor auth of a user. + */ + DISABLE_TOTP("authme.admin.totpdisable"), + /** * Administrator command to get the last known IP of a user. */ diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 027da3dc0..aa267deac 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -18,7 +18,7 @@ softdepend: commands: authme: description: AuthMe op commands - usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|purgeplayer|backup|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|recent|debug + usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|totp|disabletotp|spawn|setspawn|firstspawn|setfirstspawn|purge|purgeplayer|backup|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|recent|debug email: description: Add email or recover password usage: /email show|add|change|recover|code|setpassword @@ -85,6 +85,8 @@ permissions: authme.admin.setspawn: true authme.admin.spawn: true authme.admin.switchantibot: true + authme.admin.totpdisable: true + authme.admin.totpviewstatus: true authme.admin.unregister: true authme.admin.updatemessages: true authme.admin.accounts: @@ -156,6 +158,13 @@ permissions: authme.admin.switchantibot: description: Administrator command to toggle the AntiBot protection status. default: op + authme.admin.totpdisable: + description: Administrator command to disable the two-factor auth of a user. + default: op + authme.admin.totpviewstatus: + description: Administrator command to see whether a player has enabled two-factor + authentication. + default: op authme.admin.unregister: description: Administrator command to unregister an existing user. default: op diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/TotpDisableAdminCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/TotpDisableAdminCommandTest.java new file mode 100644 index 000000000..e6728d894 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/authme/TotpDisableAdminCommandTest.java @@ -0,0 +1,120 @@ +package fr.xephi.authme.command.executable.authme; + +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.message.Messages; +import fr.xephi.authme.service.BukkitService; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Collections; + +import static org.hamcrest.Matchers.containsString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; +import static org.mockito.hamcrest.MockitoHamcrest.argThat; + +/** + * Test for {@link TotpDisableAdminCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class TotpDisableAdminCommandTest { + + @InjectMocks + private TotpDisableAdminCommand command; + + @Mock + private DataSource dataSource; + + @Mock + private Messages messages; + + @Mock + private BukkitService bukkitService; + + @BeforeClass + public static void initLogger() { + TestHelper.setupLogger(); + } + + @Test + public void shouldHandleUnknownUser() { + // given + CommandSender sender = mock(CommandSender.class); + given(dataSource.getAuth("user")).willReturn(null); + + // when + command.executeCommand(sender, Collections.singletonList("user")); + + // then + verify(messages).send(sender, MessageKey.UNKNOWN_USER); + verify(dataSource, only()).getAuth("user"); + } + + @Test + public void shouldHandleUserWithNoTotpEnabled() { + // given + CommandSender sender = mock(CommandSender.class); + PlayerAuth auth = PlayerAuth.builder() + .name("billy") + .totpKey(null) + .build(); + given(dataSource.getAuth("Billy")).willReturn(auth); + + // when + command.executeCommand(sender, Collections.singletonList("Billy")); + + // then + verify(sender).sendMessage(argThat(containsString("'Billy' does not have two-factor auth enabled"))); + verify(dataSource, only()).getAuth("Billy"); + } + + @Test + public void shouldRemoveTotpFromUser() { + // given + CommandSender sender = mock(CommandSender.class); + PlayerAuth auth = PlayerAuth.builder() + .name("Bobby") + .totpKey("56484998") + .build(); + given(dataSource.getAuth("Bobby")).willReturn(auth); + given(dataSource.removeTotpKey("Bobby")).willReturn(true); + Player player = mock(Player.class); + given(bukkitService.getPlayerExact("Bobby")).willReturn(player); + + // when + command.executeCommand(sender, Collections.singletonList("Bobby")); + + // then + verify(sender).sendMessage(argThat(containsString("Disabled two-factor authentication successfully"))); + verify(messages).send(player, MessageKey.TWO_FACTOR_REMOVED_SUCCESS); + } + + @Test + public void shouldHandleErrorWhileRemovingTotp() { + // given + CommandSender sender = mock(CommandSender.class); + PlayerAuth auth = PlayerAuth.builder() + .name("Bobby") + .totpKey("321654") + .build(); + given(dataSource.getAuth("Bobby")).willReturn(auth); + given(dataSource.removeTotpKey("Bobby")).willReturn(false); + + // when + command.executeCommand(sender, Collections.singletonList("Bobby")); + + // then + verify(messages).send(sender, MessageKey.ERROR); + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/TotpViewStatusCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/TotpViewStatusCommandTest.java new file mode 100644 index 000000000..ba6560306 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/authme/TotpViewStatusCommandTest.java @@ -0,0 +1,92 @@ +package fr.xephi.authme.command.executable.authme; + +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.message.Messages; +import org.bukkit.command.CommandSender; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Collections; + +import static org.hamcrest.Matchers.containsString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; +import static org.mockito.hamcrest.MockitoHamcrest.argThat; + +/** + * Test for {@link TotpViewStatusCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class TotpViewStatusCommandTest { + + @InjectMocks + private TotpViewStatusCommand command; + + @Mock + private DataSource dataSource; + + @Mock + private Messages messages; + + @BeforeClass + public static void initLogger() { + TestHelper.setupLogger(); + } + + @Test + public void shouldHandleUnknownUser() { + // given + CommandSender sender = mock(CommandSender.class); + given(dataSource.getAuth("user")).willReturn(null); + + // when + command.executeCommand(sender, Collections.singletonList("user")); + + // then + verify(messages).send(sender, MessageKey.UNKNOWN_USER); + verify(dataSource, only()).getAuth("user"); + } + + @Test + public void shouldInformForUserWithoutTotp() { + // given + CommandSender sender = mock(CommandSender.class); + PlayerAuth auth = PlayerAuth.builder() + .name("billy") + .totpKey(null) + .build(); + given(dataSource.getAuth("Billy")).willReturn(auth); + + // when + command.executeCommand(sender, Collections.singletonList("Billy")); + + // then + verify(sender).sendMessage(argThat(containsString("'Billy' does NOT have two-factor auth enabled"))); + } + + @Test + public void shouldInformForUserWithTotpEnabled() { + // given + CommandSender sender = mock(CommandSender.class); + PlayerAuth auth = PlayerAuth.builder() + .name("billy") + .totpKey("92841575") + .build(); + given(dataSource.getAuth("Billy")).willReturn(auth); + + // when + command.executeCommand(sender, Collections.singletonList("Billy")); + + // then + verify(sender).sendMessage(argThat(containsString("'Billy' has enabled two-factor authentication"))); + } +}