Github user necouchman commented on a diff in the pull request: https://github.com/apache/guacamole-client/pull/247#discussion_r166056157 --- Diff: extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/CodeUsageTrackingService.java --- @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.totp.user; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.totp.conf.ConfigurationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service for tracking past valid uses of TOTP codes. An internal thread + * periodically walks through records of past codes, removing records which + * should be invalid by their own nature (no longer matching codes generated by + * the secret key). + */ +@Singleton +public class CodeUsageTrackingService { + + /** + * The number of periods during which a previously-used code should remain + * unusable. Once this period has elapsed, the code can be reused again if + * it is otherwise valid. + */ + private static final int INVALID_INTERVAL = 2; + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(CodeUsageTrackingService.class); + + /** + * Executor service which runs the cleanup task. + */ + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); + + /** + * Service for retrieving configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Map of previously-used codes to the timestamp after which the code can + * be used again, providing the TOTP key legitimately generates that code. + */ + private final ConcurrentMap<UsedCode, Long> invalidCodes = + new ConcurrentHashMap<UsedCode, Long>(); + + /** + * Creates a new CodeUsageTrackingService which tracks past valid uses of + * TOTP codes on a per-user basis. + */ + public CodeUsageTrackingService() { + executor.scheduleAtFixedRate(new CodeEvictionTask(), 1, 1, TimeUnit.MINUTES); + } + + /** + * Task which iterates through all explicitly-invalidated codes, evicting + * those codes which are old enough that they would fail validation against + * the secret key anyway. + */ + private class CodeEvictionTask implements Runnable { + + @Override + public void run() { + + // Get start time of cleanup check + long checkStart = System.currentTimeMillis(); + + // For each code still being tracked, remove those which are old + // enough that they would fail validation against the secret key + Iterator<Map.Entry<UsedCode, Long>> entries = invalidCodes.entrySet().iterator(); + while (entries.hasNext()) { + + Map.Entry<UsedCode, Long> entry = entries.next(); + long invalidUntil = entry.getValue(); + + // If code is sufficiently old, evict it and check the next one + if (checkStart >= invalidUntil) + entries.remove(); + + } + + // Log completion and duration + logger.debug("TOTP tracking cleanup check completed in {} ms.", + System.currentTimeMillis() - checkStart); + + } + + } + + /** + * A valid TOTP code which was previously used by a particular user. + */ + private class UsedCode { + + /** + * The username of the user which previously used this code. + */ + private final String username; + + /** + * The valid code given by the user. + */ + private final String code; + + /** + * Creates a new UsedCode which records the given code as having been + * used by the given user. + * + * @param username + * The username of the user which previously used the given code. + * + * @param code + * The valid code given by the user. + */ + public UsedCode(String username, String code) { + this.username = username; + this.code = code; + } + + /** + * Returns the username of the user which previously used the code + * associated with this UsedCode. + * + * @return + * The username of the user which previously used this code. + */ + public String getUsername() { + return username; + } + + /** + * Returns the valid code given by the user when this UsedCode was + * created. + * + * @return + * The valid code given by the user. + */ + public String getCode() { + return code; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 79 * hash + this.username.hashCode(); + hash = 79 * hash + this.code.hashCode(); --- End diff -- ok
---