package password.pwm.svc.token; import password.pwm.PwmApplication; import password.pwm.bean.SessionLabel; import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.error.PwmException; import password.pwm.error.PwmOperationalException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.svc.PwmService; import password.pwm.util.DataStore; import password.pwm.util.java.ClosableIterator; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; import java.time.Instant; import java.util.concurrent.TimeUnit; public class DataStoreTokenMachine implements TokenMachine { private static final PwmLogger LOGGER = PwmLogger.forClass(DataStoreTokenMachine.class); private final TokenService tokenService; private final TimeDuration maxTokenPurgeAge; private final DataStore dataStore; private final PwmApplication pwmApplication; DataStoreTokenMachine( final PwmApplication pwmApplication, final TokenService tokenService, final DataStore dataStore ) { this.pwmApplication = pwmApplication; this.tokenService = tokenService; this.dataStore = dataStore; final Configuration configuration = pwmApplication.getConfig(); final long maxTokenAgeMS = configuration.readSettingAsLong(PwmSetting.TOKEN_LIFETIME) * 1000; this.maxTokenPurgeAge = new TimeDuration(maxTokenAgeMS, TimeUnit.MILLISECONDS); } @Override public TokenKey keyFromKey(final String key) throws PwmUnrecoverableException { return StoredTokenKey.fromKeyValue(pwmApplication, key); } @Override public TokenKey keyFromStoredHash(final String storedHash) { return StoredTokenKey.fromStoredHash(storedHash); } public void cleanup() throws PwmUnrecoverableException, PwmOperationalException { if (size() < 1) { return; } purgeOutdatedTokens(); } private void purgeOutdatedTokens() throws PwmUnrecoverableException, PwmOperationalException { final Instant startTime = Instant.now(); LOGGER.trace("beginning purge cycle; database size = " + size()); try (ClosableIterator<String> keyIterator = dataStore.iterator()) { while (tokenService.status() == PwmService.STATUS.OPEN && keyIterator.hasNext()) { final String storedHash = keyIterator.next(); final TokenKey loopKey = keyFromStoredHash(storedHash); retrieveToken(loopKey); // retrieving token tests validity and causes purging } } catch (Exception e) { LOGGER.error("unexpected error while cleaning expired stored tokens: " + e.getMessage()); } LOGGER.trace("completed record purge cycle in " + TimeDuration.fromCurrent(startTime).asCompactString() + "; database size = " + size()); } private boolean testIfTokenNeedsPurging(final TokenPayload theToken) { if (theToken == null) { return false; } final Instant issueDate = theToken.getDate(); if (issueDate == null) { LOGGER.error("retrieved token has no issueDate, marking as purgable: " + JsonUtil.serialize(theToken)); return true; } final TimeDuration duration = TimeDuration.fromCurrent(issueDate); return duration.isLongerThan(maxTokenPurgeAge); } public String generateToken( final SessionLabel sessionLabel, final TokenPayload tokenPayload ) throws PwmUnrecoverableException, PwmOperationalException { return tokenService.makeUniqueTokenForMachine(sessionLabel, this); } public TokenPayload retrieveToken(final TokenKey tokenKey) throws PwmOperationalException, PwmUnrecoverableException { final String storedHash = tokenKey.getStoredHash(); final String storedRawValue = dataStore.get(storedHash); if (storedRawValue != null && storedRawValue.length() > 0 ) { final TokenPayload tokenPayload; try { tokenPayload = tokenService.fromEncryptedString(storedRawValue); } catch (PwmException e) { LOGGER.trace("error while trying to decrypted stored token payload for key '" + storedHash + "', will purge record, error: " + e.getMessage()); dataStore.remove(storedHash); return null; } if (testIfTokenNeedsPurging(tokenPayload)) { LOGGER.trace("stored token key '" + storedHash + "', has an outdated issue date and will be purged"); dataStore.remove(storedHash); } else { return tokenPayload; } } return null; } public void storeToken(final TokenKey tokenKey, final TokenPayload tokenPayload) throws PwmOperationalException, PwmUnrecoverableException { final String rawValue = tokenService.toEncryptedString(tokenPayload); final String storedHash = tokenKey.getStoredHash(); dataStore.put(storedHash, rawValue); } public void removeToken(final TokenKey tokenKey) throws PwmOperationalException, PwmUnrecoverableException { final String storedHash = tokenKey.getStoredHash(); dataStore.remove(storedHash); } public int size() throws PwmOperationalException { return dataStore.size(); } public boolean supportsName() { return true; } }