#1141 2FA implementation fixes

- Merge TotpService into TotpAuthenticator
- Add missing tests
- Migrate old 2fa enabled key to new one
This commit is contained in:
ljacqu
2018-05-01 22:49:07 +02:00
parent 29ac3a7022
commit 1e3ed795c1
13 changed files with 356 additions and 79 deletions
@@ -0,0 +1,93 @@
package fr.xephi.authme.command.executable.totp;
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.security.totp.GenerateTotpService;
import fr.xephi.authme.security.totp.TotpAuthenticator.TotpGenerationResult;
import org.bukkit.entity.Player;
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.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Test for {@link AddTotpCommand}.
*/
@RunWith(MockitoJUnitRunner.class)
public class AddTotpCommandTest {
@InjectMocks
private AddTotpCommand addTotpCommand;
@Mock
private GenerateTotpService generateTotpService;
@Mock
private DataSource dataSource;
@Mock
private Messages messages;
@Test
public void shouldHandleNonExistentUser() {
// given
Player player = mockPlayerWithName("bob");
given(dataSource.getAuth("bob")).willReturn(null);
// when
addTotpCommand.runCommand(player, Collections.emptyList());
// then
verify(messages).send(player, MessageKey.REGISTER_MESSAGE);
verifyZeroInteractions(generateTotpService);
}
@Test
public void shouldNotAddCodeForAlreadyExistingTotp() {
// given
Player player = mockPlayerWithName("arend");
PlayerAuth auth = PlayerAuth.builder().name("arend")
.totpKey("TOTP2345").build();
given(dataSource.getAuth("arend")).willReturn(auth);
// when
addTotpCommand.runCommand(player, Collections.emptyList());
// then
verify(messages).send(player, MessageKey.TWO_FACTOR_ALREADY_ENABLED);
verifyZeroInteractions(generateTotpService);
}
@Test
public void shouldGenerateTotpCode() {
// given
Player player = mockPlayerWithName("charles");
PlayerAuth auth = PlayerAuth.builder().name("charles").build();
given(dataSource.getAuth("charles")).willReturn(auth);
TotpGenerationResult generationResult = new TotpGenerationResult(
"777Key214", "http://example.org/qr-code/link");
given(generateTotpService.generateTotpKey(player)).willReturn(generationResult);
// when
addTotpCommand.runCommand(player, Collections.emptyList());
// then
verify(messages).send(player, MessageKey.TWO_FACTOR_CREATE, generationResult.getTotpKey(), generationResult.getAuthenticatorQrCodeUrl());
verify(messages).send(player, MessageKey.TWO_FACTOR_CREATE_CONFIRMATION_REQUIRED);
}
private static Player mockPlayerWithName(String name) {
Player player = mock(Player.class);
given(player.getName()).willReturn(name);
return player;
}
}
@@ -0,0 +1,139 @@
package fr.xephi.authme.command.executable.totp;
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.security.totp.GenerateTotpService;
import fr.xephi.authme.security.totp.TotpAuthenticator.TotpGenerationResult;
import org.bukkit.entity.Player;
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.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Test for {@link ConfirmTotpCommand}.
*/
@RunWith(MockitoJUnitRunner.class)
public class ConfirmTotpCommandTest {
@InjectMocks
private ConfirmTotpCommand command;
@Mock
private GenerateTotpService generateTotpService;
@Mock
private DataSource dataSource;
@Mock
private Messages messages;
@Test
public void shouldAddTotpCodeToUserAfterSuccessfulConfirmation() {
// given
Player player = mock(Player.class);
String playerName = "George";
given(player.getName()).willReturn(playerName);
PlayerAuth auth = PlayerAuth.builder().name(playerName).build();
given(dataSource.getAuth(playerName)).willReturn(auth);
given(generateTotpService.getGeneratedTotpKey(player)).willReturn(new TotpGenerationResult("totp-key", "url-not-relevant"));
String totpCode = "954321";
given(generateTotpService.isTotpCodeCorrectForGeneratedTotpKey(player, totpCode)).willReturn(true);
// when
command.runCommand(player, Collections.singletonList(totpCode));
// then
verify(generateTotpService).isTotpCodeCorrectForGeneratedTotpKey(player, totpCode);
verify(generateTotpService).removeGenerateTotpKey(player);
verify(dataSource).setTotpKey(playerName, "totp-key");
verify(messages).send(player, MessageKey.TWO_FACTOR_ENABLE_SUCCESS);
}
@Test
public void shouldHandleWrongTotpCode() {
// given
Player player = mock(Player.class);
String playerName = "George";
given(player.getName()).willReturn(playerName);
PlayerAuth auth = PlayerAuth.builder().name(playerName).build();
given(dataSource.getAuth(playerName)).willReturn(auth);
given(generateTotpService.getGeneratedTotpKey(player)).willReturn(new TotpGenerationResult("totp-key", "url-not-relevant"));
String totpCode = "754321";
given(generateTotpService.isTotpCodeCorrectForGeneratedTotpKey(player, totpCode)).willReturn(false);
// when
command.runCommand(player, Collections.singletonList(totpCode));
// then
verify(generateTotpService).isTotpCodeCorrectForGeneratedTotpKey(player, totpCode);
verify(generateTotpService, never()).removeGenerateTotpKey(any(Player.class));
verify(dataSource, only()).getAuth(playerName);
verify(messages).send(player, MessageKey.TWO_FACTOR_ENABLE_ERROR_WRONG_CODE);
}
@Test
public void shouldHandleMissingTotpKey() {
// given
Player player = mock(Player.class);
String playerName = "George";
given(player.getName()).willReturn(playerName);
PlayerAuth auth = PlayerAuth.builder().name(playerName).build();
given(dataSource.getAuth(playerName)).willReturn(auth);
given(generateTotpService.getGeneratedTotpKey(player)).willReturn(null);
// when
command.runCommand(player, Collections.singletonList("871634"));
// then
verify(generateTotpService, only()).getGeneratedTotpKey(player);
verify(dataSource, only()).getAuth(playerName);
verify(messages).send(player, MessageKey.TWO_FACTOR_ENABLE_ERROR_NO_CODE);
}
@Test
public void shouldStopForAlreadyExistingTotpKeyOnAccount() {
// given
Player player = mock(Player.class);
String playerName = "George";
given(player.getName()).willReturn(playerName);
PlayerAuth auth = PlayerAuth.builder().name(playerName).totpKey("A987234").build();
given(dataSource.getAuth(playerName)).willReturn(auth);
// when
command.runCommand(player, Collections.singletonList("871634"));
// then
verify(dataSource, only()).getAuth(playerName);
verifyZeroInteractions(generateTotpService);
verify(messages).send(player, MessageKey.TWO_FACTOR_ALREADY_ENABLED);
}
@Test
public void shouldHandleMissingAuthAccount() {
// given
Player player = mock(Player.class);
String playerName = "George";
given(player.getName()).willReturn(playerName);
given(dataSource.getAuth(playerName)).willReturn(null);
// when
command.runCommand(player, Collections.singletonList("984685"));
// then
verify(dataSource, only()).getAuth(playerName);
verifyZeroInteractions(generateTotpService);
verify(messages).send(player, MessageKey.REGISTER_MESSAGE);
}
}
@@ -0,0 +1,47 @@
package fr.xephi.authme.command.executable.totp;
import fr.xephi.authme.command.CommandMapper;
import fr.xephi.authme.command.FoundCommandResult;
import fr.xephi.authme.command.help.HelpProvider;
import org.bukkit.command.CommandSender;
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.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Test for {@link TotpBaseCommand}.
*/
@RunWith(MockitoJUnitRunner.class)
public class TotpBaseCommandTest {
@InjectMocks
private TotpBaseCommand command;
@Mock
private CommandMapper mapper;
@Mock
private HelpProvider helpProvider;
@Test
public void shouldOutputHelp() {
// given
CommandSender sender = mock(CommandSender.class);
FoundCommandResult mappingResult = mock(FoundCommandResult.class);
given(mapper.mapPartsToCommand(sender, Collections.singletonList("totp"))).willReturn(mappingResult);
// when
command.executeCommand(sender, Collections.emptyList());
// then
verify(mapper).mapPartsToCommand(sender, Collections.singletonList("totp"));
verify(helpProvider).outputHelp(sender, mappingResult, HelpProvider.SHOW_CHILDREN);
}
}
@@ -100,6 +100,23 @@ public class MessageUpdaterTest {
equalTo("seconds in plural"));
}
@Test
public void shouldPerformNewerMigrations() throws IOException {
// given
File messagesFile = temporaryFolder.newFile();
Files.copy(TestHelper.getJarFile(TestHelper.PROJECT_ROOT + "message/messages_test2.yml"), messagesFile);
// when
boolean wasChanged = messageUpdater.migrateAndSave(messagesFile, "messages/messages_en.yml", "messages/messages_en.yml");
// then
assertThat(wasChanged, equalTo(true));
FileConfiguration configuration = YamlConfiguration.loadConfiguration(messagesFile);
assertThat(configuration.getString(MessageKey.TWO_FACTOR_CREATE.getKey()), equalTo("Old 2fa create text"));
assertThat(configuration.getString(MessageKey.WRONG_PASSWORD.getKey()), equalTo("test2 - wrong password")); // from pre-5.5 key
assertThat(configuration.getString(MessageKey.SECOND.getKey()), equalTo("second")); // from messages_en.yml
}
@Test
public void shouldHaveAllKeysInConfigurationData() {
// given
@@ -1,6 +1,7 @@
package fr.xephi.authme.security.totp;
import com.warrenstrange.googleauth.IGoogleAuthenticator;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.security.totp.TotpAuthenticator.TotpGenerationResult;
import fr.xephi.authme.service.BukkitService;
import org.bukkit.entity.Player;
@@ -86,6 +87,25 @@ public class TotpAuthenticatorTest {
verifyZeroInteractions(googleAuthenticator);
}
@Test
public void shouldVerifyCode() {
// given
String totpKey = "ASLO43KDF2J";
PlayerAuth auth = PlayerAuth.builder()
.name("Maya")
.totpKey(totpKey)
.build();
String inputCode = "408435";
given(totpAuthenticator.checkCode(totpKey, inputCode)).willReturn(true);
// when
boolean result = totpAuthenticator.checkCode(auth, inputCode);
// then
assertThat(result, equalTo(true));
verify(googleAuthenticator).authorize(totpKey, 408435);
}
private final class TotpAuthenticatorTestImpl extends TotpAuthenticator {
TotpAuthenticatorTestImpl(BukkitService bukkitService) {
@@ -1,46 +0,0 @@
package fr.xephi.authme.security.totp;
import fr.xephi.authme.data.auth.PlayerAuth;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
/**
* Test for {@link TotpService}.
*/
@RunWith(MockitoJUnitRunner.class)
public class TotpServiceTest {
@InjectMocks
private TotpService totpService;
@Mock
private TotpAuthenticator totpAuthenticator;
@Test
public void shouldVerifyCode() {
// given
String totpKey = "ASLO43KDF2J";
PlayerAuth auth = PlayerAuth.builder()
.name("Maya")
.totpKey(totpKey)
.build();
String inputCode = "408435";
given(totpAuthenticator.checkCode(totpKey, inputCode)).willReturn(true);
// when
boolean result = totpService.verifyCode(auth, inputCode);
// then
assertThat(result, equalTo(true));
verify(totpAuthenticator).checkCode(totpKey, inputCode);
}
}
@@ -4,3 +4,5 @@ unknown_user: 'Message from test2'
login: 'test2 - login'
not_logged_in: 'test2 - not logged in'
wrong_pwd: 'test2 - wrong password'
misc:
two_factor_create: 'Old 2fa create text'