Github user mike-jumper commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/247#discussion_r166051021
--- 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 --
> Maybe this is part of the IETF reference implementation, but is there
some significance to the values 7 and 79, here?
Nope, it's not specific to the IETF implementation. It's a generic hash
function for the values within this class. The primes 7 and 79 were arbitrarily
chosen and have no meaning other than to generally distribute the values well.
---