#949 Create expiring map type + integrate it into recovery code service
This commit is contained in:
@@ -1,38 +1,38 @@
|
||||
package fr.xephi.authme.service;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import fr.xephi.authme.initialization.HasCleanup;
|
||||
import fr.xephi.authme.initialization.SettingsDependent;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.SecuritySettings;
|
||||
import fr.xephi.authme.util.ExpiringMap;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static fr.xephi.authme.settings.properties.SecuritySettings.RECOVERY_CODE_HOURS_VALID;
|
||||
import static fr.xephi.authme.util.Utils.MILLIS_PER_HOUR;
|
||||
|
||||
/**
|
||||
* Manager for recovery codes.
|
||||
*/
|
||||
public class RecoveryCodeService implements SettingsDependent {
|
||||
|
||||
private Map<String, ExpiringEntry> recoveryCodes = new ConcurrentHashMap<>();
|
||||
public class RecoveryCodeService implements SettingsDependent, HasCleanup {
|
||||
|
||||
private final ExpiringMap<String, String> recoveryCodes;
|
||||
private int recoveryCodeLength;
|
||||
private long recoveryCodeExpirationMillis;
|
||||
private int recoveryCodeExpiration;
|
||||
|
||||
@Inject
|
||||
RecoveryCodeService(Settings settings) {
|
||||
reload(settings);
|
||||
recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH);
|
||||
recoveryCodeExpiration = settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID);
|
||||
recoveryCodes = new ExpiringMap<>(recoveryCodeExpiration, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether recovery codes are enabled or not
|
||||
*/
|
||||
public boolean isRecoveryCodeNeeded() {
|
||||
return recoveryCodeLength > 0 && recoveryCodeExpirationMillis > 0;
|
||||
return recoveryCodeLength > 0 && recoveryCodeExpiration > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,7 +43,7 @@ public class RecoveryCodeService implements SettingsDependent {
|
||||
*/
|
||||
public String generateCode(String player) {
|
||||
String code = RandomStringUtils.generateHex(recoveryCodeLength);
|
||||
recoveryCodes.put(player, new ExpiringEntry(code, System.currentTimeMillis() + recoveryCodeExpirationMillis));
|
||||
recoveryCodes.put(player, code);
|
||||
return code;
|
||||
}
|
||||
|
||||
@@ -55,11 +55,8 @@ public class RecoveryCodeService implements SettingsDependent {
|
||||
* @return true if the code matches and has not expired, false otherwise
|
||||
*/
|
||||
public boolean isCodeValid(String player, String code) {
|
||||
ExpiringEntry entry = recoveryCodes.get(player);
|
||||
if (entry != null) {
|
||||
return code != null && code.equals(entry.getCode());
|
||||
}
|
||||
return false;
|
||||
String storedCode = recoveryCodes.get(player);
|
||||
return storedCode != null && storedCode.equals(code);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,26 +71,12 @@ public class RecoveryCodeService implements SettingsDependent {
|
||||
@Override
|
||||
public void reload(Settings settings) {
|
||||
recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH);
|
||||
recoveryCodeExpirationMillis = settings.getProperty(RECOVERY_CODE_HOURS_VALID) * MILLIS_PER_HOUR;
|
||||
recoveryCodeExpiration = settings.getProperty(RECOVERY_CODE_HOURS_VALID);
|
||||
recoveryCodes.setExpiration(recoveryCodeExpiration, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry with an expiration.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final class ExpiringEntry {
|
||||
|
||||
private final String code;
|
||||
private final long expiration;
|
||||
|
||||
ExpiringEntry(String code, long expiration) {
|
||||
this.code = code;
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
String getCode() {
|
||||
return System.currentTimeMillis() < expiration ? code : null;
|
||||
}
|
||||
@Override
|
||||
public void performCleanup() {
|
||||
recoveryCodes.removeExpiredEntries();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Map with expiring entries. Following a configured amount of time after
|
||||
* an entry has been inserted, the map will act as if the entry does not
|
||||
* exist.
|
||||
* <p>
|
||||
* Time starts counting directly after insertion. Inserting a new entry with
|
||||
* a key that already has a value will "reset" the expiration. Although the
|
||||
* expiration can be redefined later on, only entries which are inserted
|
||||
* afterwards will use the new expiration.
|
||||
* <p>
|
||||
* An expiration of {@code <= 0} will make the map expire all entries
|
||||
* immediately after insertion. Note that the map does not remove expired
|
||||
* entries automatically; this is only done when calling
|
||||
* {@link #removeExpiredEntries()}.
|
||||
*
|
||||
* @param <K> the key type
|
||||
* @param <V> the value type
|
||||
*/
|
||||
public class ExpiringMap<K, V> {
|
||||
|
||||
private final Map<K, ExpiringEntry<V>> entries = new ConcurrentHashMap<>();
|
||||
private long expirationMillis;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param duration the duration of time after which entries expire
|
||||
* @param unit the time unit in which {@code duration} is expressed
|
||||
*/
|
||||
public ExpiringMap(long duration, TimeUnit unit) {
|
||||
setExpiration(duration, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with the given key,
|
||||
* if available and not expired.
|
||||
*
|
||||
* @param key the key to look up
|
||||
* @return the associated value, or {@code null} if not available
|
||||
*/
|
||||
public V get(K key) {
|
||||
ExpiringEntry<V> value = entries.get(key);
|
||||
return value == null ? null : value.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a value for the given key. Overwrites a previous value
|
||||
* for the key if it exists.
|
||||
*
|
||||
* @param key the key to insert a value for
|
||||
* @param value the value to insert
|
||||
*/
|
||||
public void put(K key, V value) {
|
||||
long expiration = System.currentTimeMillis() + expirationMillis;
|
||||
entries.put(key, new ExpiringEntry<>(value, expiration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the value for the given key, if available.
|
||||
*
|
||||
* @param key the key to remove the value for
|
||||
*/
|
||||
public void remove(K key) {
|
||||
entries.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all entries which have expired from the internal structure.
|
||||
*/
|
||||
public void removeExpiredEntries() {
|
||||
entries.entrySet().removeIf(entry -> System.currentTimeMillis() > entry.getValue().getExpiration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new expiration duration. Note that already present entries
|
||||
* will still make use of the old expiration.
|
||||
*
|
||||
* @param duration the duration of time after which entries expire
|
||||
* @param unit the time unit in which {@code duration} is expressed
|
||||
*/
|
||||
public void setExpiration(long duration, TimeUnit unit) {
|
||||
this.expirationMillis = unit.toMillis(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class holding a value paired with an expiration timestamp.
|
||||
*
|
||||
* @param <V> the value type
|
||||
*/
|
||||
private static final class ExpiringEntry<V> {
|
||||
|
||||
private final V value;
|
||||
private final long expiration;
|
||||
|
||||
ExpiringEntry(V value, long expiration) {
|
||||
this.value = value;
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
V getValue() {
|
||||
return System.currentTimeMillis() > expiration ? null : value;
|
||||
}
|
||||
|
||||
long getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user