#1141 Split TOTP permissions for add/remove, refactor TOTP services

- Split TotpService further into GenerateTotpService and TotpAuthenticator, which wraps the GoogleAuthenticator impl
- Add missing tests for the services
- Change GenerateTotpService's interface to behave like a collection for more intuitive method behavior
This commit is contained in:
ljacqu
2018-03-10 16:21:53 +01:00
parent e72d5d5e81
commit eb9cd31a65
17 changed files with 478 additions and 228 deletions
@@ -0,0 +1,69 @@
package fr.xephi.authme.security.totp;
import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.security.totp.TotpAuthenticator.TotpGenerationResult;
import fr.xephi.authme.util.expiring.ExpiringMap;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.concurrent.TimeUnit;
/**
* Handles the generation of new TOTP secrets for players.
*/
public class GenerateTotpService implements HasCleanup {
private static final int NEW_TOTP_KEY_EXPIRATION_MINUTES = 5;
private final ExpiringMap<String, TotpGenerationResult> totpKeys;
@Inject
private TotpAuthenticator totpAuthenticator;
GenerateTotpService() {
this.totpKeys = new ExpiringMap<>(NEW_TOTP_KEY_EXPIRATION_MINUTES, TimeUnit.MINUTES);
}
/**
* Generates a new TOTP key and returns the corresponding QR code.
*
* @param player the player to save the TOTP key for
* @return TOTP generation result
*/
public TotpGenerationResult generateTotpKey(Player player) {
TotpGenerationResult credentials = totpAuthenticator.generateTotpKey(player);
totpKeys.put(player.getName().toLowerCase(), credentials);
return credentials;
}
/**
* Returns the generated TOTP secret of a player, if available and not yet expired.
*
* @param player the player to retrieve the TOTP key for
* @return TOTP generation result
*/
public TotpGenerationResult getGeneratedTotpKey(Player player) {
return totpKeys.get(player.getName().toLowerCase());
}
public void removeGenerateTotpKey(Player player) {
totpKeys.remove(player.getName().toLowerCase());
}
/**
* Returns whether the given totp code is correct for the generated TOTP key of the player.
*
* @param player the player to verify the code for
* @param totpCode the totp code to verify with the generated secret
* @return true if the input code is correct, false if the code is invalid or no unexpired totp key is available
*/
public boolean isTotpCodeCorrectForGeneratedTotpKey(Player player, String totpCode) {
TotpGenerationResult totpDetails = totpKeys.get(player.getName().toLowerCase());
return totpDetails != null && totpAuthenticator.checkCode(totpDetails.getTotpKey(), totpCode);
}
@Override
public void performCleanup() {
totpKeys.removeExpiredEntries();
}
}
@@ -0,0 +1,67 @@
package fr.xephi.authme.security.totp;
import com.google.common.annotations.VisibleForTesting;
import com.warrenstrange.googleauth.GoogleAuthenticator;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator;
import com.warrenstrange.googleauth.IGoogleAuthenticator;
import fr.xephi.authme.service.BukkitService;
import org.bukkit.entity.Player;
import javax.inject.Inject;
/**
* Provides rudimentary TOTP functions (wraps third-party TOTP implementation).
*/
public class TotpAuthenticator {
private final IGoogleAuthenticator authenticator;
private final BukkitService bukkitService;
@Inject
TotpAuthenticator(BukkitService bukkitService) {
this(new GoogleAuthenticator(), bukkitService);
}
@VisibleForTesting
TotpAuthenticator(IGoogleAuthenticator authenticator, BukkitService bukkitService) {
this.authenticator = authenticator;
this.bukkitService = bukkitService;
}
public boolean checkCode(String totpKey, String inputCode) {
try {
Integer totpCode = Integer.valueOf(inputCode);
return authenticator.authorize(totpKey, totpCode);
} catch (NumberFormatException e) {
// ignore
}
return false;
}
public TotpGenerationResult generateTotpKey(Player player) {
GoogleAuthenticatorKey credentials = authenticator.createCredentials();
String qrCodeUrl = GoogleAuthenticatorQRGenerator.getOtpAuthURL(
bukkitService.getIp(), player.getName(), credentials);
return new TotpGenerationResult(credentials.getKey(), qrCodeUrl);
}
public static final class TotpGenerationResult {
private final String totpKey;
private final String authenticatorQrCodeUrl;
TotpGenerationResult(String totpKey, String authenticatorQrCodeUrl) {
this.totpKey = totpKey;
this.authenticatorQrCodeUrl = authenticatorQrCodeUrl;
}
public String getTotpKey() {
return totpKey;
}
public String getAuthenticatorQrCodeUrl() {
return authenticatorQrCodeUrl;
}
}
}
@@ -0,0 +1,18 @@
package fr.xephi.authme.security.totp;
import fr.xephi.authme.data.auth.PlayerAuth;
import javax.inject.Inject;
/**
* Service for TOTP actions.
*/
public class TotpService {
@Inject
private TotpAuthenticator totpAuthenticator;
public boolean verifyCode(PlayerAuth auth, String totpCode) {
return totpAuthenticator.checkCode(auth.getTotpKey(), totpCode);
}
}