You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by vn...@apache.org on 2018/02/05 18:04:19 UTC

[01/21] guacamole-client git commit: GUACAMOLE-96: Handle enrollment via QR code for unconfirmed users.

Repository: guacamole-client
Updated Branches:
  refs/heads/master e68013123 -> 9ee43e656


GUACAMOLE-96: Handle enrollment via QR code for unconfirmed users.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/170a11bf
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/170a11bf
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/170a11bf

Branch: refs/heads/master
Commit: 170a11bf2a17a98b7ce292ecc53c947b755bad14
Parents: 8ac8fec
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 14:01:39 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:17 2018 -0800

----------------------------------------------------------------------
 extensions/guacamole-auth-totp/pom.xml          |  20 ++
 .../guacamole-auth-totp/src/licenses/LICENSE    |   9 +
 .../src/licenses/bundled/zxing-3.3.1/LICENSE    | 245 +++++++++++++++++++
 .../apache/guacamole/auth/totp/UserTOTPKey.java |  28 ++-
 .../auth/totp/UserVerificationService.java      |  21 +-
 .../auth/totp/form/AuthenticationCodeField.java | 141 ++++++++++-
 .../templates/authenticationCodeField.html      |  12 +-
 7 files changed, 465 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/170a11bf/extensions/guacamole-auth-totp/pom.xml
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/pom.xml b/extensions/guacamole-auth-totp/pom.xml
index 17aff05..af44455 100644
--- a/extensions/guacamole-auth-totp/pom.xml
+++ b/extensions/guacamole-auth-totp/pom.xml
@@ -249,6 +249,26 @@
             <scope>test</scope>
         </dependency>
 
+        <!-- ZXing - Barcode library -->
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.3.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.3.1</version>
+        </dependency>
+
+        <!-- Guacamole depends on an implementation of JAX-WS -->
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+            <version>2.0</version>
+            <scope>provided</scope>
+        </dependency>
+
     </dependencies>
 
 </project>

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/170a11bf/extensions/guacamole-auth-totp/src/licenses/LICENSE
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/licenses/LICENSE b/extensions/guacamole-auth-totp/src/licenses/LICENSE
index 37d934c..8a66d23 100644
--- a/extensions/guacamole-auth-totp/src/licenses/LICENSE
+++ b/extensions/guacamole-auth-totp/src/licenses/LICENSE
@@ -283,3 +283,12 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+
+ZXing Barcode Scanning Library (https://github.com/zxing/zxing/)
+----------------------------------------------------------------
+
+    Version: 3.3.1
+    From: 'ZXing authors' (https://github.com/zxing/zxing/blob/zxing-3.3.1/AUTHORS)
+    License(s):
+        Apache v2.0 (bundled/zxing-3.3.1/LICENSE)
+

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/170a11bf/extensions/guacamole-auth-totp/src/licenses/bundled/zxing-3.3.1/LICENSE
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/zxing-3.3.1/LICENSE b/extensions/guacamole-auth-totp/src/licenses/bundled/zxing-3.3.1/LICENSE
new file mode 100644
index 0000000..510991e
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/licenses/bundled/zxing-3.3.1/LICENSE
@@ -0,0 +1,245 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
+
+========================================================================
+jai-imageio
+========================================================================
+
+Copyright (c) 2005 Sun Microsystems, Inc.
+Copyright © 2010-2014 University of Manchester
+Copyright © 2010-2015 Stian Soiland-Reyes
+Copyright © 2015 Peter Hull
+All Rights Reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistribution of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistribution in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in
+  the documentation and/or other materials provided with the
+  distribution.
+
+Neither the name of Sun Microsystems, Inc. or the names of
+contributors may be used to endorse or promote products derived
+from this software without specific prior written permission.
+
+This software is provided "AS IS," without a warranty of any
+kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+You acknowledge that this software is not designed or intended for
+use in the design, construction, operation or maintenance of any
+nuclear facility.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/170a11bf/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
index d93d253..3de3785 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
@@ -33,6 +33,11 @@ public class UserTOTPKey {
     private static final Random RANDOM = new SecureRandom();
 
     /**
+     * The username of the user associated with this key.
+     */
+    private final String username;
+
+    /**
      * Whether the associated secret key has been confirmed by the user. A key
      * is confirmed once the user has successfully entered a valid TOTP
      * derived from that key.
@@ -63,17 +68,23 @@ public class UserTOTPKey {
      * Creates a new, unconfirmed, randomly-generated TOTP key having the given
      * length.
      *
+     * @param username
+     *     The username of the user associated with this key.
+     *
      * @param length
      *     The length of the key to generate, in bytes.
      */
-    public UserTOTPKey(int length) {
-        this(generateBytes(length), false);
+    public UserTOTPKey(String username, int length) {
+        this(username, generateBytes(length), false);
     }
 
     /**
      * Creates a new UserTOTPKey containing the given key and having the given
      * confirmed state.
      *
+     * @param username
+     *     The username of the user associated with this key.
+     *
      * @param secret
      *     The raw binary secret key to be used to generate TOTP codes.
      *
@@ -82,12 +93,23 @@ public class UserTOTPKey {
      *     successfully generate the corresponding TOTP codes (the user has
      *     been "enrolled"), false otherwise.
      */
-    public UserTOTPKey(byte[] secret, boolean confirmed) {
+    public UserTOTPKey(String username, byte[] secret, boolean confirmed) {
+        this.username = username;
         this.confirmed = confirmed;
         this.secret = secret;
     }
 
     /**
+     * Returns the username of the user associated with this key.
+     *
+     * @return
+     *     The username of the user associated with this key.
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
      * Returns the raw binary secret key to be used to generate TOTP codes.
      *
      * @return

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/170a11bf/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
index da24995..a44b6ee 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
@@ -76,6 +76,9 @@ public class UserVerificationService {
      * @param context
      *     The UserContext of the user whose TOTP key should be retrieved.
      *
+     * @param username
+     *     The username of the user associated with the given UserContext.
+     *
      * @return
      *     The TOTP key associated with the user having the given UserContext,
      *     or null if the extension storing the user does not support storage
@@ -85,7 +88,8 @@ public class UserVerificationService {
      *     If a new key is generated, but the extension storing the associated
      *     user fails while updating the user account.
      */
-    private UserTOTPKey getKey(UserContext context) throws GuacamoleException {
+    private UserTOTPKey getKey(UserContext context,
+            String username) throws GuacamoleException {
 
         // Retrieve attributes from current user
         User self = context.self();
@@ -96,7 +100,7 @@ public class UserVerificationService {
         if (secret == null) {
 
             // Generate random key for user
-            UserTOTPKey generated = new UserTOTPKey(TOTPGenerator.Mode.SHA1.getRecommendedKeyLength());
+            UserTOTPKey generated = new UserTOTPKey(username, TOTPGenerator.Mode.SHA1.getRecommendedKeyLength());
             if (setKey(context, generated))
                 return generated;
 
@@ -121,7 +125,7 @@ public class UserVerificationService {
 
         // Otherwise, parse value from attributes
         boolean confirmed = "true".equals(attributes.get(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME));
-        return new UserTOTPKey(key, confirmed);
+        return new UserTOTPKey(username, key, confirmed);
 
     }
 
@@ -205,7 +209,7 @@ public class UserVerificationService {
             return;
 
         // Ignore users which do not have an associated key
-        UserTOTPKey key = getKey(context);
+        UserTOTPKey key = getKey(context, username);
         if (key == null)
             return;
 
@@ -219,7 +223,14 @@ public class UserVerificationService {
         // If no TOTP provided, request one
         if (code == null) {
 
-            // FIXME: Handle key.isConfirmed() for initial prompt
+            // If the user hasn't completed enrollment, request that they do
+            if (!key.isConfirmed())
+                throw new GuacamoleInsufficientCredentialsException(
+                        "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo(
+                            Collections.<Field>singletonList(new AuthenticationCodeField(key))
+                        ));
+
+            // Otherwise simply request the user's authentication code
             throw new GuacamoleInsufficientCredentialsException(
                     "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo(
                         Collections.<Field>singletonList(new AuthenticationCodeField())

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/170a11bf/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
index 8119657..1be5f27 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
@@ -19,7 +19,21 @@
 
 package org.apache.guacamole.auth.totp.form;
 
+import com.google.common.io.BaseEncoding;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.WriterException;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import javax.ws.rs.core.UriBuilder;
+import javax.xml.bind.DatatypeConverter;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.auth.totp.UserTOTPKey;
 import org.apache.guacamole.form.Field;
+import org.codehaus.jackson.annotate.JsonProperty;
 
 /**
  * Field which prompts the user for an authentication code generated via TOTP.
@@ -38,11 +52,134 @@ public class AuthenticationCodeField extends Field {
     private static final String FIELD_TYPE_NAME = "GUAC_TOTP_CODE";
 
     /**
+     * The width of QR codes to generate, in pixels.
+     */
+    private static final int QR_CODE_WIDTH = 256;
+
+    /**
+     * The height of QR codes to generate, in pixels.
+     */
+    private static final int QR_CODE_HEIGHT = 256;
+
+    /**
+     * BaseEncoding which encodes/decodes base32.
+     */
+    private static final BaseEncoding BASE32 = BaseEncoding.base32();
+
+    /**
+     * The TOTP key to expose to the user for the sake of enrollment, if any.
+     * If no such key should be exposed to the user, this will be null.
+     */
+    private final UserTOTPKey key;
+
+    /**
      * Creates a new field which prompts the user for an authentication code
-     * generated via TOTP.
+     * generated via TOTP, and provide the user with their TOTP key to
+     * facilitate enrollment.
+     *
+     * @param key
+     *     The TOTP key to expose to the user for the sake of enrollment.
      */
-    public AuthenticationCodeField() {
+    public AuthenticationCodeField(UserTOTPKey key) {
         super(PARAMETER_NAME, FIELD_TYPE_NAME);
+        this.key = key;
+    }
+
+    /**
+     * Creates a new field which prompts the user for an authentication code
+     * generated via TOTP. The user's TOTP key is not exposed for enrollment.
+     */
+    public AuthenticationCodeField() {
+        this(null);
+    }
+
+    /**
+     * Returns the "otpauth" URI for the secret key used to generate TOTP codes
+     * for the current user. If the secret key is not being exposed to
+     * facilitate enrollment, null is returned.
+     *
+     * @return
+     *     The "otpauth" URI for the secret key used to generate TOTP codes
+     *     for the current user, or null is the secret ket is not being exposed
+     *     to facilitate enrollment.
+     *
+     * @throws GuacamoleException
+     *     If the configuration information required for generating the key URI
+     *     cannot be read from guacamole.properties.
+     */
+    @JsonProperty("keyUri")
+    public URI getKeyURI() throws GuacamoleException {
+
+        // Do not generate a key URI if no key is being exposed
+        if (key == null)
+            return null;
+
+        // FIXME: Pull from configuration
+        String issuer = "Some Issuer";
+        String algorithm = "SHA1";
+        String digits = "6";
+        String period = "30";
+
+        // Format "otpauth" URL (see https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
+        return UriBuilder.fromUri("otpauth://totp/")
+                .path(issuer + ":" + key.getUsername())
+                .queryParam("secret", BASE32.encode(key.getSecret()))
+                .queryParam("issuer", issuer)
+                .queryParam("algorithm", algorithm)
+                .queryParam("digits", digits)
+                .queryParam("period", period)
+                .build();
+
+    }
+
+    /**
+     * Returns the URL of a QR code describing the user's TOTP key and
+     * configuration. If the key is not being exposed for enrollment, null is
+     * returned.
+     *
+     * @return 
+     *     The URL of a QR code describing the user's TOTP key and
+     *     configuration, or null if the key is not being exposed for
+     *     enrollment.
+     *
+     * @throws GuacamoleException
+     *     If the configuration information required for generating the QR code
+     *     cannot be read from guacamole.properties.
+     */
+    @JsonProperty("qrCode")
+    public String getQRCode() throws GuacamoleException {
+
+        // Do not generate a QR code if no key is being exposed
+        URI keyURI = getKeyURI();
+        if (keyURI == null)
+            return null;
+
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        try {
+
+            // Create QR code writer
+            QRCodeWriter writer = new QRCodeWriter();
+            BitMatrix matrix = writer.encode(keyURI.toString(),
+                    BarcodeFormat.QR_CODE, QR_CODE_WIDTH, QR_CODE_HEIGHT);
+
+            // Produce PNG image of TOTP key text
+            MatrixToImageWriter.writeToStream(matrix, "PNG", stream);
+
+        }
+        catch (WriterException e) {
+            throw new IllegalArgumentException("QR code could not be "
+                    + "generated for TOTP key.", e);
+        }
+        catch (IOException e) {
+            throw new IllegalStateException("Image stream of QR code could "
+                    + "not be written.", e);
+        }
+
+        // Return data URI for generated image
+        return "data:image/png;base64,"
+                + DatatypeConverter.printBase64Binary(stream.toByteArray());
+
     }
 
 }

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/170a11bf/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
index 4e7fb0f..ae155d8 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
+++ b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
@@ -1,3 +1,13 @@
 <div class="totp-code-field">
-    <input type="text" ng-model="model" autocorrect="off" autocapitalize="off"/>
+
+    <!-- QR Code (if available) -->
+    <div class="totp-qr-code" ng-show="field.qrCode">
+        <img ng-src="{{field.qrCode}}">
+    </div>
+
+    <!-- Field for entry of the current TOTP code -->
+    <div class="totp-code">
+        <input type="text" ng-model="model" autocorrect="off" autocapitalize="off"/>
+    </div>
+
 </div>


[09/21] guacamole-client git commit: GUACAMOLE-96: Allow users to enter either the current or previous TOTP codes.

Posted by vn...@apache.org.
GUACAMOLE-96: Allow users to enter either the current or previous TOTP codes.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/78c398f4
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/78c398f4
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/78c398f4

Branch: refs/heads/master
Commit: 78c398f45d484ba4935870f6cd5a146a6f9d2f16
Parents: 8dd5537
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 01:19:39 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:17 2018 -0800

----------------------------------------------------------------------
 .../auth/totp/UserVerificationService.java      |  2 +-
 .../apache/guacamole/totp/TOTPGenerator.java    | 29 ++++++++++++++++++++
 2 files changed, 30 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/78c398f4/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
index cb73730..823c5ef 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
@@ -142,7 +142,7 @@ public class UserVerificationService {
             // Verify provided TOTP against value produced by generator
             byte[] key = BASE32.decode(encodedKey);
             TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA1, 6);
-            if (code.equals(totp.generate()))
+            if (code.equals(totp.generate()) || code.equals(totp.previous()))
                 return;
 
         }

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/78c398f4/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java
index 004c23b..b8c0d95 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java
@@ -399,4 +399,33 @@ public class TOTPGenerator {
         return generate(System.currentTimeMillis() / 1000);
     }
 
+    /**
+     * Returns the TOTP code which would have been generated immediately prior
+     * to the code returned by invoking generate() with the given timestamp.
+     *
+     * @param time
+     *     The absolute timestamp to use to generate the TOTP code, in seconds
+     *     since midnight, 1970-01-01, UTC (UNIX epoch).
+     *
+     * @return
+     *     The TOTP code which would have been generated immediately prior to
+     *     the the code returned by invoking generate() with the given
+     *     timestamp.
+     */
+    public String previous(long time) {
+        return generate(Math.max(startTime, time - timeStep));
+    }
+
+    /**
+     * Returns the TOTP code which would have been generated immediately prior
+     * to the code currently being returned by generate().
+     *
+     * @return
+     *     The TOTP code which would have been generated immediately prior to
+     *     the code currently being returned by generate().
+     */
+    public String previous() {
+        return previous(System.currentTimeMillis() / 1000);
+    }
+
 }


[18/21] guacamole-client git commit: GUACAMOLE-96: Add configuration parameters for details of TOTP generation.

Posted by vn...@apache.org.
GUACAMOLE-96: Add configuration parameters for details of TOTP generation.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/a422fdf9
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/a422fdf9
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/a422fdf9

Branch: refs/heads/master
Commit: a422fdf9c235e898d5c05499cef638501beb6508
Parents: 170a11b
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 14:29:03 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:18 2018 -0800

----------------------------------------------------------------------
 .../totp/TOTPAuthenticationProviderModule.java  |   2 +
 .../auth/totp/UserVerificationService.java      |  33 +++-
 .../auth/totp/conf/ConfigurationService.java    | 161 +++++++++++++++++++
 .../auth/totp/conf/TOTPModeProperty.java        |  62 +++++++
 .../auth/totp/form/AuthenticationCodeField.java |  42 ++---
 5 files changed, 274 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/a422fdf9/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
index 757a412..e72beec 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
@@ -21,6 +21,7 @@ package org.apache.guacamole.auth.totp;
 
 import com.google.inject.AbstractModule;
 import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.auth.totp.conf.ConfigurationService;
 import org.apache.guacamole.environment.Environment;
 import org.apache.guacamole.environment.LocalEnvironment;
 import org.apache.guacamole.net.auth.AuthenticationProvider;
@@ -71,6 +72,7 @@ public class TOTPAuthenticationProviderModule extends AbstractModule {
         bind(Environment.class).toInstance(environment);
 
         // Bind TOTP-specific services
+        bind(ConfigurationService.class);
         bind(UserVerificationService.class);
 
     }

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/a422fdf9/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
index a44b6ee..987d4ca 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
@@ -20,6 +20,8 @@
 package org.apache.guacamole.auth.totp;
 
 import com.google.common.io.BaseEncoding;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
 import java.security.InvalidKeyException;
 import java.util.Collections;
 import java.util.HashMap;
@@ -28,6 +30,7 @@ import javax.servlet.http.HttpServletRequest;
 import org.apache.guacamole.GuacamoleClientException;
 import org.apache.guacamole.GuacamoleException;
 import org.apache.guacamole.GuacamoleUnsupportedException;
+import org.apache.guacamole.auth.totp.conf.ConfigurationService;
 import org.apache.guacamole.auth.totp.form.AuthenticationCodeField;
 import org.apache.guacamole.form.Field;
 import org.apache.guacamole.net.auth.AuthenticatedUser;
@@ -67,6 +70,18 @@ public class UserVerificationService {
     private static final BaseEncoding BASE32 = BaseEncoding.base32();
 
     /**
+     * Service for retrieving configuration information.
+     */
+    @Inject
+    private ConfigurationService confService;
+
+    /**
+     * Provider for AuthenticationCodeField instances.
+     */
+    @Inject
+    private Provider<AuthenticationCodeField> codeFieldProvider;
+
+    /**
      * Retrieves and decodes the base32-encoded TOTP key associated with user
      * having the given UserContext. If no TOTP key is associated with the user,
      * a random key is generated and associated with the user. If the extension
@@ -100,7 +115,8 @@ public class UserVerificationService {
         if (secret == null) {
 
             // Generate random key for user
-            UserTOTPKey generated = new UserTOTPKey(username, TOTPGenerator.Mode.SHA1.getRecommendedKeyLength());
+            TOTPGenerator.Mode mode = confService.getMode();
+            UserTOTPKey generated = new UserTOTPKey(username,mode.getRecommendedKeyLength());
             if (setKey(context, generated))
                 return generated;
 
@@ -223,25 +239,32 @@ public class UserVerificationService {
         // If no TOTP provided, request one
         if (code == null) {
 
+            AuthenticationCodeField field = codeFieldProvider.get();
+
             // If the user hasn't completed enrollment, request that they do
-            if (!key.isConfirmed())
+            if (!key.isConfirmed()) {
+                field.exposeKey(key);
                 throw new GuacamoleInsufficientCredentialsException(
                         "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo(
-                            Collections.<Field>singletonList(new AuthenticationCodeField(key))
+                            Collections.<Field>singletonList(field)
                         ));
+            }
 
             // Otherwise simply request the user's authentication code
             throw new GuacamoleInsufficientCredentialsException(
                     "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo(
-                        Collections.<Field>singletonList(new AuthenticationCodeField())
+                        Collections.<Field>singletonList(field)
                     ));
 
         }
 
         try {
 
+            // Get generator based on user's key and provided configuration
+            TOTPGenerator totp = new TOTPGenerator(key.getSecret(),
+                    confService.getMode(), confService.getDigits());
+
             // Verify provided TOTP against value produced by generator
-            TOTPGenerator totp = new TOTPGenerator(key.getSecret(), TOTPGenerator.Mode.SHA1, 6);
             if (code.equals(totp.generate()) || code.equals(totp.previous())) {
 
                 // Record key as confirmed, if it hasn't already been so recorded

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/a422fdf9/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java
new file mode 100644
index 0000000..8658849
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java
@@ -0,0 +1,161 @@
+/*
+ * 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.conf;
+
+import com.google.inject.Inject;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleServerException;
+import org.apache.guacamole.environment.Environment;
+import org.apache.guacamole.properties.IntegerGuacamoleProperty;
+import org.apache.guacamole.properties.StringGuacamoleProperty;
+import org.apache.guacamole.totp.TOTPGenerator;
+
+/**
+ * Service for retrieving configuration information regarding the TOTP
+ * authentication extension.
+ */
+public class ConfigurationService {
+
+    /**
+     * The Guacamole server environment.
+     */
+    @Inject
+    private Environment environment;
+
+    /**
+     * The human-readable name of the entity issuing user accounts. By default,
+     * this will be "Apache Guacamole".
+     */
+    private static final StringGuacamoleProperty TOTP_ISSUER =
+            new StringGuacamoleProperty() {
+
+        @Override
+        public String getName() { return "totp-issuer"; }
+
+    };
+
+    /**
+     * The number of digits which should be included in each generated TOTP
+     * code. By default, this will be 6.
+     */
+    private static final IntegerGuacamoleProperty TOTP_DIGITS=
+            new IntegerGuacamoleProperty() {
+
+        @Override
+        public String getName() { return "totp-digits"; }
+
+    };
+
+    /**
+     * The duration that each generated code should remain valid, in seconds.
+     * By default, this will be 30.
+     */
+    private static final IntegerGuacamoleProperty TOTP_PERIOD =
+            new IntegerGuacamoleProperty() {
+
+        @Override
+        public String getName() { return "totp-period"; }
+
+    };
+
+    /**
+     * The hash algorithm that should be used to generate TOTP codes. By
+     * default, this will be "sha1". Legal values are "sha1", "sha256", and
+     * "sha512".
+     */
+    private static final TOTPModeProperty TOTP_MODE =
+            new TOTPModeProperty() {
+
+        @Override
+        public String getName() { return "totp-mode"; }
+
+    };
+
+    /**
+     * Returns the human-readable name of the entity issuing user accounts. If
+     * not specified, "Apache Guacamole" will be used by default.
+     *
+     * @return
+     *     The human-readable name of the entity issuing user accounts.
+     *
+     * @throws GuacamoleException
+     *     If the "totp-issuer" property cannot be read from
+     *     guacamole.properties.
+     */
+    public String getIssuer() throws GuacamoleException {
+        return environment.getProperty(TOTP_ISSUER, "Apache Guacamole");
+    }
+
+    /**
+     * Returns the number of digits which should be included in each generated
+     * TOTP code. If not specified, 6 will be used by default.
+     *
+     * @return
+     *     The number of digits which should be included in each generated
+     *     TOTP code.
+     *
+     * @throws GuacamoleException
+     *     If the "totp-digits" property cannot be read from
+     *     guacamole.properties.
+     */
+    public int getDigits() throws GuacamoleException {
+
+        // Validate legal number of digits
+        int digits = environment.getProperty(TOTP_DIGITS, 6);
+        if (digits < 6 || digits > 8)
+            throw new GuacamoleServerException("TOTP codes may have no fewer "
+                    + "than 6 digits and no more than 8 digits.");
+
+        return digits;
+
+    }
+
+    /**
+     * Returns the duration that each generated code should remain valid, in
+     * seconds. If not specified, 30 will be used by default.
+     *
+     * @return
+     *     The duration that each generated code should remain valid, in
+     *     seconds.
+     *
+     * @throws GuacamoleException
+     *     If the "totp-period" property cannot be read from
+     *     guacamole.properties.
+     */
+    public int getPeriod() throws GuacamoleException {
+        return environment.getProperty(TOTP_PERIOD, 30);
+    }
+
+    /**
+     * Returns the hash algorithm that should be used to generate TOTP codes. If
+     * not specified, SHA1 will be used by default.
+     *
+     * @return
+     *     The hash algorithm that should be used to generate TOTP codes.
+     *
+     * @throws GuacamoleException
+     *     If the "totp-mode" property cannot be read from
+     *     guacamole.properties.
+     */
+    public TOTPGenerator.Mode getMode() throws GuacamoleException {
+        return environment.getProperty(TOTP_MODE, TOTPGenerator.Mode.SHA1);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/a422fdf9/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java
new file mode 100644
index 0000000..bfe3ef3
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java
@@ -0,0 +1,62 @@
+/*
+ * 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.conf;
+
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleServerException;
+import org.apache.guacamole.properties.GuacamoleProperty;
+import org.apache.guacamole.totp.TOTPGenerator;
+
+/**
+ * A GuacamoleProperty whose value is a TOTP generation method. The string
+ * values "sha1", "sha256", and "sha512" are each parsed to their corresponding
+ * values within the TOTPGenerator.Mode enum. All other string values result in
+ * parse errors.
+ */
+public abstract class TOTPModeProperty
+        implements GuacamoleProperty<TOTPGenerator.Mode> {
+
+    @Override
+    public TOTPGenerator.Mode parseValue(String value)
+            throws GuacamoleException {
+
+        // If no value provided, return null.
+        if (value == null)
+            return null;
+
+        // SHA1
+        if (value.equals("sha1"))
+            return TOTPGenerator.Mode.SHA1;
+
+        // SHA256
+        if (value.equals("sha256"))
+            return TOTPGenerator.Mode.SHA256;
+
+        // SHA512
+        if (value.equals("sha512"))
+            return TOTPGenerator.Mode.SHA512;
+
+        // The provided value is not legal
+        throw new GuacamoleServerException("TOTP mode must be one of "
+                + "\"sha1\", \"sha256\", or \"sha512\".");
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/a422fdf9/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
index 1be5f27..e0333dd 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
@@ -20,6 +20,7 @@
 package org.apache.guacamole.auth.totp.form;
 
 import com.google.common.io.BaseEncoding;
+import com.google.inject.Inject;
 import com.google.zxing.BarcodeFormat;
 import com.google.zxing.WriterException;
 import com.google.zxing.client.j2se.MatrixToImageWriter;
@@ -32,6 +33,7 @@ import javax.ws.rs.core.UriBuilder;
 import javax.xml.bind.DatatypeConverter;
 import org.apache.guacamole.GuacamoleException;
 import org.apache.guacamole.auth.totp.UserTOTPKey;
+import org.apache.guacamole.auth.totp.conf.ConfigurationService;
 import org.apache.guacamole.form.Field;
 import org.codehaus.jackson.annotate.JsonProperty;
 
@@ -67,30 +69,33 @@ public class AuthenticationCodeField extends Field {
     private static final BaseEncoding BASE32 = BaseEncoding.base32();
 
     /**
+     * Service for retrieving configuration information.
+     */
+    @Inject
+    private ConfigurationService confService;
+
+    /**
      * The TOTP key to expose to the user for the sake of enrollment, if any.
      * If no such key should be exposed to the user, this will be null.
      */
-    private final UserTOTPKey key;
+    private UserTOTPKey key;
 
     /**
      * Creates a new field which prompts the user for an authentication code
-     * generated via TOTP, and provide the user with their TOTP key to
-     * facilitate enrollment.
-     *
-     * @param key
-     *     The TOTP key to expose to the user for the sake of enrollment.
+     * generated via TOTP. The user's TOTP key is not exposed for enrollment.
      */
-    public AuthenticationCodeField(UserTOTPKey key) {
+    public AuthenticationCodeField() {
         super(PARAMETER_NAME, FIELD_TYPE_NAME);
-        this.key = key;
     }
 
     /**
-     * Creates a new field which prompts the user for an authentication code
-     * generated via TOTP. The user's TOTP key is not exposed for enrollment.
+     * Exposes the given key to facilitate enrollment.
+     *
+     * @param key
+     *     The TOTP key to expose to the user for the sake of enrollment.
      */
-    public AuthenticationCodeField() {
-        this(null);
+    public void exposeKey(UserTOTPKey key) {
+        this.key = key;
     }
 
     /**
@@ -114,20 +119,15 @@ public class AuthenticationCodeField extends Field {
         if (key == null)
             return null;
 
-        // FIXME: Pull from configuration
-        String issuer = "Some Issuer";
-        String algorithm = "SHA1";
-        String digits = "6";
-        String period = "30";
-
         // Format "otpauth" URL (see https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
+        String issuer = confService.getIssuer();
         return UriBuilder.fromUri("otpauth://totp/")
                 .path(issuer + ":" + key.getUsername())
                 .queryParam("secret", BASE32.encode(key.getSecret()))
                 .queryParam("issuer", issuer)
-                .queryParam("algorithm", algorithm)
-                .queryParam("digits", digits)
-                .queryParam("period", period)
+                .queryParam("algorithm", confService.getMode())
+                .queryParam("digits", confService.getDigits())
+                .queryParam("period", confService.getPeriod())
                 .build();
 
     }


[14/21] guacamole-client git commit: GUACAMOLE-96: TOTP detail headers should always be left-aligned.

Posted by vn...@apache.org.
GUACAMOLE-96: TOTP detail headers should always be left-aligned.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/608a1117
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/608a1117
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/608a1117

Branch: refs/heads/master
Commit: 608a11170baba9b983e2bf8601b8a7936c42d986
Parents: a426f59
Author: Michael Jumper <mj...@apache.org>
Authored: Fri Nov 24 14:05:32 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:18 2018 -0800

----------------------------------------------------------------------
 extensions/guacamole-auth-totp/src/main/resources/styles/totp.css | 1 +
 1 file changed, 1 insertion(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/608a1117/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
index e578e6a..c8a2505 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
+++ b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
@@ -75,6 +75,7 @@ h3.totp-details-header::before {
 
 .totp-details th {
     padding-right: 0.25em;
+    text-align: left;
 }
 
 .totp-details td {


[07/21] guacamole-client git commit: GUACAMOLE-96: Add TOTP generator implementation based on reference implementation from IETF.

Posted by vn...@apache.org.
GUACAMOLE-96: Add TOTP generator implementation based on reference implementation from IETF.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/b55e5617
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/b55e5617
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/b55e5617

Branch: refs/heads/master
Commit: b55e56179c656191d3363b9089e3e3f235351d83
Parents: 264fd24
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 00:22:26 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:17 2018 -0800

----------------------------------------------------------------------
 LICENSE                                         |  37 ++
 extensions/guacamole-auth-totp/pom.xml          |  15 +
 .../guacamole-auth-totp/src/licenses/LICENSE    |  37 ++
 .../bundled/totp-reference-impl-07/license.txt  |  28 ++
 .../apache/guacamole/totp/TOTPGenerator.java    | 402 +++++++++++++++++++
 .../guacamole/totp/TOTPGeneratorTest.java       | 168 ++++++++
 6 files changed, 687 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b55e5617/LICENSE
----------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
index 2c1ab31..3fa304c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -254,3 +254,40 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+
+TOTP Reference Implementation (https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Reference-Impl)
+-------------------------------------------------------------------------------
+
+    Verson: 07
+    From: 'IETF Trust' (http://trustee.ietf.org/license-info)
+    License(s):
+        BSD 3-clause (extensions/guacamole-auth-duo/src/licenses/bundled/totp-reference-impl-07/license.txt)
+
+Copyright (c) 2011 IETF Trust and the persons identified as authors
+of the code. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ - Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+ - Neither the name of Internet Society, IETF or IETF Trust, nor the names
+   of specific contributors, may be used to endorse or promote products
+   derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b55e5617/extensions/guacamole-auth-totp/pom.xml
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/pom.xml b/extensions/guacamole-auth-totp/pom.xml
index b7fa965..5b421d7 100644
--- a/extensions/guacamole-auth-totp/pom.xml
+++ b/extensions/guacamole-auth-totp/pom.xml
@@ -154,6 +154,21 @@
             <scope>provided</scope>
         </dependency>
 
+        <!-- Guava - Utility Library -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>18.0</version>
+        </dependency>
+
+        <!-- JUnit -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
 </project>

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b55e5617/extensions/guacamole-auth-totp/src/licenses/LICENSE
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/licenses/LICENSE b/extensions/guacamole-auth-totp/src/licenses/LICENSE
index c6cbf77..e1fa0fc 100644
--- a/extensions/guacamole-auth-totp/src/licenses/LICENSE
+++ b/extensions/guacamole-auth-totp/src/licenses/LICENSE
@@ -237,3 +237,40 @@ JSR-330 / Dependency Injection for Java (http://code.google.com/p/atinject/)
     License(s):
         Apache v2.0 (bundled/javax.inject-1/LICENSE-2.0.txt)
 
+
+TOTP Reference Implementation (https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Reference-Impl)
+-------------------------------------------------------------------------------
+
+    Verson: 07
+    From: 'IETF Trust' (http://trustee.ietf.org/license-info)
+    License(s):
+        BSD 3-clause (bundled/totp-reference-impl-07/license.txt)
+
+Copyright (c) 2011 IETF Trust and the persons identified as authors
+of the code. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ - Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+ - Neither the name of Internet Society, IETF or IETF Trust, nor the names
+   of specific contributors, may be used to endorse or promote products
+   derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b55e5617/extensions/guacamole-auth-totp/src/licenses/bundled/totp-reference-impl-07/license.txt
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/totp-reference-impl-07/license.txt b/extensions/guacamole-auth-totp/src/licenses/bundled/totp-reference-impl-07/license.txt
new file mode 100644
index 0000000..bb1afcd
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/licenses/bundled/totp-reference-impl-07/license.txt
@@ -0,0 +1,28 @@
+Copyright (c) 2011 IETF Trust and the persons identified as authors
+of the code. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ - Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+ - Neither the name of Internet Society, IETF or IETF Trust, nor the names
+   of specific contributors, may be used to endorse or promote products
+   derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b55e5617/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java
new file mode 100644
index 0000000..004c23b
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java
@@ -0,0 +1,402 @@
+/*
+ * 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.totp;
+
+import com.google.common.primitives.Longs;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/*
+ * NOTE: This TOTP implementation is based on the TOTP reference implementation
+ * provided by the IETF Trust at:
+ *
+ * https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Reference-Impl
+ */
+
+/*
+ * Copyright (c) 2011 IETF Trust and the persons identified as authors
+ * of the code. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  - Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  - Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  - Neither the name of Internet Society, IETF or IETF Trust, nor the names
+ *    of specific contributors, may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Generator which uses the TOTP algorithm to generate authentication codes.
+ */
+public class TOTPGenerator {
+
+    /**
+     * The default time to use as the basis for comparison when transforming
+     * provided TOTP timestamps into counter values required for HOTP, in
+     * seconds since midnight, 1970-01-01, UTC (UNIX epoch).
+     */
+    public static final long DEFAULT_START_TIME = 0;
+
+    /**
+     * The default frequency at which new TOTP codes should be generated (and
+     * old codes invalidated), in seconds.
+     */
+    public static final long DEFAULT_TIME_STEP = 30;
+
+    /**
+     * The TOTP generation mode. The mode dictates the hash function which
+     * should be used to generate authentication codes, as well as the required
+     * key size.
+     */
+    private final Mode mode;
+
+    /**
+     * The shared key to use to generate authentication codes. The size
+     * required for this key depends on the generation mode.
+     */
+    private final Key key;
+
+    /**
+     * The length of codes to generate, in digits.
+     */
+    private final int length;
+
+    /**
+     * The base time against which the timestamp specified for each TOTP
+     * should be compared to produce the corresponding HOTP counter value, in
+     * seconds since midnight, 1970-01-01, UTC (UNIX epoch). This is the value
+     * value referred to as "T0" in the TOTP specification.
+     */
+    private final long startTime;
+
+    /**
+     * The frequency that new TOTP codes should be generated and invalidated,
+     * in seconds. This is the value referred to as "X" in the TOTP
+     * specification.
+     */
+    private final long timeStep;
+
+    /**
+     * The operating mode for TOTP, defining the hash algorithm to be used.
+     */
+    public enum Mode {
+
+        /**
+         * TOTP mode which generates hashes using SHA1. TOTP in SHA1 mode
+         * requires 160-bit keys.
+         */
+        SHA1("HmacSHA1"),
+
+        /**
+         * TOTP mode which generates hashes using SHA256. TOTP in SHA256 mode
+         * requires 256-bit keys.
+         */
+        SHA256("HmacSHA256"),
+
+        /**
+         * TOTP mode which generates hashes using SHA512. TOTP in SHA512 mode
+         * requires 512-bit keys.
+         */
+        SHA512("HmacSHA512");
+
+        /**
+         * The name of the HMAC algorithm which the TOTP implementation should
+         * use when operating in this mode, in the format required by
+         * Mac.getInstance().
+         */
+        private final String algorithmName;
+
+        /**
+         * Creates a new TOTP operating mode which is associated with the
+         * given HMAC algorithm.
+         *
+         * @param algorithmName
+         *     The name of the HMAC algorithm which the TOTP implementation
+         *     should use when operating in this mode, in the format required
+         *     by Mac.getInstance().
+         */
+        private Mode(String algorithmName) {
+            this.algorithmName = algorithmName;
+        }
+
+        /**
+         * Returns the name of the HMAC algorithm which the TOTP implementation
+         * should use when operating in this mode. The name returned will be
+         * in the format required by Mac.getInstance().
+         *
+         * @return
+         *     The name of the HMAC algorithm which the TOTP implementation
+         *     should use.
+         */
+        public String getAlgorithmName() {
+            return algorithmName;
+        }
+
+    }
+
+    /**
+     * Creates a new TOTP generator which uses the given shared key to generate
+     * authentication codes. The provided generation mode dictates the size of
+     * the key required, while the given start time and time step dictate how
+     * timestamps provided for code generation are converted to the counter
+     * value used by HOTP (the algorithm which forms the basis of TOTP).
+     *
+     * @param key
+     *     The shared key to use to generate authentication codes.
+     *
+     * @param mode
+     *     The mode in which the TOTP algorithm should operate.
+     *
+     * @param length
+     *     The length of the codes to generate, in digits. As required
+     *     by the specification, this value MUST be at least 6 but no greater
+     *     than 8.
+     *
+     * @param startTime
+     *     The base time against which the timestamp specified for each TOTP
+     *     should be compared to produce the corresponding HOTP counter value,
+     *     in seconds since midnight, 1970-01-01, UTC (UNIX epoch). This is the
+     *     value referred to as "T0" in the TOTP specification.
+     *
+     * @param timeStep
+     *     The frequency that new TOTP codes should be generated and
+     *     invalidated, in seconds. This is the value referred to as "X" in the
+     *     TOTP specification.
+     *
+     * @throws InvalidKeyException
+     *     If the provided key is invalid for the requested TOTP mode.
+     */
+    public TOTPGenerator(byte[] key, Mode mode, int length, long startTime,
+            long timeStep) throws InvalidKeyException {
+
+        // Validate length is within spec
+        if (length < 6 || length > 8)
+            throw new IllegalArgumentException("TOTP codes must be at least 6 "
+                    + "digits and no more than 8 digits.");
+
+        this.key = new SecretKeySpec(key, "RAW");
+        this.mode = mode;
+        this.length = length;
+        this.startTime = startTime;
+        this.timeStep = timeStep;
+
+        // Verify key validity
+        getMacInstance(this.mode, this.key);
+
+    }
+
+    /**
+     * Creates a new TOTP generator which uses the given shared key to generate
+     * authentication codes. The provided generation mode dictates the size of
+     * the key required. The start time and time step used to produce the
+     * counter value used by HOTP (the algorithm which forms the basis of TOTP)
+     * are set to the default values recommended by the TOTP specification (0
+     * and 30 respectively).
+     *
+     * @param key
+     *     The shared key to use to generate authentication codes.
+     *
+     * @param mode
+     *     The mode in which the TOTP algorithm should operate.
+     *
+     * @param length
+     *     The length of the codes to generate, in digits. As required
+     *     by the specification, this value MUST be at least 6 but no greater
+     *     than 8.
+     *
+     * @throws InvalidKeyException
+     *     If the provided key is invalid for the requested TOTP mode.
+     */
+    public TOTPGenerator(byte[] key, Mode mode, int length)
+            throws InvalidKeyException {
+        this(key, mode, length, DEFAULT_START_TIME, DEFAULT_TIME_STEP);
+    }
+
+    /**
+     * Returns a new Mac instance which produces message authentication codes
+     * using the given secret key and the algorithm required by the given TOTP
+     * mode.
+     *
+     * @param mode
+     *     The TOTP mode which dictates the HMAC algorithm to be used.
+     *
+     * @param key
+     *     The secret key to use to produce message authentication codes.
+     *
+     * @return
+     *     A new Mac instance which produces message authentication codes
+     *     using the given secret key and the algorithm required by the given
+     *     TOTP mode.
+     *
+     * @throws InvalidKeyException
+     *     If the provided key is invalid for the requested TOTP mode.
+     */
+    private static Mac getMacInstance(Mode mode, Key key)
+            throws InvalidKeyException {
+
+        try {
+            Mac hmac = Mac.getInstance(mode.getAlgorithmName());
+            hmac.init(key);
+            return hmac;
+        }
+        catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException("Support for the HMAC "
+                    + "algorithm required for TOTP in " + mode + " mode is "
+                    + "missing.", e);
+        }
+
+    }
+
+    /**
+     * Calculates the HMAC for the given message using the key and algorithm
+     * provided when this TOTPGenerator was created.
+     *
+     * @param message
+     *     The message to calculate the HMAC of.
+     *
+     * @return
+     *     The HMAC of the given message.
+     */
+    private byte[] getHMAC(byte[] message) {
+
+        try {
+            return getMacInstance(mode, key).doFinal(message);
+        }
+        catch (InvalidKeyException e) {
+
+            // As the key is verified during construction of the TOTPGenerator,
+            // this should never happen
+            throw new IllegalStateException("Provided key became invalid after "
+                    + "passing validation.", e);
+
+        }
+
+    }
+
+    /**
+     * Given an arbitrary integer value, returns a code containing the decimal
+     * representation of that value and exactly the given number of digits. If
+     * the given value has more than the desired number of digits, leading
+     * digits will be truncated to reduce the length. If the given value has
+     * fewer than the desired number of digits, leading zeroes will be added to
+     * increase the length.
+     *
+     * @param value
+     *     The value to convert into a decimal code of the given length.
+     *
+     * @param length
+     *     The number of digits to include in the code.
+     *
+     * @return
+     *     A code containing the decimal value of the given integer, truncated
+     *     or padded such that exactly the given number of digits are present.
+     */
+    private String toCode(int value, int length) {
+
+        // Convert value to simple integer string
+        String valueString = Integer.toString(value);
+
+        // If the resulting string is too long, truncate to the last N digits
+        if (valueString.length() > length)
+            return valueString.substring(valueString.length() - length);
+
+        // Otherwise, add zeroes until the desired length is reached
+        StringBuilder builder = new StringBuilder(length);
+        for (int i = valueString.length(); i < length; i++)
+            builder.append('0');
+
+        // Return the padded integer string
+        builder.append(valueString);
+        return builder.toString();
+
+    }
+
+    /**
+     * Generates a TOTP code of the given length using the given absolute
+     * timestamp rather than the current system time.
+     *
+     * @param time
+     *     The absolute timestamp to use to generate the TOTP code, in seconds
+     *     since midnight, 1970-01-01, UTC (UNIX epoch).
+     *
+     * @return
+     *     The TOTP code which corresponds to the given timestamp, having
+     *     exactly the given length.
+     *
+     * @throws IllegalArgumentException
+     *     If the given length is invalid as defined by the TOTP specification.
+     */
+    public String generate(long time) {
+
+        // Calculate HOTP counter value based on provided time
+        long counter = (time - startTime) / timeStep;
+        byte[] hash = getHMAC(Longs.toByteArray(counter));
+
+        // Calculate HOTP value as defined by section 5.2 of RFC 4226:
+        // https://tools.ietf.org/html/rfc4226#section-5.2
+        int offset = hash[hash.length - 1] & 0xF;
+        int binary
+                = ((hash[offset]     & 0x7F) << 24)
+                | ((hash[offset + 1] & 0xFF) << 16)
+                | ((hash[offset + 2] & 0xFF) << 8)
+                |  (hash[offset + 3] & 0xFF);
+
+        // Truncate or pad the value accordingly
+        return toCode(binary, length);
+
+    }
+
+    /**
+     * Generates a TOTP code of the given length using the current system time.
+     *
+     * @return
+     *     The TOTP code which corresponds to the current system time, having
+     *     exactly the given length.
+     *
+     * @throws IllegalArgumentException
+     *     If the given length is invalid as defined by the TOTP specification.
+     */
+    public String generate() {
+        return generate(System.currentTimeMillis() / 1000);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b55e5617/extensions/guacamole-auth-totp/src/test/java/org/apache/guacamole/totp/TOTPGeneratorTest.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/test/java/org/apache/guacamole/totp/TOTPGeneratorTest.java b/extensions/guacamole-auth-totp/src/test/java/org/apache/guacamole/totp/TOTPGeneratorTest.java
new file mode 100644
index 0000000..9266ad7
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/test/java/org/apache/guacamole/totp/TOTPGeneratorTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.totp;
+
+import java.security.InvalidKeyException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/*
+ * NOTE: The tests for this TOTP implementation is based on the TOTP reference
+ * implementation provided by the IETF Trust at:
+ *
+ * https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Reference-Impl
+ */
+
+/*
+ * Copyright (c) 2011 IETF Trust and the persons identified as authors
+ * of the code. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  - Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  - Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  - Neither the name of Internet Society, IETF or IETF Trust, nor the names
+ *    of specific contributors, may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Test which verifies the correctness of the TOTPGenerator class against the
+ * test inputs and results provided in the IETF reference implementation and
+ * spec for TOTP:
+ *
+ * https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Test-Vectors
+ */
+public class TOTPGeneratorTest {
+
+    /**
+     * Verifies the results of generating authentication codes using the TOTP
+     * algorithm in SHA1 mode.
+     */
+    @Test
+    public void testGenerateSHA1() {
+
+        // 160-bit key consisting of the bytes "12345678901234567890" repeated
+        // as necessary
+        final byte[] key = {
+            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'
+        };
+
+        try {
+            final TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA1, 8);
+            assertEquals("94287082", totp.generate(59));
+            assertEquals("07081804", totp.generate(1111111109));
+            assertEquals("14050471", totp.generate(1111111111));
+            assertEquals("89005924", totp.generate(1234567890));
+            assertEquals("69279037", totp.generate(2000000000));
+            assertEquals("65353130", totp.generate(20000000000L));
+        }
+        catch (InvalidKeyException e) {
+            fail("SHA1 test key is invalid.");
+        }
+
+
+    }
+
+    /**
+     * Verifies the results of generating authentication codes using the TOTP
+     * algorithm in SHA256 mode.
+     */
+    @Test
+    public void testGenerateSHA256() {
+
+        // 256-bit key consisting of the bytes "12345678901234567890" repeated
+        // as necessary
+        final byte[] key = {
+            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+            '1', '2'
+        };
+
+        try {
+            final TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA256, 8);
+            assertEquals("46119246", totp.generate(59));
+            assertEquals("68084774", totp.generate(1111111109));
+            assertEquals("67062674", totp.generate(1111111111));
+            assertEquals("91819424", totp.generate(1234567890));
+            assertEquals("90698825", totp.generate(2000000000));
+            assertEquals("77737706", totp.generate(20000000000L));
+        }
+        catch (InvalidKeyException e) {
+            fail("SHA256 test key is invalid.");
+        }
+
+    }
+
+    /**
+     * Verifies the results of generating authentication codes using the TOTP
+     * algorithm in SHA512 mode.
+     */
+    @Test
+    public void testGenerateSHA512() {
+
+        // 512-bit key consisting of the bytes "12345678901234567890" repeated
+        // as necessary
+        final byte[] key = {
+            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+            '1', '2', '3', '4'
+        };
+
+        try {
+            final TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA512, 8);
+            assertEquals("90693936", totp.generate(59));
+            assertEquals("25091201", totp.generate(1111111109));
+            assertEquals("99943326", totp.generate(1111111111));
+            assertEquals("93441116", totp.generate(1234567890));
+            assertEquals("38618901", totp.generate(2000000000));
+            assertEquals("47863826", totp.generate(20000000000L));
+        }
+        catch (InvalidKeyException e) {
+            fail("SHA512 test key is invalid.");
+        }
+
+    }
+
+}


[17/21] guacamole-client git commit: GUACAMOLE-96: Open "otpauth" link when user clicks on barcode.

Posted by vn...@apache.org.
GUACAMOLE-96: Open "otpauth" link when user clicks on barcode.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/b9dba7dd
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/b9dba7dd
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/b9dba7dd

Branch: refs/heads/master
Commit: b9dba7ddf4eaa9c291035c706bfc6b32408544e8
Parents: 78cde50
Author: Michael Jumper <mj...@apache.org>
Authored: Fri Nov 24 13:54:26 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:18 2018 -0800

----------------------------------------------------------------------
 .../controllers/authenticationCodeFieldController.js   | 13 +++++++++++--
 .../src/main/resources/styles/totp.css                 |  1 +
 .../resources/templates/authenticationCodeField.html   |  2 +-
 3 files changed, 13 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b9dba7dd/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
index 27881f8..8f19c9f 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
+++ b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
@@ -21,8 +21,8 @@
  * Controller for the "GUAC_TOTP_CODE" field which prompts the user to enter
  * the code generated by their authentication device.
  */
-angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$scope',
-    function authenticationCodeFieldController($scope) {
+angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$scope', '$window',
+    function authenticationCodeFieldController($scope, $window) {
 
     /**
      * The secret key split into groups of at most four characters each, or
@@ -56,4 +56,13 @@ angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$sc
         $scope.detailsShown = false;
     };
 
+    /**
+     * Attempts to open the "otpauth" URI containing the user's TOTP key,
+     * invoking whichever application may be installed locally for handling
+     * multi-factor authentication.
+     */
+    $scope.openKeyURI = function openKeyURI() {
+        $window.open($scope.field.keyUri);
+    };
+
 }]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b9dba7dd/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
index 6d2d89f..e578e6a 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
+++ b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
@@ -29,6 +29,7 @@
     margin: 1em;
     border: 1px solid rgba(0,0,0,0.25);
     box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
+    cursor: pointer;
 }
 
 h3.totp-details-header {

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b9dba7dd/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
index 0837b74..c493a20 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
+++ b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
@@ -6,7 +6,7 @@
         <p translate="TOTP.HELP_ENROLL_BARCODE"></p>
 
         <!-- Barcode and key details -->
-        <div class="totp-qr-code"><img ng-src="{{field.qrCode}}"></div>
+        <div class="totp-qr-code"><img ng-src="{{field.qrCode}}" ng-click="openKeyURI()"></div>
         <h3 class="totp-details-header">
             {{'TOTP.SECTION_HEADER_DETAILS' | translate}}
             <a class="totp-show-details" ng-click="showDetails()">{{'TOTP.ACTION_SHOW_DETAILS' | translate}}</a>


[15/21] guacamole-client git commit: GUACAMOLE-96: Scroll login interface if too large for screen.

Posted by vn...@apache.org.
GUACAMOLE-96: Scroll login interface if too large for screen.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/a426f597
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/a426f597
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/a426f597

Branch: refs/heads/master
Commit: a426f59765471adb72dc011a56b50d9c403d4ab4
Parents: b9dba7d
Author: Michael Jumper <mj...@apache.org>
Authored: Fri Nov 24 14:02:18 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:18 2018 -0800

----------------------------------------------------------------------
 guacamole/src/main/webapp/app/login/styles/login.css | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/a426f597/guacamole/src/main/webapp/app/login/styles/login.css
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/login/styles/login.css b/guacamole/src/main/webapp/app/login/styles/login.css
index 1707827..4cb07f1 100644
--- a/guacamole/src/main/webapp/app/login/styles/login.css
+++ b/guacamole/src/main/webapp/app/login/styles/login.css
@@ -20,7 +20,7 @@
 div.login-ui {
     height: 100%;
     width: 100%;
-    position: fixed;
+    position: absolute;
     left: 0;
     top: 0;
     display: table;


[02/21] guacamole-client git commit: GUACAMOLE-96: Migrate to TOTP-specific field type for authentication code.

Posted by vn...@apache.org.
GUACAMOLE-96: Migrate to TOTP-specific field type for authentication code.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/8ac8fec4
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/8ac8fec4
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/8ac8fec4

Branch: refs/heads/master
Commit: 8ac8fec47834a13317591a676faf11ed29b34929
Parents: 0844e9d
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 12:03:18 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:17 2018 -0800

----------------------------------------------------------------------
 extensions/guacamole-auth-totp/pom.xml          | 80 ++++++++++++++++++++
 .../auth/totp/UserVerificationService.java      | 28 ++-----
 .../auth/totp/form/AuthenticationCodeField.java | 48 ++++++++++++
 .../src/main/resources/config/totpConfig.js     | 33 ++++++++
 .../authenticationCodeFieldController.js        | 29 +++++++
 .../src/main/resources/guac-manifest.json       | 14 +++-
 .../src/main/resources/styles/totp.css          | 20 +++++
 .../templates/authenticationCodeField.html      |  3 +
 .../src/main/resources/totpModule.js            | 28 +++++++
 9 files changed, 259 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ac8fec4/extensions/guacamole-auth-totp/pom.xml
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/pom.xml b/extensions/guacamole-auth-totp/pom.xml
index 5b421d7..17aff05 100644
--- a/extensions/guacamole-auth-totp/pom.xml
+++ b/extensions/guacamole-auth-totp/pom.xml
@@ -53,6 +53,85 @@
                 </configuration>
             </plugin>
 
+            <!-- Pre-cache Angular templates with maven-angular-plugin -->
+            <plugin>
+                <groupId>com.keithbranton.mojo</groupId>
+                <artifactId>angular-maven-plugin</artifactId>
+                <version>0.3.2</version>
+                <executions>
+                    <execution>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>html2js</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <sourceDir>${basedir}/src/main/resources</sourceDir>
+                    <include>**/*.html</include>
+                    <target>${basedir}/src/main/resources/generated/templates-main/templates.js</target>
+                    <prefix>app/ext/totp</prefix>
+                </configuration>
+            </plugin>
+
+            <!-- JS/CSS Minification Plugin -->
+            <plugin>
+                <groupId>com.samaxes.maven</groupId>
+                <artifactId>minify-maven-plugin</artifactId>
+                <version>1.7.5</version>
+                <executions>
+                    <execution>
+                        <id>default-cli</id>
+                        <configuration>
+                            <charset>UTF-8</charset>
+
+                            <webappSourceDir>${basedir}/src/main/resources</webappSourceDir>
+                            <webappTargetDir>${project.build.directory}/classes</webappTargetDir>
+
+                            <cssSourceDir>/</cssSourceDir>
+                            <cssTargetDir>/</cssTargetDir>
+                            <cssFinalFile>totp.css</cssFinalFile>
+
+                            <cssSourceFiles>
+                                <cssSourceFile>license.txt</cssSourceFile>
+                            </cssSourceFiles>
+
+                            <cssSourceIncludes>
+                                <cssSourceInclude>**/*.css</cssSourceInclude>
+                            </cssSourceIncludes>
+
+                            <jsSourceDir>/</jsSourceDir>
+                            <jsTargetDir>/</jsTargetDir>
+                            <jsFinalFile>totp.js</jsFinalFile>
+
+                            <jsSourceFiles>
+                                <jsSourceFile>license.txt</jsSourceFile>
+                            </jsSourceFiles>
+
+                            <jsSourceIncludes>
+                                <jsSourceInclude>**/*.js</jsSourceInclude>
+                            </jsSourceIncludes>
+
+                            <!-- Do not minify and include tests -->
+                            <jsSourceExcludes>
+                                <jsSourceExclude>**/*.test.js</jsSourceExclude>
+                            </jsSourceExcludes>
+                            <jsEngine>CLOSURE</jsEngine>
+
+                            <!-- Disable warnings for JSDoc annotations -->
+                            <closureWarningLevels>
+                                <misplacedTypeAnnotation>OFF</misplacedTypeAnnotation>
+                                <nonStandardJsDocs>OFF</nonStandardJsDocs>
+                            </closureWarningLevels>
+
+                        </configuration>
+                        <goals>
+                            <goal>minify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
             <!-- Assembly plugin - for easy distribution -->
             <plugin>
                 <artifactId>maven-assembly-plugin</artifactId>
@@ -105,6 +184,7 @@
                     <excludes>
                         <exclude>**/*.json</exclude>
                         <exclude>src/licenses/**/*</exclude>
+                        <exclude>src/main/resources/templates/*.html</exclude>
                     </excludes>
                 </configuration>
 

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ac8fec4/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
index d694c5e..da24995 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
@@ -28,8 +28,8 @@ import javax.servlet.http.HttpServletRequest;
 import org.apache.guacamole.GuacamoleClientException;
 import org.apache.guacamole.GuacamoleException;
 import org.apache.guacamole.GuacamoleUnsupportedException;
+import org.apache.guacamole.auth.totp.form.AuthenticationCodeField;
 import org.apache.guacamole.form.Field;
-import org.apache.guacamole.form.TextField;
 import org.apache.guacamole.net.auth.AuthenticatedUser;
 import org.apache.guacamole.net.auth.Credentials;
 import org.apache.guacamole.net.auth.User;
@@ -62,26 +62,6 @@ public class UserVerificationService {
     private static final String TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME = "guac-totp-key-confirmed";
 
     /**
-     * The name of the HTTP parameter which will contain the TOTP code provided
-     * by the user to verify their identity.
-     */
-    private static final String TOTP_PARAMETER_NAME = "guac-totp";
-
-    /**
-     * The field which should be exposed to the user to request that they
-     * provide their TOTP code.
-     */
-    private static final Field TOTP_FIELD = new TextField(TOTP_PARAMETER_NAME);
-
-    /**
-     * CredentialsInfo object describing the credentials expected for a user
-     * who has verified their identity with TOTP.
-     */
-    private static final CredentialsInfo TOTP_CREDENTIALS = new CredentialsInfo(
-            Collections.singletonList(TOTP_FIELD)
-    );
-
-    /**
      * BaseEncoding instance which decoded/encodes base32.
      */
     private static final BaseEncoding BASE32 = BaseEncoding.base32();
@@ -234,14 +214,16 @@ public class UserVerificationService {
         HttpServletRequest request = credentials.getRequest();
 
         // Retrieve TOTP from request
-        String code = request.getParameter(TOTP_PARAMETER_NAME);
+        String code = request.getParameter(AuthenticationCodeField.PARAMETER_NAME);
 
         // If no TOTP provided, request one
         if (code == null) {
 
             // FIXME: Handle key.isConfirmed() for initial prompt
             throw new GuacamoleInsufficientCredentialsException(
-                    "LOGIN.INFO_TOTP_REQUIRED", TOTP_CREDENTIALS);
+                    "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo(
+                        Collections.<Field>singletonList(new AuthenticationCodeField())
+                    ));
 
         }
 

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ac8fec4/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
new file mode 100644
index 0000000..8119657
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
@@ -0,0 +1,48 @@
+/*
+ * 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.form;
+
+import org.apache.guacamole.form.Field;
+
+/**
+ * Field which prompts the user for an authentication code generated via TOTP.
+ */
+public class AuthenticationCodeField extends Field {
+
+    /**
+     * The name of the HTTP parameter which will contain the TOTP code provided
+     * by the user to verify their identity.
+     */
+    public static final String PARAMETER_NAME = "guac-totp";
+
+    /**
+     * The unique name associated with this field type.
+     */
+    private static final String FIELD_TYPE_NAME = "GUAC_TOTP_CODE";
+
+    /**
+     * Creates a new field which prompts the user for an authentication code
+     * generated via TOTP.
+     */
+    public AuthenticationCodeField() {
+        super(PARAMETER_NAME, FIELD_TYPE_NAME);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ac8fec4/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js b/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js
new file mode 100644
index 0000000..54bb56c
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+/**
+ * Config block which registers TOTP-specific field types.
+ */
+angular.module('guacTOTP').config(['formServiceProvider',
+    function guacTOTPConfig(formServiceProvider) {
+
+    // Define field for the TOTP code provided by the user
+    formServiceProvider.registerFieldType('GUAC_TOTP_CODE', {
+        module      : 'guacTOTP',
+        controller  : 'authenticationCodeFieldController',
+        templateUrl : 'app/ext/totp/templates/authenticationCodeField.html'
+    });
+
+}]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ac8fec4/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
new file mode 100644
index 0000000..c9cecc6
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/**
+ * Controller for the "GUAC_TOTP_CODE" field which prompts the user to enter
+ * the code generated by their authentication device.
+ */
+angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$scope', '$element',
+    function authenticationCodeFieldController($scope, $element) {
+
+    // STUB
+
+}]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ac8fec4/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json
index 539562c..dee8291 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json
+++ b/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json
@@ -11,6 +11,18 @@
 
     "translations" : [
         "translations/en.json"
-    ]
+    ],
+
+    "js" : [
+        "totp.min.js"
+    ],
+
+    "css" : [
+        "totp.min.css"
+    ],
+
+    "resources" : {
+        "templates/authenticationCodeField.html" : "text/html"
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ac8fec4/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
new file mode 100644
index 0000000..8181e2c
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/* STUB */

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ac8fec4/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
new file mode 100644
index 0000000..4e7fb0f
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
@@ -0,0 +1,3 @@
+<div class="totp-code-field">
+    <input type="text" ng-model="model" autocorrect="off" autocapitalize="off"/>
+</div>

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ac8fec4/extensions/guacamole-auth-totp/src/main/resources/totpModule.js
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/totpModule.js b/extensions/guacamole-auth-totp/src/main/resources/totpModule.js
new file mode 100644
index 0000000..c6a0c7e
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/resources/totpModule.js
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/**
+ * Module which provides handling for TOTP multi-factor authentication.
+ */
+angular.module('guacTOTP', [
+    'form'
+]);
+
+// Ensure the guacTOTP module is loaded along with the rest of the app
+angular.module('index').requires.push('guacTOTP');


[10/21] guacamole-client git commit: GUACAMOLE-96: Pull TOTP key from user attribute.

Posted by vn...@apache.org.
GUACAMOLE-96: Pull TOTP key from user attribute.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/8dd5537c
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/8dd5537c
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/8dd5537c

Branch: refs/heads/master
Commit: 8dd5537cf30f9b9a57e637dae53c47c237416064
Parents: 19e03a1
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 01:05:42 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:17 2018 -0800

----------------------------------------------------------------------
 .../guacamole/auth/totp/UserVerificationService.java    | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8dd5537c/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
index 7cffffe..cb73730 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
@@ -22,6 +22,7 @@ package org.apache.guacamole.auth.totp;
 import com.google.common.io.BaseEncoding;
 import java.security.InvalidKeyException;
 import java.util.Collections;
+import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import org.apache.guacamole.GuacamoleClientException;
 import org.apache.guacamole.GuacamoleException;
@@ -47,6 +48,11 @@ public class UserVerificationService {
     private final Logger logger = LoggerFactory.getLogger(UserVerificationService.class);
 
     /**
+     * The name of the user attribute which stores the TOTP key.
+     */
+    private static final String TOTP_KEY_ATTRIBUTE_NAME = "guac-totp-key";
+
+    /**
      * The name of the HTTP parameter which will contain the TOTP code provided
      * by the user to verify their identity.
      */
@@ -84,10 +90,8 @@ public class UserVerificationService {
      *     UserContext, or null if no TOTP key is associated with the user.
      */
     public String getKey(UserContext context){
-
-        // FIXME: Hard-coded key
-        return "JBSWY3DPEHPK3PXP";
-
+        Map<String, String> attributes = context.self().getAttributes();
+        return attributes.get(TOTP_KEY_ATTRIBUTE_NAME);
     }
 
     /**


[11/21] guacamole-client git commit: GUACAMOLE-96: Allow user's raw TOTP key details to be exposed within UI during enrollment.

Posted by vn...@apache.org.
GUACAMOLE-96: Allow user's raw TOTP key details to be exposed within UI during enrollment.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/78cde50d
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/78cde50d
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/78cde50d

Branch: refs/heads/master
Commit: 78cde50df96fcda535622fd9039ac0c6dc59c5ef
Parents: 5b2b633
Author: Michael Jumper <mj...@apache.org>
Authored: Fri Nov 24 13:33:39 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:18 2018 -0800

----------------------------------------------------------------------
 .../src/main/resources/config/totpConfig.js     |  1 +
 .../authenticationCodeFieldController.js        | 59 ++++++++++++++++++++
 .../src/main/resources/styles/totp.css          | 56 ++++++++++++++++++-
 .../templates/authenticationCodeField.html      | 31 +++++++++-
 .../src/main/resources/translations/en.json     | 12 +++-
 5 files changed, 156 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/78cde50d/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js b/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js
index 81cc07d..54bb56c 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js
+++ b/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js
@@ -26,6 +26,7 @@ angular.module('guacTOTP').config(['formServiceProvider',
     // Define field for the TOTP code provided by the user
     formServiceProvider.registerFieldType('GUAC_TOTP_CODE', {
         module      : 'guacTOTP',
+        controller  : 'authenticationCodeFieldController',
         templateUrl : 'app/ext/totp/templates/authenticationCodeField.html'
     });
 

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/78cde50d/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
new file mode 100644
index 0000000..27881f8
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+/**
+ * Controller for the "GUAC_TOTP_CODE" field which prompts the user to enter
+ * the code generated by their authentication device.
+ */
+angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$scope',
+    function authenticationCodeFieldController($scope) {
+
+    /**
+     * The secret key split into groups of at most four characters each, or
+     * null if the secret key is not exposed.
+     *
+     * @type String[]
+     */
+    $scope.groupedSecret = $scope.field.secret && $scope.field.secret.match(/.{1,4}/g);
+
+    /**
+     * Whether the raw details of the secret key and TOTP configuration should
+     * be shown. By default, such details are hidden. If the secret key is not
+     * exposed, this property has no effect.
+     */
+    $scope.detailsShown = false;
+
+    /**
+     * Shows the raw details of the secret key and TOTP configuration. If the
+     * secret key is not exposed, or the details are already shown, this
+     * function has no effect.
+     */
+    $scope.showDetails = function showDetails() {
+        $scope.detailsShown = true;
+    };
+
+    /**
+     * Hides the raw details of the secret key and TOTP configuration. If the
+     * details are already hidden, this function has no effect.
+     */
+    $scope.hideDetails = function hideDetails() {
+        $scope.detailsShown = false;
+    };
+
+}]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/78cde50d/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
index 6db7729..6d2d89f 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
+++ b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-.totp-enroll p {
+.totp-enroll p, .totp-details {
     font-size: 0.8em;
 }
 
@@ -30,3 +30,57 @@
     border: 1px solid rgba(0,0,0,0.25);
     box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
 }
+
+h3.totp-details-header {
+    font-size: 0.8em;
+}
+
+h3.totp-details-header::before {
+    content: '▸ ';
+}
+
+.totp-details-visible h3.totp-details-header::before {
+    content: '▾ ';
+}
+
+.totp-details,
+.totp-hide-details {
+    display: none;
+}
+
+.totp-details-visible .totp-details {
+    display: table;
+}
+
+.totp-details-visible .totp-hide-details {
+    display: inline;
+}
+
+.totp-details-visible .totp-show-details {
+    display: none;
+}
+
+.totp-hide-details, .totp-show-details {
+    color: blue;
+    text-decoration: underline;
+    cursor: pointer;
+    margin: 0 0.25em;
+    font-weight: normal;
+}
+
+.totp-details {
+    margin: 0 auto;
+}
+
+.totp-details th {
+    padding-right: 0.25em;
+}
+
+.totp-details td {
+    font-family: monospace;
+}
+
+.totp-detail {
+    display: inline-block;
+    margin: 0 0.25em;
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/78cde50d/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
index 5a39be7..0837b74 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
+++ b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
@@ -1,11 +1,40 @@
-<div class="totp-code-field">
+<div class="totp-code-field" ng-class="{ 'totp-details-visible' : detailsShown }">
 
     <!-- Enroll user if necessary -->
     <div class="totp-enroll" ng-show="field.qrCode">
+
         <p translate="TOTP.HELP_ENROLL_BARCODE"></p>
+
+        <!-- Barcode and key details -->
         <div class="totp-qr-code"><img ng-src="{{field.qrCode}}"></div>
+        <h3 class="totp-details-header">
+            {{'TOTP.SECTION_HEADER_DETAILS' | translate}}
+            <a class="totp-show-details" ng-click="showDetails()">{{'TOTP.ACTION_SHOW_DETAILS' | translate}}</a>
+            <a class="totp-hide-details" ng-click="hideDetails()">{{'TOTP.ACTION_HIDE_DETAILS' | translate}}</a>
+        </h3>
+        <table class="totp-details">
+            <tr>
+                <th>{{'TOTP.FIELD_HEADER_SECRET_KEY' | translate}}</th>
+                <td><span ng-repeat="group in groupedSecret"
+                          class="totp-detail">{{ group }}</span></td>
+            </tr>
+            <tr>
+                <th>{{'TOTP.FIELD_HEADER_DIGITS' | translate}}</th>
+                <td><span class="totp-detail">{{ field.digits }}</span></td>
+            </tr>
+            <tr>
+                <th>{{'TOTP.FIELD_HEADER_ALGORITHM' | translate}}</th>
+                <td><span class="totp-detail">{{ field.mode }}</span></td>
+            </tr>
+            <tr>
+                <th>{{'TOTP.FIELD_HEADER_INTERVAL' | translate}}</th>
+                <td><span class="totp-detail">{{ field.period }}</span></td>
+            </tr>
+        </table>
+
         <p translate="TOTP.HELP_ENROLL_VERIFY"
            translate-values="{ DIGITS : field.digits }"></p>
+
     </div>
 
     <!-- Field for entry of the current TOTP code -->

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/78cde50d/extensions/guacamole-auth-totp/src/main/resources/translations/en.json
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/translations/en.json b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json
index bd0e9e2..6f73aa0 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/translations/en.json
+++ b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json
@@ -10,6 +10,14 @@
 
     "TOTP" : {
 
+        "ACTION_HIDE_DETAILS" : "Hide",
+        "ACTION_SHOW_DETAILS" : "Show",
+
+        "FIELD_HEADER_ALGORITHM"  : "Algorithm:",
+        "FIELD_HEADER_DIGITS"     : "Digits:",
+        "FIELD_HEADER_INTERVAL"   : "Interval:",
+        "FIELD_HEADER_SECRET_KEY" : "Secret Key:",
+
         "FIELD_PLACEHOLDER_CODE" : "Authentication Code",
 
         "INFO_CODE_REQUIRED"       : "Please enter your authentication code to verify your identity.",
@@ -17,7 +25,9 @@
         "INFO_VERIFICATION_FAILED" : "Verification failed. Please try again.",
 
         "HELP_ENROLL_BARCODE" : "To complete the enrollment process, scan the barcode below with the two-factor authentication app on your phone or device.",
-        "HELP_ENROLL_VERIFY"  : "After scanning the barcode, enter the {DIGITS}-digit authentication code displayed to verify that enrollment was successful."
+        "HELP_ENROLL_VERIFY"  : "After scanning the barcode, enter the {DIGITS}-digit authentication code displayed to verify that enrollment was successful.",
+
+        "SECTION_HEADER_DETAILS" : "Details:"
 
     }
 


[04/21] guacamole-client git commit: GUACAMOLE-96: Abstract TOTP key into separate class with confirmation semantics.

Posted by vn...@apache.org.
GUACAMOLE-96: Abstract TOTP key into separate class with confirmation semantics.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/8e3cbf06
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/8e3cbf06
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/8e3cbf06

Branch: refs/heads/master
Commit: 8e3cbf06274c385afb99340e3b1c153a7946fb08
Parents: 4178a4b
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 10:56:35 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:17 2018 -0800

----------------------------------------------------------------------
 .../apache/guacamole/auth/totp/UserTOTPKey.java | 126 ++++++++++++++++
 .../auth/totp/UserVerificationService.java      | 147 ++++++++++++++++---
 2 files changed, 256 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8e3cbf06/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
new file mode 100644
index 0000000..d93d253
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
@@ -0,0 +1,126 @@
+/*
+ * 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;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * The key used to generate TOTP codes for a particular user.
+ */
+public class UserTOTPKey {
+
+    /**
+     * Secure source of random bytes.
+     */
+    private static final Random RANDOM = new SecureRandom();
+
+    /**
+     * Whether the associated secret key has been confirmed by the user. A key
+     * is confirmed once the user has successfully entered a valid TOTP
+     * derived from that key.
+     */
+    private boolean confirmed;
+
+    /**
+     * The base32-encoded TOTP key associated with the user.
+     */
+    private byte[] secret;
+
+    /**
+     * Generates the given number of random bytes.
+     *
+     * @param length
+     *     The number of random bytes to generate.
+     *
+     * @return
+     *     A new array of exactly the given number of random bytes.
+     */
+    private static byte[] generateBytes(int length) {
+        byte[] bytes = new byte[length];
+        RANDOM.nextBytes(bytes);
+        return bytes;
+    }
+
+    /**
+     * Creates a new, unconfirmed, randomly-generated TOTP key having the given
+     * length.
+     *
+     * @param length
+     *     The length of the key to generate, in bytes.
+     */
+    public UserTOTPKey(int length) {
+        this(generateBytes(length), false);
+    }
+
+    /**
+     * Creates a new UserTOTPKey containing the given key and having the given
+     * confirmed state.
+     *
+     * @param secret
+     *     The raw binary secret key to be used to generate TOTP codes.
+     *
+     * @param confirmed
+     *     true if the user associated with the key has confirmed that they can
+     *     successfully generate the corresponding TOTP codes (the user has
+     *     been "enrolled"), false otherwise.
+     */
+    public UserTOTPKey(byte[] secret, boolean confirmed) {
+        this.confirmed = confirmed;
+        this.secret = secret;
+    }
+
+    /**
+     * Returns the raw binary secret key to be used to generate TOTP codes.
+     *
+     * @return
+     *     The raw binary secret key to be used to generate TOTP codes.
+     */
+    public byte[] getSecret() {
+        return secret;
+    }
+
+    /**
+     * Returns whether the user associated with the key has confirmed that they
+     * can successfully generate the corresponding TOTP codes (the user has
+     * been "enrolled").
+     *
+     * @return
+     *     true if the user has confirmed that they can successfully generate
+     *     the TOTP codes generated by this key, false otherwise.
+     */
+    public boolean isConfirmed() {
+        return confirmed;
+    }
+
+    /**
+     * Sets whether the user associated with the key has confirmed that they
+     * can successfully generate the corresponding TOTP codes (the user has
+     * been "enrolled").
+     *
+     * @param confirmed
+     *     true if the user has confirmed that they can successfully generate
+     *     the TOTP codes generated by this key, false otherwise.
+     */
+    public void setConfirmed(boolean confirmed) {
+        this.confirmed = confirmed;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8e3cbf06/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
index 823c5ef..d694c5e 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
@@ -22,14 +22,17 @@ package org.apache.guacamole.auth.totp;
 import com.google.common.io.BaseEncoding;
 import java.security.InvalidKeyException;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import org.apache.guacamole.GuacamoleClientException;
 import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleUnsupportedException;
 import org.apache.guacamole.form.Field;
 import org.apache.guacamole.form.TextField;
 import org.apache.guacamole.net.auth.AuthenticatedUser;
 import org.apache.guacamole.net.auth.Credentials;
+import org.apache.guacamole.net.auth.User;
 import org.apache.guacamole.net.auth.UserContext;
 import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
 import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
@@ -50,7 +53,13 @@ public class UserVerificationService {
     /**
      * The name of the user attribute which stores the TOTP key.
      */
-    private static final String TOTP_KEY_ATTRIBUTE_NAME = "guac-totp-key";
+    private static final String TOTP_KEY_SECRET_ATTRIBUTE_NAME = "guac-totp-key-secret";
+
+    /**
+     * The name of the user attribute defines whether the TOTP key has been
+     * confirmed by the user, and the user is thus fully enrolled.
+     */
+    private static final String TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME = "guac-totp-key-confirmed";
 
     /**
      * The name of the HTTP parameter which will contain the TOTP code provided
@@ -78,20 +87,115 @@ public class UserVerificationService {
     private static final BaseEncoding BASE32 = BaseEncoding.base32();
 
     /**
-     * Retrieves the base32-encoded TOTP key associated with user having the
-     * given UserContext. If no TOTP key is associated with the user, null is
+     * Retrieves and decodes the base32-encoded TOTP key associated with user
+     * having the given UserContext. If no TOTP key is associated with the user,
+     * a random key is generated and associated with the user. If the extension
+     * storing the user does not support storage of the TOTP key, null is
      * returned.
      *
      * @param context
      *     The UserContext of the user whose TOTP key should be retrieved.
      *
      * @return
-     *     The base32-encoded TOTP key associated with user having the given
-     *     UserContext, or null if no TOTP key is associated with the user.
+     *     The TOTP key associated with the user having the given UserContext,
+     *     or null if the extension storing the user does not support storage
+     *     of the TOTP key.
+     *
+     * @throws GuacamoleException
+     *     If a new key is generated, but the extension storing the associated
+     *     user fails while updating the user account.
      */
-    public String getKey(UserContext context){
+    private UserTOTPKey getKey(UserContext context) throws GuacamoleException {
+
+        // Retrieve attributes from current user
+        User self = context.self();
         Map<String, String> attributes = context.self().getAttributes();
-        return attributes.get(TOTP_KEY_ATTRIBUTE_NAME);
+
+        // If no key is defined, attempt to generate a new key
+        String secret = attributes.get(TOTP_KEY_SECRET_ATTRIBUTE_NAME);
+        if (secret == null) {
+
+            // Generate random key for user
+            UserTOTPKey generated = new UserTOTPKey(TOTPGenerator.Mode.SHA1.getRecommendedKeyLength());
+            if (setKey(context, generated))
+                return generated;
+
+            // Fail if key cannot be set
+            return null;
+
+        }
+
+        // Parse retrieved base32 key value
+        byte[] key;
+        try {
+            key = BASE32.decode(secret);
+        }
+
+        // If key is not valid base32, warn but otherwise pretend the key does
+        // not exist
+        catch (IllegalArgumentException e) {
+            logger.warn("TOTP key of user \"{}\" is not valid base32.", self.getIdentifier());
+            logger.debug("TOTP key is not valid base32.", e);
+            return null;
+        }
+
+        // Otherwise, parse value from attributes
+        boolean confirmed = "true".equals(attributes.get(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME));
+        return new UserTOTPKey(key, confirmed);
+
+    }
+
+    /**
+     * Attempts to store the given TOTP key within the user account of the user
+     * having the given UserContext. As not all extensions will support storage
+     * of arbitrary attributes, this operation may fail.
+     *
+     * @param context
+     *     The UserContext associated with the user whose TOTP key is to be
+     *     stored.
+     *
+     * @param key
+     *     The TOTP key to store.
+     *
+     * @return
+     *     true if the TOTP key was successfully stored, false if the extension
+     *     handling storage does not support storage of the key.
+     *
+     * @throws GuacamoleException
+     *     If the extension handling storage fails internally while attempting
+     *     to update the user.
+     */
+    private boolean setKey(UserContext context, UserTOTPKey key)
+            throws GuacamoleException {
+
+        // Get mutable set of attributes
+        User self = context.self();
+        Map<String, String> attributes = new HashMap<String, String>();
+
+        // Set/overwrite current TOTP key state
+        attributes.put(TOTP_KEY_SECRET_ATTRIBUTE_NAME, BASE32.encode(key.getSecret()));
+        attributes.put(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, key.isConfirmed() ? "true" : "false");
+        self.setAttributes(attributes);
+
+        // Confirm that attributes have actually been set
+        Map<String, String> setAttributes = self.getAttributes();
+        if (!setAttributes.containsKey(TOTP_KEY_SECRET_ATTRIBUTE_NAME)
+                || !setAttributes.containsKey(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME))
+            return false;
+
+        // Update user object
+        try {
+            context.getUserDirectory().update(self);
+        }
+        catch (GuacamoleUnsupportedException e) {
+            logger.debug("Extension storage for user is explicitly read-only. "
+                    + "Cannot update attributes to store TOTP key.", e);
+            return false;
+        }
+
+        // TOTP key successfully stored/updated
+        return true;
+
     }
 
     /**
@@ -121,8 +225,8 @@ public class UserVerificationService {
             return;
 
         // Ignore users which do not have an associated key
-        String encodedKey = getKey(context);
-        if (encodedKey == null)
+        UserTOTPKey key = getKey(context);
+        if (key == null)
             return;
 
         // Pull the original HTTP request used to authenticate
@@ -133,27 +237,36 @@ public class UserVerificationService {
         String code = request.getParameter(TOTP_PARAMETER_NAME);
 
         // If no TOTP provided, request one
-        if (code == null)
+        if (code == null) {
+
+            // FIXME: Handle key.isConfirmed() for initial prompt
             throw new GuacamoleInsufficientCredentialsException(
                     "LOGIN.INFO_TOTP_REQUIRED", TOTP_CREDENTIALS);
 
+        }
+
         try {
 
             // Verify provided TOTP against value produced by generator
-            byte[] key = BASE32.decode(encodedKey);
-            TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA1, 6);
-            if (code.equals(totp.generate()) || code.equals(totp.previous()))
+            TOTPGenerator totp = new TOTPGenerator(key.getSecret(), TOTPGenerator.Mode.SHA1, 6);
+            if (code.equals(totp.generate()) || code.equals(totp.previous())) {
+
+                // Record key as confirmed, if it hasn't already been so recorded
+                if (!key.isConfirmed()) {
+                    key.setConfirmed(true);
+                    setKey(context, key);
+                }
+
+                // User has been verified
                 return;
 
+            }
+
         }
         catch (InvalidKeyException e) {
             logger.warn("User \"{}\" is associated with an invalid TOTP key.", username);
             logger.debug("TOTP key is not valid.", e);
         }
-        catch (IllegalArgumentException e) {
-            logger.warn("TOTP key of user \"{}\" is not valid base32.", username);
-            logger.debug("TOTP key is not valid base32.", e);
-        }
 
         // Provided code is not valid
         throw new GuacamoleClientException("LOGIN.INFO_TOTP_VERIFICATION_FAILED");


[06/21] guacamole-client git commit: GUACAMOLE-96: Include recommended key length for each TOTP mode.

Posted by vn...@apache.org.
GUACAMOLE-96: Include recommended key length for each TOTP mode.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/4178a4b8
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/4178a4b8
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/4178a4b8

Branch: refs/heads/master
Commit: 4178a4b8b3f4898db4af9159ea2d3df747a01638
Parents: 78c398f
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 10:37:23 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:17 2018 -0800

----------------------------------------------------------------------
 .../apache/guacamole/totp/TOTPGenerator.java    | 33 +++++++++++++++++---
 1 file changed, 29 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/4178a4b8/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java
index b8c0d95..d075c8a 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java
@@ -124,19 +124,19 @@ public class TOTPGenerator {
          * TOTP mode which generates hashes using SHA1. TOTP in SHA1 mode
          * requires 160-bit keys.
          */
-        SHA1("HmacSHA1"),
+        SHA1("HmacSHA1", 20),
 
         /**
          * TOTP mode which generates hashes using SHA256. TOTP in SHA256 mode
          * requires 256-bit keys.
          */
-        SHA256("HmacSHA256"),
+        SHA256("HmacSHA256", 32),
 
         /**
          * TOTP mode which generates hashes using SHA512. TOTP in SHA512 mode
          * requires 512-bit keys.
          */
-        SHA512("HmacSHA512");
+        SHA512("HmacSHA512", 64);
 
         /**
          * The name of the HMAC algorithm which the TOTP implementation should
@@ -146,6 +146,13 @@ public class TOTPGenerator {
         private final String algorithmName;
 
         /**
+         * The recommended length of keys generated for TOTP in this mode, in
+         * bytes. Keys are recommended to be the same length as the hash
+         * involved.
+         */
+        private final int recommendedKeyLength;
+
+        /**
          * Creates a new TOTP operating mode which is associated with the
          * given HMAC algorithm.
          *
@@ -153,9 +160,14 @@ public class TOTPGenerator {
          *     The name of the HMAC algorithm which the TOTP implementation
          *     should use when operating in this mode, in the format required
          *     by Mac.getInstance().
+         *
+         * @param recommendedKeyLength
+         *     The recommended length of keys generated for TOTP in this mode,
+         *     in bytes.
          */
-        private Mode(String algorithmName) {
+        private Mode(String algorithmName, int recommendedKeyLength) {
             this.algorithmName = algorithmName;
+            this.recommendedKeyLength = recommendedKeyLength;
         }
 
         /**
@@ -171,6 +183,19 @@ public class TOTPGenerator {
             return algorithmName;
         }
 
+        /**
+         * Returns the recommended length of keys generated for TOTP in this
+         * mode, in bytes. Keys are recommended to be the same length as the
+         * hash involved.
+         *
+         * @return
+         *     The recommended length of keys generated for TOTP in this mode,
+         *     in bytes.
+         */
+        public int getRecommendedKeyLength() {
+            return recommendedKeyLength;
+        }
+
     }
 
     /**


[08/21] guacamole-client git commit: GUACAMOLE-96: Add skeleton TOTP authentication extension (hard-coded, fake TOTP).

Posted by vn...@apache.org.
GUACAMOLE-96: Add skeleton TOTP authentication extension (hard-coded, fake TOTP).


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/264fd24b
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/264fd24b
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/264fd24b

Branch: refs/heads/master
Commit: 264fd24b6586b66392bf9e2c022fa31918719a8a
Parents: 1b26298
Author: Michael Jumper <mj...@apache.org>
Authored: Sun Nov 19 21:14:18 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:17 2018 -0800

----------------------------------------------------------------------
 extensions/guacamole-auth-totp/.gitignore       |   3 +
 extensions/guacamole-auth-totp/pom.xml          | 159 ++++++++++++
 .../guacamole-auth-totp/src/licenses/LICENSE    | 239 +++++++++++++++++++
 .../guacamole-auth-totp/src/licenses/NOTICE     |   5 +
 .../src/licenses/bundled/README                 |   4 +
 .../licenses/bundled/aopalliance-1.0/LICENSE    |   4 +
 .../src/licenses/bundled/guice-3.0/COPYING      | 202 ++++++++++++++++
 .../bundled/javax.inject-1/LICENSE-2.0.txt      | 202 ++++++++++++++++
 .../src/main/assembly/dist.xml                  |  53 ++++
 .../auth/totp/TOTPAuthenticationProvider.java   | 123 ++++++++++
 .../totp/TOTPAuthenticationProviderModule.java  |  78 ++++++
 .../auth/totp/UserVerificationService.java      | 102 ++++++++
 .../src/main/resources/guac-manifest.json       |  16 ++
 .../src/main/resources/license.txt              |  18 ++
 .../src/main/resources/translations/en.json     |  13 +
 pom.xml                                         |   1 +
 16 files changed, 1222 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/.gitignore
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/.gitignore b/extensions/guacamole-auth-totp/.gitignore
new file mode 100644
index 0000000..1de9633
--- /dev/null
+++ b/extensions/guacamole-auth-totp/.gitignore
@@ -0,0 +1,3 @@
+src/main/resources/generated/
+target/
+*~

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/pom.xml
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/pom.xml b/extensions/guacamole-auth-totp/pom.xml
new file mode 100644
index 0000000..b7fa965
--- /dev/null
+++ b/extensions/guacamole-auth-totp/pom.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+                        http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.apache.guacamole</groupId>
+    <artifactId>guacamole-auth-totp</artifactId>
+    <packaging>jar</packaging>
+    <version>0.9.14</version>
+    <name>guacamole-auth-totp</name>
+    <url>http://guacamole.incubator.apache.org/</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <build>
+        <plugins>
+
+            <!-- Written for 1.6 -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.3</version>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                    <compilerArgs>
+                        <arg>-Xlint:all</arg>
+                        <arg>-Werror</arg>
+                    </compilerArgs>
+                    <fork>true</fork>
+                </configuration>
+            </plugin>
+
+            <!-- Assembly plugin - for easy distribution -->
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.5.3</version>
+                <configuration>
+                    <finalName>${project.artifactId}-${project.version}</finalName>
+                    <appendAssemblyId>false</appendAssemblyId>
+                    <descriptors>
+                        <descriptor>src/main/assembly/dist.xml</descriptor>
+                    </descriptors>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-dist-archive</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- Copy dependencies prior to packaging -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>2.10</version>
+                <executions>
+                    <execution>
+                        <id>unpack-dependencies</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>unpack-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <includeScope>runtime</includeScope>
+                            <outputDirectory>${project.build.directory}/classes</outputDirectory>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- Verify format using Apache RAT -->
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <version>0.12</version>
+
+                <configuration>
+                    <excludes>
+                        <exclude>**/*.json</exclude>
+                        <exclude>src/licenses/**/*</exclude>
+                    </excludes>
+                </configuration>
+
+                <!-- Bind RAT to validate phase -->
+                <executions>
+                    <execution>
+                        <id>validate</id>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                    </execution>
+                </executions>
+
+            </plugin>
+
+        </plugins>
+    </build>
+
+    <dependencies>
+
+       <!-- Guacamole Extension API -->
+        <dependency>
+            <groupId>org.apache.guacamole</groupId>
+            <artifactId>guacamole-ext</artifactId>
+            <version>0.9.14</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Guice -->
+        <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+            <version>3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject.extensions</groupId>
+            <artifactId>guice-multibindings</artifactId>
+            <version>3.0</version>
+        </dependency>
+
+        <!-- Java servlet API -->
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.5</version>
+            <scope>provided</scope>
+        </dependency>
+
+    </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/licenses/LICENSE
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/licenses/LICENSE b/extensions/guacamole-auth-totp/src/licenses/LICENSE
new file mode 100644
index 0000000..c6cbf77
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/licenses/LICENSE
@@ -0,0 +1,239 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
+
+
+==============================================================================
+
+APACHE GUACAMOLE SUBCOMPONENTS
+
+Apache Guacamole includes a number of subcomponents with separate copyright
+notices and license terms. Your use of these subcomponents is subject to the
+terms and conditions of the following licenses.
+
+
+AOP Alliance (http://aopalliance.sourceforge.net/)
+--------------------------------------------------
+
+    Version: 1.0
+    From: 'AOP Alliance' (http://aopalliance.sourceforge.net/members.html)
+    License(s):
+        Public Domain (bundled/aopalliance-1.0/LICENSE)
+
+
+Google Guice (https://github.com/google/guice)
+----------------------------------------------
+
+    Version: 3.0
+    From: 'Google Inc.' (http://www.google.com/)
+    License(s):
+        Apache v2.0 (bundled/guice-3.0/COPYING)
+
+
+JSR-330 / Dependency Injection for Java (http://code.google.com/p/atinject/)
+----------------------------------------------------------------------------
+
+    Version: 1
+    From: 'JSR-330 Expert Group' (https://jcp.org/en/jsr/detail?id=330)
+    License(s):
+        Apache v2.0 (bundled/javax.inject-1/LICENSE-2.0.txt)
+

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/licenses/NOTICE
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/licenses/NOTICE b/extensions/guacamole-auth-totp/src/licenses/NOTICE
new file mode 100644
index 0000000..47f2b4c
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/licenses/NOTICE
@@ -0,0 +1,5 @@
+Apache Guacamole
+Copyright 2017 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/licenses/bundled/README
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/README b/extensions/guacamole-auth-totp/src/licenses/bundled/README
new file mode 100644
index 0000000..47ba19d
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/licenses/bundled/README
@@ -0,0 +1,4 @@
+Apache Guacamole includes a number of subcomponents with separate copyright
+notices and license terms. Your use of these subcomponents is subject to the
+terms and conditions of their respective licenses, included within this
+directory for reference.

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/licenses/bundled/aopalliance-1.0/LICENSE
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/aopalliance-1.0/LICENSE b/extensions/guacamole-auth-totp/src/licenses/bundled/aopalliance-1.0/LICENSE
new file mode 100644
index 0000000..8e0e378
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/licenses/bundled/aopalliance-1.0/LICENSE
@@ -0,0 +1,4 @@
+From http://aopalliance.sourceforge.net/:
+
+    LICENCE: all the source code provided by AOP Alliance is Public Domain.
+

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/licenses/bundled/guice-3.0/COPYING
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/guice-3.0/COPYING b/extensions/guacamole-auth-totp/src/licenses/bundled/guice-3.0/COPYING
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/licenses/bundled/guice-3.0/COPYING
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/licenses/bundled/javax.inject-1/LICENSE-2.0.txt
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/javax.inject-1/LICENSE-2.0.txt b/extensions/guacamole-auth-totp/src/licenses/bundled/javax.inject-1/LICENSE-2.0.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/licenses/bundled/javax.inject-1/LICENSE-2.0.txt
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/main/assembly/dist.xml
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/assembly/dist.xml b/extensions/guacamole-auth-totp/src/main/assembly/dist.xml
new file mode 100644
index 0000000..b89fd53
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/assembly/dist.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<assembly
+    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+    
+    <id>dist</id>
+    <baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
+
+    <!-- Output tar.gz -->
+    <formats>
+        <format>tar.gz</format>
+    </formats>
+
+    <!-- Include licenses and extension .jar -->
+    <fileSets>
+
+        <!-- Include licenses -->
+        <fileSet>
+            <outputDirectory></outputDirectory>
+            <directory>src/licenses</directory>
+        </fileSet>
+
+        <!-- Include extension .jar -->
+        <fileSet>
+            <directory>target</directory>
+            <outputDirectory></outputDirectory>
+            <includes>
+                <include>*.jar</include>
+            </includes>
+        </fileSet>
+
+    </fileSets>
+
+</assembly>

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
new file mode 100644
index 0000000..835ba87
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
@@ -0,0 +1,123 @@
+/*
+ * 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;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.auth.AuthenticatedUser;
+import org.apache.guacamole.net.auth.AuthenticationProvider;
+import org.apache.guacamole.net.auth.Credentials;
+import org.apache.guacamole.net.auth.UserContext;
+
+/**
+ * AuthenticationProvider implementation which uses TOTP as an additional
+ * authentication factor for users which have already been authenticated by
+ * some other AuthenticationProvider.
+ */
+public class TOTPAuthenticationProvider implements AuthenticationProvider {
+
+    /**
+     * Injector which will manage the object graph of this authentication
+     * provider.
+     */
+    private final Injector injector;
+
+    /**
+     * Creates a new TOTPAuthenticationProvider that verifies users using TOTP.
+     *
+     * @throws GuacamoleException
+     *     If a required property is missing, or an error occurs while parsing
+     *     a property.
+     */
+    public TOTPAuthenticationProvider() throws GuacamoleException {
+
+        // Set up Guice injector.
+        injector = Guice.createInjector(
+            new TOTPAuthenticationProviderModule(this)
+        );
+
+    }
+
+    @Override
+    public String getIdentifier() {
+        return "totp";
+    }
+
+    @Override
+    public Object getResource() {
+        return null;
+    }
+
+    @Override
+    public AuthenticatedUser authenticateUser(Credentials credentials)
+            throws GuacamoleException {
+        return null;
+    }
+
+    @Override
+    public AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
+            Credentials credentials) throws GuacamoleException {
+        return authenticatedUser;
+    }
+
+    @Override
+    public UserContext getUserContext(AuthenticatedUser authenticatedUser)
+            throws GuacamoleException {
+        return null;
+    }
+
+    @Override
+    public UserContext updateUserContext(UserContext context,
+            AuthenticatedUser authenticatedUser, Credentials credentials)
+                throws GuacamoleException {
+        return context;
+    }
+
+    @Override
+    public UserContext decorate(UserContext context,
+            AuthenticatedUser authenticatedUser, Credentials credentials)
+            throws GuacamoleException {
+
+        UserVerificationService verificationService =
+                injector.getInstance(UserVerificationService.class);
+
+        // Verify identity of user
+        verificationService.verifyIdentity(context, authenticatedUser);
+
+        // User has been verified, and authentication should be allowed to
+        // continue
+        return context;
+
+    }
+
+    @Override
+    public UserContext redecorate(UserContext decorated, UserContext context,
+            AuthenticatedUser authenticatedUser, Credentials credentials)
+            throws GuacamoleException {
+        return context;
+    }
+
+    @Override
+    public void shutdown() {
+        // Do nothing
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
new file mode 100644
index 0000000..757a412
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import com.google.inject.AbstractModule;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.environment.Environment;
+import org.apache.guacamole.environment.LocalEnvironment;
+import org.apache.guacamole.net.auth.AuthenticationProvider;
+
+/**
+ * Guice module which configures TOTP-specific injections.
+ */
+public class TOTPAuthenticationProviderModule extends AbstractModule {
+
+    /**
+     * Guacamole server environment.
+     */
+    private final Environment environment;
+
+    /**
+     * A reference to the TOTPAuthenticationProvider on behalf of which this
+     * module has configured injection.
+     */
+    private final AuthenticationProvider authProvider;
+
+    /**
+     * Creates a new TOTP authentication provider module which configures
+     * injection for the TOTPAuthenticationProvider.
+     *
+     * @param authProvider
+     *     The AuthenticationProvider for which injection is being configured.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while retrieving the Guacamole server
+     *     environment.
+     */
+    public TOTPAuthenticationProviderModule(AuthenticationProvider authProvider)
+            throws GuacamoleException {
+
+        // Get local environment
+        this.environment = new LocalEnvironment();
+
+        // Store associated auth provider
+        this.authProvider = authProvider;
+
+    }
+
+    @Override
+    protected void configure() {
+
+        // Bind core implementations of guacamole-ext classes
+        bind(AuthenticationProvider.class).toInstance(authProvider);
+        bind(Environment.class).toInstance(environment);
+
+        // Bind TOTP-specific services
+        bind(UserVerificationService.class);
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
new file mode 100644
index 0000000..f28149a
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.guacamole.GuacamoleClientException;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.form.Field;
+import org.apache.guacamole.form.TextField;
+import org.apache.guacamole.net.auth.AuthenticatedUser;
+import org.apache.guacamole.net.auth.Credentials;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
+
+/**
+ * Service for verifying the identity of a user using TOTP.
+ */
+public class UserVerificationService {
+
+    /**
+     * The name of the HTTP parameter which will contain the TOTP code provided
+     * by the user to verify their identity.
+     */
+    private static final String TOTP_PARAMETER_NAME = "guac-totp";
+
+    /**
+     * The field which should be exposed to the user to request that they
+     * provide their TOTP code.
+     */
+    private static final Field TOTP_FIELD = new TextField(TOTP_PARAMETER_NAME);
+
+    /**
+     * CredentialsInfo object describing the credentials expected for a user
+     * who has verified their identity with TOTP.
+     */
+    private static final CredentialsInfo TOTP_CREDENTIALS = new CredentialsInfo(
+            Collections.singletonList(TOTP_FIELD)
+    );
+
+    /**
+     * Verifies the identity of the given user using TOTP. If a authentication
+     * code from the user's TOTP device has not already been provided, a code is
+     * requested in the form of additional expected credentials. Any provided
+     * code is cryptographically verified. If no code is present, or the
+     * received code is invalid, an exception is thrown.
+     *
+     * @param context
+     *     The UserContext provided for the user by another authentication
+     *     extension.
+     *
+     * @param authenticatedUser
+     *     The user whose identity should be verified using TOTP.
+     *
+     * @throws GuacamoleException
+     *     If required TOTP-specific configuration options are missing or
+     *     malformed, or if the user's identity cannot be verified.
+     */
+    public void verifyIdentity(UserContext context,
+            AuthenticatedUser authenticatedUser) throws GuacamoleException {
+
+        // Pull the original HTTP request used to authenticate
+        Credentials credentials = authenticatedUser.getCredentials();
+        HttpServletRequest request = credentials.getRequest();
+
+        // Ignore anonymous users
+        if (authenticatedUser.getIdentifier().equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
+            return;
+
+        // Retrieve TOTP from request
+        String totp = request.getParameter(TOTP_PARAMETER_NAME);
+
+        // If no TOTP provided, request one
+        if (totp == null)
+            throw new GuacamoleInsufficientCredentialsException(
+                    "LOGIN.INFO_TOTP_REQUIRED", TOTP_CREDENTIALS);
+
+        // FIXME: Hard-coded code
+        if (!totp.equals("123456"))
+            throw new GuacamoleClientException("LOGIN.INFO_TOTP_VERIFICATION_FAILED");
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json
new file mode 100644
index 0000000..539562c
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json
@@ -0,0 +1,16 @@
+{
+
+    "guacamoleVersion" : "0.9.14",
+
+    "name"      : "TOTP TFA Authentication Backend",
+    "namespace" : "totp",
+
+    "authProviders" : [
+        "org.apache.guacamole.auth.totp.TOTPAuthenticationProvider"
+    ],
+
+    "translations" : [
+        "translations/en.json"
+    ]
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/main/resources/license.txt
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/license.txt b/extensions/guacamole-auth-totp/src/main/resources/license.txt
new file mode 100644
index 0000000..042f3ce
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/resources/license.txt
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/translations/en.json b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json
new file mode 100644
index 0000000..540b94e
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json
@@ -0,0 +1,13 @@
+{
+
+    "DATA_SOURCE_TOTP" : {
+        "NAME" : "TOTP TFA Backend"
+    },
+
+    "LOGIN" : {
+        "FIELD_HEADER_GUAC_TOTP"        : "Authentication Code",
+        "INFO_TOTP_REQUIRED"            : "Please enter your authentication code to verify your identity.",
+        "INFO_TOTP_VERIFICATION_FAILED" : "Verification failed. Please try again."
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/264fd24b/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 59cff02..4f7c8ed 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,6 +55,7 @@
         <module>extensions/guacamole-auth-jdbc</module>
         <module>extensions/guacamole-auth-ldap</module>
         <module>extensions/guacamole-auth-openid</module>
+        <module>extensions/guacamole-auth-totp</module>
 
         <!-- Example web applications using the Guacamole APIs -->
         <module>doc/guacamole-example</module>


[19/21] guacamole-client git commit: GUACAMOLE-96: Block external access to TOTP-internal attributes.

Posted by vn...@apache.org.
GUACAMOLE-96: Block external access to TOTP-internal attributes.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/96e3d029
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/96e3d029
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/96e3d029

Branch: refs/heads/master
Commit: 96e3d029992ac09d27aac808c489779000fb6fe1
Parents: 2a894c4
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 16:15:01 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:18 2018 -0800

----------------------------------------------------------------------
 .../auth/totp/TOTPAuthenticationProvider.java   |   6 +-
 .../totp/TOTPAuthenticationProviderModule.java  |   1 +
 .../apache/guacamole/auth/totp/UserTOTPKey.java | 148 ----------
 .../auth/totp/UserVerificationService.java      | 292 -------------------
 .../auth/totp/form/AuthenticationCodeField.java |   2 +-
 .../guacamole/auth/totp/user/TOTPUser.java      | 102 +++++++
 .../auth/totp/user/TOTPUserContext.java         |  64 ++++
 .../guacamole/auth/totp/user/UserTOTPKey.java   | 148 ++++++++++
 .../auth/totp/user/UserVerificationService.java | 281 ++++++++++++++++++
 9 files changed, 601 insertions(+), 443 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
index 835ba87..28e2380 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
@@ -19,9 +19,11 @@
 
 package org.apache.guacamole.auth.totp;
 
+import org.apache.guacamole.auth.totp.user.UserVerificationService;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.auth.totp.user.TOTPUserContext;
 import org.apache.guacamole.net.auth.AuthenticatedUser;
 import org.apache.guacamole.net.auth.AuthenticationProvider;
 import org.apache.guacamole.net.auth.Credentials;
@@ -104,7 +106,7 @@ public class TOTPAuthenticationProvider implements AuthenticationProvider {
 
         // User has been verified, and authentication should be allowed to
         // continue
-        return context;
+        return new TOTPUserContext(context);
 
     }
 
@@ -112,7 +114,7 @@ public class TOTPAuthenticationProvider implements AuthenticationProvider {
     public UserContext redecorate(UserContext decorated, UserContext context,
             AuthenticatedUser authenticatedUser, Credentials credentials)
             throws GuacamoleException {
-        return context;
+        return new TOTPUserContext(context);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
index e72beec..94b7232 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
@@ -19,6 +19,7 @@
 
 package org.apache.guacamole.auth.totp;
 
+import org.apache.guacamole.auth.totp.user.UserVerificationService;
 import com.google.inject.AbstractModule;
 import org.apache.guacamole.GuacamoleException;
 import org.apache.guacamole.auth.totp.conf.ConfigurationService;

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
deleted file mode 100644
index 3de3785..0000000
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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;
-
-import java.security.SecureRandom;
-import java.util.Random;
-
-/**
- * The key used to generate TOTP codes for a particular user.
- */
-public class UserTOTPKey {
-
-    /**
-     * Secure source of random bytes.
-     */
-    private static final Random RANDOM = new SecureRandom();
-
-    /**
-     * The username of the user associated with this key.
-     */
-    private final String username;
-
-    /**
-     * Whether the associated secret key has been confirmed by the user. A key
-     * is confirmed once the user has successfully entered a valid TOTP
-     * derived from that key.
-     */
-    private boolean confirmed;
-
-    /**
-     * The base32-encoded TOTP key associated with the user.
-     */
-    private byte[] secret;
-
-    /**
-     * Generates the given number of random bytes.
-     *
-     * @param length
-     *     The number of random bytes to generate.
-     *
-     * @return
-     *     A new array of exactly the given number of random bytes.
-     */
-    private static byte[] generateBytes(int length) {
-        byte[] bytes = new byte[length];
-        RANDOM.nextBytes(bytes);
-        return bytes;
-    }
-
-    /**
-     * Creates a new, unconfirmed, randomly-generated TOTP key having the given
-     * length.
-     *
-     * @param username
-     *     The username of the user associated with this key.
-     *
-     * @param length
-     *     The length of the key to generate, in bytes.
-     */
-    public UserTOTPKey(String username, int length) {
-        this(username, generateBytes(length), false);
-    }
-
-    /**
-     * Creates a new UserTOTPKey containing the given key and having the given
-     * confirmed state.
-     *
-     * @param username
-     *     The username of the user associated with this key.
-     *
-     * @param secret
-     *     The raw binary secret key to be used to generate TOTP codes.
-     *
-     * @param confirmed
-     *     true if the user associated with the key has confirmed that they can
-     *     successfully generate the corresponding TOTP codes (the user has
-     *     been "enrolled"), false otherwise.
-     */
-    public UserTOTPKey(String username, byte[] secret, boolean confirmed) {
-        this.username = username;
-        this.confirmed = confirmed;
-        this.secret = secret;
-    }
-
-    /**
-     * Returns the username of the user associated with this key.
-     *
-     * @return
-     *     The username of the user associated with this key.
-     */
-    public String getUsername() {
-        return username;
-    }
-
-    /**
-     * Returns the raw binary secret key to be used to generate TOTP codes.
-     *
-     * @return
-     *     The raw binary secret key to be used to generate TOTP codes.
-     */
-    public byte[] getSecret() {
-        return secret;
-    }
-
-    /**
-     * Returns whether the user associated with the key has confirmed that they
-     * can successfully generate the corresponding TOTP codes (the user has
-     * been "enrolled").
-     *
-     * @return
-     *     true if the user has confirmed that they can successfully generate
-     *     the TOTP codes generated by this key, false otherwise.
-     */
-    public boolean isConfirmed() {
-        return confirmed;
-    }
-
-    /**
-     * Sets whether the user associated with the key has confirmed that they
-     * can successfully generate the corresponding TOTP codes (the user has
-     * been "enrolled").
-     *
-     * @param confirmed
-     *     true if the user has confirmed that they can successfully generate
-     *     the TOTP codes generated by this key, false otherwise.
-     */
-    public void setConfirmed(boolean confirmed) {
-        this.confirmed = confirmed;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
deleted file mode 100644
index 851bb94..0000000
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * 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;
-
-import com.google.common.io.BaseEncoding;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.security.InvalidKeyException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import javax.servlet.http.HttpServletRequest;
-import org.apache.guacamole.GuacamoleClientException;
-import org.apache.guacamole.GuacamoleException;
-import org.apache.guacamole.GuacamoleUnsupportedException;
-import org.apache.guacamole.auth.totp.conf.ConfigurationService;
-import org.apache.guacamole.auth.totp.form.AuthenticationCodeField;
-import org.apache.guacamole.form.Field;
-import org.apache.guacamole.net.auth.AuthenticatedUser;
-import org.apache.guacamole.net.auth.Credentials;
-import org.apache.guacamole.net.auth.User;
-import org.apache.guacamole.net.auth.UserContext;
-import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
-import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
-import org.apache.guacamole.totp.TOTPGenerator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Service for verifying the identity of a user using TOTP.
- */
-public class UserVerificationService {
-
-    /**
-     * Logger for this class.
-     */
-    private final Logger logger = LoggerFactory.getLogger(UserVerificationService.class);
-
-    /**
-     * The name of the user attribute which stores the TOTP key.
-     */
-    private static final String TOTP_KEY_SECRET_ATTRIBUTE_NAME = "guac-totp-key-secret";
-
-    /**
-     * The name of the user attribute defines whether the TOTP key has been
-     * confirmed by the user, and the user is thus fully enrolled.
-     */
-    private static final String TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME = "guac-totp-key-confirmed";
-
-    /**
-     * BaseEncoding instance which decoded/encodes base32.
-     */
-    private static final BaseEncoding BASE32 = BaseEncoding.base32();
-
-    /**
-     * Service for retrieving configuration information.
-     */
-    @Inject
-    private ConfigurationService confService;
-
-    /**
-     * Provider for AuthenticationCodeField instances.
-     */
-    @Inject
-    private Provider<AuthenticationCodeField> codeFieldProvider;
-
-    /**
-     * Retrieves and decodes the base32-encoded TOTP key associated with user
-     * having the given UserContext. If no TOTP key is associated with the user,
-     * a random key is generated and associated with the user. If the extension
-     * storing the user does not support storage of the TOTP key, null is
-     * returned.
-     *
-     * @param context
-     *     The UserContext of the user whose TOTP key should be retrieved.
-     *
-     * @param username
-     *     The username of the user associated with the given UserContext.
-     *
-     * @return
-     *     The TOTP key associated with the user having the given UserContext,
-     *     or null if the extension storing the user does not support storage
-     *     of the TOTP key.
-     *
-     * @throws GuacamoleException
-     *     If a new key is generated, but the extension storing the associated
-     *     user fails while updating the user account.
-     */
-    private UserTOTPKey getKey(UserContext context,
-            String username) throws GuacamoleException {
-
-        // Retrieve attributes from current user
-        User self = context.self();
-        Map<String, String> attributes = context.self().getAttributes();
-
-        // If no key is defined, attempt to generate a new key
-        String secret = attributes.get(TOTP_KEY_SECRET_ATTRIBUTE_NAME);
-        if (secret == null) {
-
-            // Generate random key for user
-            TOTPGenerator.Mode mode = confService.getMode();
-            UserTOTPKey generated = new UserTOTPKey(username,mode.getRecommendedKeyLength());
-            if (setKey(context, generated))
-                return generated;
-
-            // Fail if key cannot be set
-            return null;
-
-        }
-
-        // Parse retrieved base32 key value
-        byte[] key;
-        try {
-            key = BASE32.decode(secret);
-        }
-
-        // If key is not valid base32, warn but otherwise pretend the key does
-        // not exist
-        catch (IllegalArgumentException e) {
-            logger.warn("TOTP key of user \"{}\" is not valid base32.", self.getIdentifier());
-            logger.debug("TOTP key is not valid base32.", e);
-            return null;
-        }
-
-        // Otherwise, parse value from attributes
-        boolean confirmed = "true".equals(attributes.get(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME));
-        return new UserTOTPKey(username, key, confirmed);
-
-    }
-
-    /**
-     * Attempts to store the given TOTP key within the user account of the user
-     * having the given UserContext. As not all extensions will support storage
-     * of arbitrary attributes, this operation may fail.
-     *
-     * @param context
-     *     The UserContext associated with the user whose TOTP key is to be
-     *     stored.
-     *
-     * @param key
-     *     The TOTP key to store.
-     *
-     * @return
-     *     true if the TOTP key was successfully stored, false if the extension
-     *     handling storage does not support storage of the key.
-     *
-     * @throws GuacamoleException
-     *     If the extension handling storage fails internally while attempting
-     *     to update the user.
-     */
-    private boolean setKey(UserContext context, UserTOTPKey key)
-            throws GuacamoleException {
-
-        // Get mutable set of attributes
-        User self = context.self();
-        Map<String, String> attributes = new HashMap<String, String>();
-
-        // Set/overwrite current TOTP key state
-        attributes.put(TOTP_KEY_SECRET_ATTRIBUTE_NAME, BASE32.encode(key.getSecret()));
-        attributes.put(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, key.isConfirmed() ? "true" : "false");
-        self.setAttributes(attributes);
-
-        // Confirm that attributes have actually been set
-        Map<String, String> setAttributes = self.getAttributes();
-        if (!setAttributes.containsKey(TOTP_KEY_SECRET_ATTRIBUTE_NAME)
-                || !setAttributes.containsKey(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME))
-            return false;
-
-        // Update user object
-        try {
-            context.getUserDirectory().update(self);
-        }
-        catch (GuacamoleUnsupportedException e) {
-            logger.debug("Extension storage for user is explicitly read-only. "
-                    + "Cannot update attributes to store TOTP key.", e);
-            return false;
-        }
-
-        // TOTP key successfully stored/updated
-        return true;
-
-    }
-
-    /**
-     * Verifies the identity of the given user using TOTP. If a authentication
-     * code from the user's TOTP device has not already been provided, a code is
-     * requested in the form of additional expected credentials. Any provided
-     * code is cryptographically verified. If no code is present, or the
-     * received code is invalid, an exception is thrown.
-     *
-     * @param context
-     *     The UserContext provided for the user by another authentication
-     *     extension.
-     *
-     * @param authenticatedUser
-     *     The user whose identity should be verified using TOTP.
-     *
-     * @throws GuacamoleException
-     *     If required TOTP-specific configuration options are missing or
-     *     malformed, or if the user's identity cannot be verified.
-     */
-    public void verifyIdentity(UserContext context,
-            AuthenticatedUser authenticatedUser) throws GuacamoleException {
-
-        // Ignore anonymous users
-        String username = authenticatedUser.getIdentifier();
-        if (username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
-            return;
-
-        // Ignore users which do not have an associated key
-        UserTOTPKey key = getKey(context, username);
-        if (key == null)
-            return;
-
-        // Pull the original HTTP request used to authenticate
-        Credentials credentials = authenticatedUser.getCredentials();
-        HttpServletRequest request = credentials.getRequest();
-
-        // Retrieve TOTP from request
-        String code = request.getParameter(AuthenticationCodeField.PARAMETER_NAME);
-
-        // If no TOTP provided, request one
-        if (code == null) {
-
-            AuthenticationCodeField field = codeFieldProvider.get();
-
-            // If the user hasn't completed enrollment, request that they do
-            if (!key.isConfirmed()) {
-                field.exposeKey(key);
-                throw new GuacamoleInsufficientCredentialsException(
-                        "TOTP.INFO_ENROLL_REQUIRED", new CredentialsInfo(
-                            Collections.<Field>singletonList(field)
-                        ));
-            }
-
-            // Otherwise simply request the user's authentication code
-            throw new GuacamoleInsufficientCredentialsException(
-                    "TOTP.INFO_CODE_REQUIRED", new CredentialsInfo(
-                        Collections.<Field>singletonList(field)
-                    ));
-
-        }
-
-        try {
-
-            // Get generator based on user's key and provided configuration
-            TOTPGenerator totp = new TOTPGenerator(key.getSecret(),
-                    confService.getMode(), confService.getDigits());
-
-            // Verify provided TOTP against value produced by generator
-            if (code.equals(totp.generate()) || code.equals(totp.previous())) {
-
-                // Record key as confirmed, if it hasn't already been so recorded
-                if (!key.isConfirmed()) {
-                    key.setConfirmed(true);
-                    setKey(context, key);
-                }
-
-                // User has been verified
-                return;
-
-            }
-
-        }
-        catch (InvalidKeyException e) {
-            logger.warn("User \"{}\" is associated with an invalid TOTP key.", username);
-            logger.debug("TOTP key is not valid.", e);
-        }
-
-        // Provided code is not valid
-        throw new GuacamoleClientException("TOTP.INFO_VERIFICATION_FAILED");
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
index c3ca207..764fe95 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
@@ -32,7 +32,7 @@ import java.net.URI;
 import javax.ws.rs.core.UriBuilder;
 import javax.xml.bind.DatatypeConverter;
 import org.apache.guacamole.GuacamoleException;
-import org.apache.guacamole.auth.totp.UserTOTPKey;
+import org.apache.guacamole.auth.totp.user.UserTOTPKey;
 import org.apache.guacamole.auth.totp.conf.ConfigurationService;
 import org.apache.guacamole.form.Field;
 import org.codehaus.jackson.annotate.JsonProperty;

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java
new file mode 100644
index 0000000..4199d43
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java
@@ -0,0 +1,102 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.Map;
+import org.apache.guacamole.net.auth.DelegatingUser;
+import org.apache.guacamole.net.auth.User;
+
+/**
+ * TOTP-specific User implementation which wraps a User from another extension,
+ * hiding and blocking access to the core attributes used by TOTP.
+ */
+public class TOTPUser extends DelegatingUser {
+
+    /**
+     * The name of the user attribute which stores the TOTP key.
+     */
+    public static final String TOTP_KEY_SECRET_ATTRIBUTE_NAME = "guac-totp-key-secret";
+
+    /**
+     * The name of the user attribute defines whether the TOTP key has been
+     * confirmed by the user, and the user is thus fully enrolled.
+     */
+    public static final String TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME = "guac-totp-key-confirmed";
+
+    /**
+     * The User object wrapped by this TOTPUser.
+     */
+    private final User undecorated;
+
+    /**
+     * Wraps the given User object, hiding and blocking access to the core
+     * attributes used by TOTP.
+     *
+     * @param user
+     *     The User object to wrap.
+     */
+    public TOTPUser(User user) {
+        super(user);
+        this.undecorated = user;
+    }
+
+    /**
+     * Returns the User object wrapped by this TOTPUser.
+     *
+     * @return
+     *     The wrapped User object.
+     */
+    public User getUndecorated() {
+        return undecorated;
+    }
+
+    @Override
+    public Map<String, String> getAttributes() {
+
+        // Create independent, mutable copy of attributes
+        Map<String, String> attributes =
+                new HashMap<String, String>(super.getAttributes());
+
+        // Do not expose any TOTP-related attributes outside this extension
+        attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME);
+        attributes.remove(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME);
+
+        // Expose only non-TOTP attributes
+        return attributes;
+
+    }
+
+    @Override
+    public void setAttributes(Map<String, String> attributes) {
+
+        // Create independent, mutable copy of attributes
+        attributes = new HashMap<String, String>(attributes);
+
+        // Do not expose any TOTP-related attributes outside this extension
+        attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME);
+        attributes.remove(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME);
+
+        // Set only non-TOTP attributes
+        super.setAttributes(attributes);
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java
new file mode 100644
index 0000000..980bbf7
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java
@@ -0,0 +1,64 @@
+/*
+ * 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 org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.auth.DecoratingDirectory;
+import org.apache.guacamole.net.auth.DelegatingUserContext;
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.User;
+import org.apache.guacamole.net.auth.UserContext;
+
+/**
+ * TOTP-specific UserContext implementation which wraps the UserContext of
+ * some other extension, providing (or hiding) additional data.
+ */
+public class TOTPUserContext extends DelegatingUserContext {
+
+    /**
+     * Creates a new TOTPUserContext which wraps the given UserContext,
+     * providing (or hiding) additional TOTP-specific data.
+     *
+     * @param userContext
+     *     The UserContext to wrap.
+     */
+    public TOTPUserContext(UserContext userContext) {
+        super(userContext);
+    }
+
+    @Override
+    public Directory<User> getUserDirectory() throws GuacamoleException {
+        return new DecoratingDirectory<User>(super.getUserDirectory()) {
+
+            @Override
+            protected User decorate(User object) {
+                return new TOTPUser(object);
+            }
+
+            @Override
+            protected User undecorate(User object) {
+                assert(object instanceof TOTPUser);
+                return ((TOTPUser) object).getUndecorated();
+            }
+
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserTOTPKey.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserTOTPKey.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserTOTPKey.java
new file mode 100644
index 0000000..d7bc903
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserTOTPKey.java
@@ -0,0 +1,148 @@
+/*
+ * 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 java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * The key used to generate TOTP codes for a particular user.
+ */
+public class UserTOTPKey {
+
+    /**
+     * Secure source of random bytes.
+     */
+    private static final Random RANDOM = new SecureRandom();
+
+    /**
+     * The username of the user associated with this key.
+     */
+    private final String username;
+
+    /**
+     * Whether the associated secret key has been confirmed by the user. A key
+     * is confirmed once the user has successfully entered a valid TOTP
+     * derived from that key.
+     */
+    private boolean confirmed;
+
+    /**
+     * The base32-encoded TOTP key associated with the user.
+     */
+    private byte[] secret;
+
+    /**
+     * Generates the given number of random bytes.
+     *
+     * @param length
+     *     The number of random bytes to generate.
+     *
+     * @return
+     *     A new array of exactly the given number of random bytes.
+     */
+    private static byte[] generateBytes(int length) {
+        byte[] bytes = new byte[length];
+        RANDOM.nextBytes(bytes);
+        return bytes;
+    }
+
+    /**
+     * Creates a new, unconfirmed, randomly-generated TOTP key having the given
+     * length.
+     *
+     * @param username
+     *     The username of the user associated with this key.
+     *
+     * @param length
+     *     The length of the key to generate, in bytes.
+     */
+    public UserTOTPKey(String username, int length) {
+        this(username, generateBytes(length), false);
+    }
+
+    /**
+     * Creates a new UserTOTPKey containing the given key and having the given
+     * confirmed state.
+     *
+     * @param username
+     *     The username of the user associated with this key.
+     *
+     * @param secret
+     *     The raw binary secret key to be used to generate TOTP codes.
+     *
+     * @param confirmed
+     *     true if the user associated with the key has confirmed that they can
+     *     successfully generate the corresponding TOTP codes (the user has
+     *     been "enrolled"), false otherwise.
+     */
+    public UserTOTPKey(String username, byte[] secret, boolean confirmed) {
+        this.username = username;
+        this.confirmed = confirmed;
+        this.secret = secret;
+    }
+
+    /**
+     * Returns the username of the user associated with this key.
+     *
+     * @return
+     *     The username of the user associated with this key.
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     * Returns the raw binary secret key to be used to generate TOTP codes.
+     *
+     * @return
+     *     The raw binary secret key to be used to generate TOTP codes.
+     */
+    public byte[] getSecret() {
+        return secret;
+    }
+
+    /**
+     * Returns whether the user associated with the key has confirmed that they
+     * can successfully generate the corresponding TOTP codes (the user has
+     * been "enrolled").
+     *
+     * @return
+     *     true if the user has confirmed that they can successfully generate
+     *     the TOTP codes generated by this key, false otherwise.
+     */
+    public boolean isConfirmed() {
+        return confirmed;
+    }
+
+    /**
+     * Sets whether the user associated with the key has confirmed that they
+     * can successfully generate the corresponding TOTP codes (the user has
+     * been "enrolled").
+     *
+     * @param confirmed
+     *     true if the user has confirmed that they can successfully generate
+     *     the TOTP codes generated by this key, false otherwise.
+     */
+    public void setConfirmed(boolean confirmed) {
+        this.confirmed = confirmed;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
new file mode 100644
index 0000000..8264efd
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
@@ -0,0 +1,281 @@
+/*
+ * 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.common.io.BaseEncoding;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.security.InvalidKeyException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.guacamole.GuacamoleClientException;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleUnsupportedException;
+import org.apache.guacamole.auth.totp.conf.ConfigurationService;
+import org.apache.guacamole.auth.totp.form.AuthenticationCodeField;
+import org.apache.guacamole.form.Field;
+import org.apache.guacamole.net.auth.AuthenticatedUser;
+import org.apache.guacamole.net.auth.Credentials;
+import org.apache.guacamole.net.auth.User;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
+import org.apache.guacamole.totp.TOTPGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Service for verifying the identity of a user using TOTP.
+ */
+public class UserVerificationService {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(UserVerificationService.class);
+
+    /**
+     * BaseEncoding instance which decoded/encodes base32.
+     */
+    private static final BaseEncoding BASE32 = BaseEncoding.base32();
+
+    /**
+     * Service for retrieving configuration information.
+     */
+    @Inject
+    private ConfigurationService confService;
+
+    /**
+     * Provider for AuthenticationCodeField instances.
+     */
+    @Inject
+    private Provider<AuthenticationCodeField> codeFieldProvider;
+
+    /**
+     * Retrieves and decodes the base32-encoded TOTP key associated with user
+     * having the given UserContext. If no TOTP key is associated with the user,
+     * a random key is generated and associated with the user. If the extension
+     * storing the user does not support storage of the TOTP key, null is
+     * returned.
+     *
+     * @param context
+     *     The UserContext of the user whose TOTP key should be retrieved.
+     *
+     * @param username
+     *     The username of the user associated with the given UserContext.
+     *
+     * @return
+     *     The TOTP key associated with the user having the given UserContext,
+     *     or null if the extension storing the user does not support storage
+     *     of the TOTP key.
+     *
+     * @throws GuacamoleException
+     *     If a new key is generated, but the extension storing the associated
+     *     user fails while updating the user account.
+     */
+    private UserTOTPKey getKey(UserContext context,
+            String username) throws GuacamoleException {
+
+        // Retrieve attributes from current user
+        User self = context.self();
+        Map<String, String> attributes = context.self().getAttributes();
+
+        // If no key is defined, attempt to generate a new key
+        String secret = attributes.get(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME);
+        if (secret == null) {
+
+            // Generate random key for user
+            TOTPGenerator.Mode mode = confService.getMode();
+            UserTOTPKey generated = new UserTOTPKey(username,mode.getRecommendedKeyLength());
+            if (setKey(context, generated))
+                return generated;
+
+            // Fail if key cannot be set
+            return null;
+
+        }
+
+        // Parse retrieved base32 key value
+        byte[] key;
+        try {
+            key = BASE32.decode(secret);
+        }
+
+        // If key is not valid base32, warn but otherwise pretend the key does
+        // not exist
+        catch (IllegalArgumentException e) {
+            logger.warn("TOTP key of user \"{}\" is not valid base32.", self.getIdentifier());
+            logger.debug("TOTP key is not valid base32.", e);
+            return null;
+        }
+
+        // Otherwise, parse value from attributes
+        boolean confirmed = "true".equals(attributes.get(TOTPUser.TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME));
+        return new UserTOTPKey(username, key, confirmed);
+
+    }
+
+    /**
+     * Attempts to store the given TOTP key within the user account of the user
+     * having the given UserContext. As not all extensions will support storage
+     * of arbitrary attributes, this operation may fail.
+     *
+     * @param context
+     *     The UserContext associated with the user whose TOTP key is to be
+     *     stored.
+     *
+     * @param key
+     *     The TOTP key to store.
+     *
+     * @return
+     *     true if the TOTP key was successfully stored, false if the extension
+     *     handling storage does not support storage of the key.
+     *
+     * @throws GuacamoleException
+     *     If the extension handling storage fails internally while attempting
+     *     to update the user.
+     */
+    private boolean setKey(UserContext context, UserTOTPKey key)
+            throws GuacamoleException {
+
+        // Get mutable set of attributes
+        User self = context.self();
+        Map<String, String> attributes = new HashMap<String, String>();
+
+        // Set/overwrite current TOTP key state
+        attributes.put(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME, BASE32.encode(key.getSecret()));
+        attributes.put(TOTPUser.TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, key.isConfirmed() ? "true" : "false");
+        self.setAttributes(attributes);
+
+        // Confirm that attributes have actually been set
+        Map<String, String> setAttributes = self.getAttributes();
+        if (!setAttributes.containsKey(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME)
+                || !setAttributes.containsKey(TOTPUser.TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME))
+            return false;
+
+        // Update user object
+        try {
+            context.getUserDirectory().update(self);
+        }
+        catch (GuacamoleUnsupportedException e) {
+            logger.debug("Extension storage for user is explicitly read-only. "
+                    + "Cannot update attributes to store TOTP key.", e);
+            return false;
+        }
+
+        // TOTP key successfully stored/updated
+        return true;
+
+    }
+
+    /**
+     * Verifies the identity of the given user using TOTP. If a authentication
+     * code from the user's TOTP device has not already been provided, a code is
+     * requested in the form of additional expected credentials. Any provided
+     * code is cryptographically verified. If no code is present, or the
+     * received code is invalid, an exception is thrown.
+     *
+     * @param context
+     *     The UserContext provided for the user by another authentication
+     *     extension.
+     *
+     * @param authenticatedUser
+     *     The user whose identity should be verified using TOTP.
+     *
+     * @throws GuacamoleException
+     *     If required TOTP-specific configuration options are missing or
+     *     malformed, or if the user's identity cannot be verified.
+     */
+    public void verifyIdentity(UserContext context,
+            AuthenticatedUser authenticatedUser) throws GuacamoleException {
+
+        // Ignore anonymous users
+        String username = authenticatedUser.getIdentifier();
+        if (username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
+            return;
+
+        // Ignore users which do not have an associated key
+        UserTOTPKey key = getKey(context, username);
+        if (key == null)
+            return;
+
+        // Pull the original HTTP request used to authenticate
+        Credentials credentials = authenticatedUser.getCredentials();
+        HttpServletRequest request = credentials.getRequest();
+
+        // Retrieve TOTP from request
+        String code = request.getParameter(AuthenticationCodeField.PARAMETER_NAME);
+
+        // If no TOTP provided, request one
+        if (code == null) {
+
+            AuthenticationCodeField field = codeFieldProvider.get();
+
+            // If the user hasn't completed enrollment, request that they do
+            if (!key.isConfirmed()) {
+                field.exposeKey(key);
+                throw new GuacamoleInsufficientCredentialsException(
+                        "TOTP.INFO_ENROLL_REQUIRED", new CredentialsInfo(
+                            Collections.<Field>singletonList(field)
+                        ));
+            }
+
+            // Otherwise simply request the user's authentication code
+            throw new GuacamoleInsufficientCredentialsException(
+                    "TOTP.INFO_CODE_REQUIRED", new CredentialsInfo(
+                        Collections.<Field>singletonList(field)
+                    ));
+
+        }
+
+        try {
+
+            // Get generator based on user's key and provided configuration
+            TOTPGenerator totp = new TOTPGenerator(key.getSecret(),
+                    confService.getMode(), confService.getDigits());
+
+            // Verify provided TOTP against value produced by generator
+            if (code.equals(totp.generate()) || code.equals(totp.previous())) {
+
+                // Record key as confirmed, if it hasn't already been so recorded
+                if (!key.isConfirmed()) {
+                    key.setConfirmed(true);
+                    setKey(context, key);
+                }
+
+                // User has been verified
+                return;
+
+            }
+
+        }
+        catch (InvalidKeyException e) {
+            logger.warn("User \"{}\" is associated with an invalid TOTP key.", username);
+            logger.debug("TOTP key is not valid.", e);
+        }
+
+        // Provided code is not valid
+        throw new GuacamoleClientException("TOTP.INFO_VERIFICATION_FAILED");
+
+    }
+
+}


[20/21] guacamole-client git commit: GUACAMOLE-96: Remove unused field controller.

Posted by vn...@apache.org.
GUACAMOLE-96: Remove unused field controller.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/456b8a03
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/456b8a03
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/456b8a03

Branch: refs/heads/master
Commit: 456b8a0394c7cc5dc6ed8dd02f9a83383b05d63b
Parents: 96e3d02
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 16:20:13 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:18 2018 -0800

----------------------------------------------------------------------
 .../src/main/resources/config/totpConfig.js     |  1 -
 .../authenticationCodeFieldController.js        | 29 --------------------
 2 files changed, 30 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/456b8a03/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js b/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js
index 54bb56c..81cc07d 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js
+++ b/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js
@@ -26,7 +26,6 @@ angular.module('guacTOTP').config(['formServiceProvider',
     // Define field for the TOTP code provided by the user
     formServiceProvider.registerFieldType('GUAC_TOTP_CODE', {
         module      : 'guacTOTP',
-        controller  : 'authenticationCodeFieldController',
         templateUrl : 'app/ext/totp/templates/authenticationCodeField.html'
     });
 

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/456b8a03/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
deleted file mode 100644
index c9cecc6..0000000
--- a/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * Controller for the "GUAC_TOTP_CODE" field which prompts the user to enter
- * the code generated by their authentication device.
- */
-angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$scope', '$element',
-    function authenticationCodeFieldController($scope, $element) {
-
-    // STUB
-
-}]);


[03/21] guacamole-client git commit: GUACAMOLE-96: Add license for Guava.

Posted by vn...@apache.org.
GUACAMOLE-96: Add license for Guava.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/0844e9d4
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/0844e9d4
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/0844e9d4

Branch: refs/heads/master
Commit: 0844e9d42297a7f87e6bf2a8fb2f75a198aa0d3f
Parents: 8e3cbf0
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 11:00:15 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:17 2018 -0800

----------------------------------------------------------------------
 .../guacamole-auth-totp/src/licenses/LICENSE    |   9 +
 .../src/licenses/bundled/guava-18.0/COPYING     | 202 +++++++++++++++++++
 2 files changed, 211 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/0844e9d4/extensions/guacamole-auth-totp/src/licenses/LICENSE
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/licenses/LICENSE b/extensions/guacamole-auth-totp/src/licenses/LICENSE
index e1fa0fc..37d934c 100644
--- a/extensions/guacamole-auth-totp/src/licenses/LICENSE
+++ b/extensions/guacamole-auth-totp/src/licenses/LICENSE
@@ -229,6 +229,15 @@ Google Guice (https://github.com/google/guice)
         Apache v2.0 (bundled/guice-3.0/COPYING)
 
 
+Guava: Google Core Libraries for Java (https://github.com/google/guava)
+-----------------------------------------------------------------------
+
+    Version: 18.0
+    From: 'Google Inc.' (http://www.google.com/)
+    License(s):
+        Apache v2.0 (bundled/guava-18.0/COPYING)
+
+
 JSR-330 / Dependency Injection for Java (http://code.google.com/p/atinject/)
 ----------------------------------------------------------------------------
 

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/0844e9d4/extensions/guacamole-auth-totp/src/licenses/bundled/guava-18.0/COPYING
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/guava-18.0/COPYING b/extensions/guacamole-auth-totp/src/licenses/bundled/guava-18.0/COPYING
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/licenses/bundled/guava-18.0/COPYING
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.


[12/21] guacamole-client git commit: GUACAMOLE-96: Clean up enrollment interface. Provide help text for user.

Posted by vn...@apache.org.
GUACAMOLE-96: Clean up enrollment interface. Provide help text for user.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/2a894c48
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/2a894c48
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/2a894c48

Branch: refs/heads/master
Commit: 2a894c487cf25e1ffa35548de1dc791aefed6471
Parents: a422fdf
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 15:51:06 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:18 2018 -0800

----------------------------------------------------------------------
 .../auth/totp/UserVerificationService.java      |  6 +++---
 .../auth/totp/form/AuthenticationCodeField.java | 22 ++++++++++++++++++++
 .../src/main/resources/styles/totp.css          | 14 ++++++++++++-
 .../templates/authenticationCodeField.html      | 13 ++++++++----
 .../src/main/resources/translations/en.json     | 17 ++++++++++++---
 5 files changed, 61 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/2a894c48/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
index 987d4ca..851bb94 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
@@ -245,14 +245,14 @@ public class UserVerificationService {
             if (!key.isConfirmed()) {
                 field.exposeKey(key);
                 throw new GuacamoleInsufficientCredentialsException(
-                        "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo(
+                        "TOTP.INFO_ENROLL_REQUIRED", new CredentialsInfo(
                             Collections.<Field>singletonList(field)
                         ));
             }
 
             // Otherwise simply request the user's authentication code
             throw new GuacamoleInsufficientCredentialsException(
-                    "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo(
+                    "TOTP.INFO_CODE_REQUIRED", new CredentialsInfo(
                         Collections.<Field>singletonList(field)
                     ));
 
@@ -285,7 +285,7 @@ public class UserVerificationService {
         }
 
         // Provided code is not valid
-        throw new GuacamoleClientException("LOGIN.INFO_TOTP_VERIFICATION_FAILED");
+        throw new GuacamoleClientException("TOTP.INFO_VERIFICATION_FAILED");
 
     }
 

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/2a894c48/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
index e0333dd..c3ca207 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
@@ -99,6 +99,28 @@ public class AuthenticationCodeField extends Field {
     }
 
     /**
+     * Returns the number of digits used for each TOTP code. If the user's key
+     * is not being exposed to facilitate enrollment, this value will not be
+     * exposed either.
+     *
+     * @return
+     *     The number of digits used for each TOTP code, or null if the user's
+     *     key is not being exposed to facilitate enrollment.
+     *
+     * @throws GuacamoleException
+     *     If the number of digits cannot be read from guacamole.properties.
+     */
+    public Integer getDigits() throws GuacamoleException {
+
+        // Do not reveal code size unless enrollment is in progress
+        if (key == null)
+            return null;
+
+        return confService.getDigits();
+        
+    }
+
+    /**
      * Returns the "otpauth" URI for the secret key used to generate TOTP codes
      * for the current user. If the secret key is not being exposed to
      * facilitate enrollment, null is returned.

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/2a894c48/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
index 8181e2c..6db7729 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
+++ b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css
@@ -17,4 +17,16 @@
  * under the License.
  */
 
-/* STUB */
+.totp-enroll p {
+    font-size: 0.8em;
+}
+
+.totp-qr-code {
+    text-align: center;
+}
+
+.totp-qr-code img {
+    margin: 1em;
+    border: 1px solid rgba(0,0,0,0.25);
+    box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/2a894c48/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
index ae155d8..5a39be7 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
+++ b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html
@@ -1,13 +1,18 @@
 <div class="totp-code-field">
 
-    <!-- QR Code (if available) -->
-    <div class="totp-qr-code" ng-show="field.qrCode">
-        <img ng-src="{{field.qrCode}}">
+    <!-- Enroll user if necessary -->
+    <div class="totp-enroll" ng-show="field.qrCode">
+        <p translate="TOTP.HELP_ENROLL_BARCODE"></p>
+        <div class="totp-qr-code"><img ng-src="{{field.qrCode}}"></div>
+        <p translate="TOTP.HELP_ENROLL_VERIFY"
+           translate-values="{ DIGITS : field.digits }"></p>
     </div>
 
     <!-- Field for entry of the current TOTP code -->
     <div class="totp-code">
-        <input type="text" ng-model="model" autocorrect="off" autocapitalize="off"/>
+        <input type="text"
+               placeholder="{{'TOTP.FIELD_PLACEHOLDER_CODE' |translate}}"
+               ng-model="model" autocorrect="off" autocapitalize="off"/>
     </div>
 
 </div>

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/2a894c48/extensions/guacamole-auth-totp/src/main/resources/translations/en.json
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/resources/translations/en.json b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json
index 540b94e..bd0e9e2 100644
--- a/extensions/guacamole-auth-totp/src/main/resources/translations/en.json
+++ b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json
@@ -5,9 +5,20 @@
     },
 
     "LOGIN" : {
-        "FIELD_HEADER_GUAC_TOTP"        : "Authentication Code",
-        "INFO_TOTP_REQUIRED"            : "Please enter your authentication code to verify your identity.",
-        "INFO_TOTP_VERIFICATION_FAILED" : "Verification failed. Please try again."
+        "FIELD_HEADER_GUAC_TOTP" : ""
+    },
+
+    "TOTP" : {
+
+        "FIELD_PLACEHOLDER_CODE" : "Authentication Code",
+
+        "INFO_CODE_REQUIRED"       : "Please enter your authentication code to verify your identity.",
+        "INFO_ENROLL_REQUIRED"     : "Multi-factor authentication has been enabled on your account.",
+        "INFO_VERIFICATION_FAILED" : "Verification failed. Please try again.",
+
+        "HELP_ENROLL_BARCODE" : "To complete the enrollment process, scan the barcode below with the two-factor authentication app on your phone or device.",
+        "HELP_ENROLL_VERIFY"  : "After scanning the barcode, enter the {DIGITS}-digit authentication code displayed to verify that enrollment was successful."
+
     }
 
 }


[13/21] guacamole-client git commit: GUACAMOLE-96: Include all TOTP key details in field when enrolling.

Posted by vn...@apache.org.
GUACAMOLE-96: Include all TOTP key details in field when enrolling.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/5b2b6337
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/5b2b6337
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/5b2b6337

Branch: refs/heads/master
Commit: 5b2b633707b997212de553130e1e9f7b6627c30e
Parents: b1c23f2
Author: Michael Jumper <mj...@apache.org>
Authored: Fri Nov 24 12:34:09 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:18 2018 -0800

----------------------------------------------------------------------
 .../auth/totp/form/AuthenticationCodeField.java | 109 +++++++++++++++++++
 1 file changed, 109 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/5b2b6337/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
index 764fe95..1a61e89 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java
@@ -35,6 +35,7 @@ import org.apache.guacamole.GuacamoleException;
 import org.apache.guacamole.auth.totp.user.UserTOTPKey;
 import org.apache.guacamole.auth.totp.conf.ConfigurationService;
 import org.apache.guacamole.form.Field;
+import org.apache.guacamole.totp.TOTPGenerator;
 import org.codehaus.jackson.annotate.JsonProperty;
 
 /**
@@ -99,6 +100,46 @@ public class AuthenticationCodeField extends Field {
     }
 
     /**
+     * Returns the username of the user associated with the key being used to
+     * generate TOTP codes. If the user's key is not being exposed to facilitate
+     * enrollment, this value will not be exposed either.
+     *
+     * @return
+     *     The username of the user associated with the key being used to
+     *     generate TOTP codes, or null if the user's key is not being exposed
+     *     to facilitate enrollment.
+     */
+    public String getUsername() {
+
+        // Do not reveal TOTP mode unless enrollment is in progress
+        if (key == null)
+            return null;
+
+        return key.getUsername();
+
+    }
+
+    /**
+     * Returns the base32-encoded secret key that is being used to generate TOTP
+     * codes for the authenticating user. If the user's key is not being exposed
+     * to facilitate enrollment, this value will not be exposed either.
+     *
+     * @return
+     *     The base32-encoded secret key that is being used to generate TOTP
+     *     codes for the authenticating user, or null if the user's key is not
+     *     being exposed to facilitate enrollment.
+     */
+    public String getSecret() {
+
+        // Do not reveal TOTP mode unless enrollment is in progress
+        if (key == null)
+            return null;
+
+        return BASE32.encode(key.getSecret());
+
+    }
+
+    /**
      * Returns the number of digits used for each TOTP code. If the user's key
      * is not being exposed to facilitate enrollment, this value will not be
      * exposed either.
@@ -121,6 +162,74 @@ public class AuthenticationCodeField extends Field {
     }
 
     /**
+     * Returns the human-readable name of the entity issuing user accounts. If
+     * the user's key is not being exposed to facilitate enrollment, this value
+     * will not be exposed either.
+     *
+     * @return
+     *     The human-readable name of the entity issuing user accounts, or null
+     *     if the user's key is not being exposed to facilitate enrollment.
+     *
+     * @throws GuacamoleException
+     *     If the issuer cannot be read from guacamole.properties.
+     */
+    public String getIssuer() throws GuacamoleException {
+
+        // Do not reveal code issuer unless enrollment is in progress
+        if (key == null)
+            return null;
+
+        return confService.getIssuer();
+
+    }
+
+    /**
+     * Returns the mode that TOTP code generation is operating in. This value
+     * will be one of "SHA1", "SHA256", or "SHA512". If the user's key is not
+     * being exposed to facilitate enrollment, this value will not be exposed
+     * either.
+     *
+     * @return
+     *     The mode that TOTP code generation is operating in, such as "SHA1",
+     *     "SHA256", or "SHA512", or null if the user's key is not being
+     *     exposed to facilitate enrollment.
+     *
+     * @throws GuacamoleException
+     *     If the TOTP mode cannot be read from guacamole.properties.
+     */
+    public TOTPGenerator.Mode getMode() throws GuacamoleException {
+
+        // Do not reveal TOTP mode unless enrollment is in progress
+        if (key == null)
+            return null;
+
+        return confService.getMode();
+
+    }
+
+    /**
+     * Returns the number of seconds that each TOTP code remains valid. If the
+     * user's key is not being exposed to facilitate enrollment, this value will
+     * not be exposed either.
+     *
+     * @return
+     *     The number of seconds that each TOTP code remains valid, or null if
+     *     the user's key is not being exposed to facilitate enrollment.
+     *
+     * @throws GuacamoleException
+     *     If the period cannot be read from guacamole.properties.
+     */
+    public Integer getPeriod() throws GuacamoleException {
+
+        // Do not reveal code period unless enrollment is in progress
+        if (key == null)
+            return null;
+
+        return confService.getPeriod();
+
+    }
+
+    /**
      * Returns the "otpauth" URI for the secret key used to generate TOTP codes
      * for the current user. If the secret key is not being exposed to
      * facilitate enrollment, null is returned.


[21/21] guacamole-client git commit: GUACAMOLE-96: Merge add support for TOTP as an additional authentication factor.

Posted by vn...@apache.org.
GUACAMOLE-96: Merge add support for TOTP as an additional authentication factor.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/9ee43e65
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/9ee43e65
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/9ee43e65

Branch: refs/heads/master
Commit: 9ee43e656cd3c4ad1c43632604ce424332404ff5
Parents: e680131 608a111
Author: Nick Couchman <vn...@apache.org>
Authored: Mon Feb 5 13:02:10 2018 -0500
Committer: Nick Couchman <vn...@apache.org>
Committed: Mon Feb 5 13:02:10 2018 -0500

----------------------------------------------------------------------
 LICENSE                                         |  37 ++
 extensions/guacamole-auth-totp/.gitignore       |   3 +
 extensions/guacamole-auth-totp/pom.xml          | 274 +++++++++++
 .../guacamole-auth-totp/src/licenses/LICENSE    | 294 ++++++++++++
 .../guacamole-auth-totp/src/licenses/NOTICE     |   5 +
 .../src/licenses/bundled/README                 |   4 +
 .../licenses/bundled/aopalliance-1.0/LICENSE    |   4 +
 .../src/licenses/bundled/guava-18.0/COPYING     | 202 ++++++++
 .../src/licenses/bundled/guice-3.0/COPYING      | 202 ++++++++
 .../bundled/javax.inject-1/LICENSE-2.0.txt      | 202 ++++++++
 .../bundled/totp-reference-impl-07/license.txt  |  28 ++
 .../src/licenses/bundled/zxing-3.3.1/LICENSE    | 245 ++++++++++
 .../src/main/assembly/dist.xml                  |  53 +++
 .../auth/totp/TOTPAuthenticationProvider.java   | 126 +++++
 .../totp/TOTPAuthenticationProviderModule.java  |  83 ++++
 .../auth/totp/conf/ConfigurationService.java    | 161 +++++++
 .../auth/totp/conf/TOTPModeProperty.java        |  62 +++
 .../auth/totp/form/AuthenticationCodeField.java | 316 +++++++++++++
 .../totp/user/CodeUsageTrackingService.java     | 264 +++++++++++
 .../guacamole/auth/totp/user/TOTPUser.java      | 102 +++++
 .../auth/totp/user/TOTPUserContext.java         |  64 +++
 .../guacamole/auth/totp/user/UserTOTPKey.java   | 148 ++++++
 .../auth/totp/user/UserVerificationService.java | 288 ++++++++++++
 .../apache/guacamole/totp/TOTPGenerator.java    | 456 +++++++++++++++++++
 .../src/main/resources/config/totpConfig.js     |  33 ++
 .../authenticationCodeFieldController.js        |  68 +++
 .../src/main/resources/guac-manifest.json       |  28 ++
 .../src/main/resources/license.txt              |  18 +
 .../src/main/resources/styles/totp.css          |  88 ++++
 .../templates/authenticationCodeField.html      |  47 ++
 .../src/main/resources/totpModule.js            |  28 ++
 .../src/main/resources/translations/en.json     |  34 ++
 .../guacamole/totp/TOTPGeneratorTest.java       | 168 +++++++
 .../src/main/webapp/app/login/styles/login.css  |   2 +-
 pom.xml                                         |   1 +
 35 files changed, 4137 insertions(+), 1 deletion(-)
----------------------------------------------------------------------



[05/21] guacamole-client git commit: GUACAMOLE-96: Verify TOTP of all users against hard-coded key.

Posted by vn...@apache.org.
GUACAMOLE-96: Verify TOTP of all users against hard-coded key.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/19e03a16
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/19e03a16
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/19e03a16

Branch: refs/heads/master
Commit: 19e03a1632eee39508378a434f3362b9e9f9a3f8
Parents: b55e561
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 00:57:37 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:17 2018 -0800

----------------------------------------------------------------------
 .../auth/totp/UserVerificationService.java      | 75 +++++++++++++++++---
 1 file changed, 66 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/19e03a16/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
index f28149a..7cffffe 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
@@ -19,6 +19,8 @@
 
 package org.apache.guacamole.auth.totp;
 
+import com.google.common.io.BaseEncoding;
+import java.security.InvalidKeyException;
 import java.util.Collections;
 import javax.servlet.http.HttpServletRequest;
 import org.apache.guacamole.GuacamoleClientException;
@@ -30,6 +32,9 @@ import org.apache.guacamole.net.auth.Credentials;
 import org.apache.guacamole.net.auth.UserContext;
 import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
 import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
+import org.apache.guacamole.totp.TOTPGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Service for verifying the identity of a user using TOTP.
@@ -37,6 +42,11 @@ import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredential
 public class UserVerificationService {
 
     /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(UserVerificationService.class);
+
+    /**
      * The name of the HTTP parameter which will contain the TOTP code provided
      * by the user to verify their identity.
      */
@@ -57,6 +67,30 @@ public class UserVerificationService {
     );
 
     /**
+     * BaseEncoding instance which decoded/encodes base32.
+     */
+    private static final BaseEncoding BASE32 = BaseEncoding.base32();
+
+    /**
+     * Retrieves the base32-encoded TOTP key associated with user having the
+     * given UserContext. If no TOTP key is associated with the user, null is
+     * returned.
+     *
+     * @param context
+     *     The UserContext of the user whose TOTP key should be retrieved.
+     *
+     * @return
+     *     The base32-encoded TOTP key associated with user having the given
+     *     UserContext, or null if no TOTP key is associated with the user.
+     */
+    public String getKey(UserContext context){
+
+        // FIXME: Hard-coded key
+        return "JBSWY3DPEHPK3PXP";
+
+    }
+
+    /**
      * Verifies the identity of the given user using TOTP. If a authentication
      * code from the user's TOTP device has not already been provided, a code is
      * requested in the form of additional expected credentials. Any provided
@@ -77,25 +111,48 @@ public class UserVerificationService {
     public void verifyIdentity(UserContext context,
             AuthenticatedUser authenticatedUser) throws GuacamoleException {
 
+        // Ignore anonymous users
+        String username = authenticatedUser.getIdentifier();
+        if (username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
+            return;
+
+        // Ignore users which do not have an associated key
+        String encodedKey = getKey(context);
+        if (encodedKey == null)
+            return;
+
         // Pull the original HTTP request used to authenticate
         Credentials credentials = authenticatedUser.getCredentials();
         HttpServletRequest request = credentials.getRequest();
 
-        // Ignore anonymous users
-        if (authenticatedUser.getIdentifier().equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
-            return;
-
         // Retrieve TOTP from request
-        String totp = request.getParameter(TOTP_PARAMETER_NAME);
+        String code = request.getParameter(TOTP_PARAMETER_NAME);
 
         // If no TOTP provided, request one
-        if (totp == null)
+        if (code == null)
             throw new GuacamoleInsufficientCredentialsException(
                     "LOGIN.INFO_TOTP_REQUIRED", TOTP_CREDENTIALS);
 
-        // FIXME: Hard-coded code
-        if (!totp.equals("123456"))
-            throw new GuacamoleClientException("LOGIN.INFO_TOTP_VERIFICATION_FAILED");
+        try {
+
+            // Verify provided TOTP against value produced by generator
+            byte[] key = BASE32.decode(encodedKey);
+            TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA1, 6);
+            if (code.equals(totp.generate()))
+                return;
+
+        }
+        catch (InvalidKeyException e) {
+            logger.warn("User \"{}\" is associated with an invalid TOTP key.", username);
+            logger.debug("TOTP key is not valid.", e);
+        }
+        catch (IllegalArgumentException e) {
+            logger.warn("TOTP key of user \"{}\" is not valid base32.", username);
+            logger.debug("TOTP key is not valid base32.", e);
+        }
+
+        // Provided code is not valid
+        throw new GuacamoleClientException("LOGIN.INFO_TOTP_VERIFICATION_FAILED");
 
     }
 


[16/21] guacamole-client git commit: GUACAMOLE-96: Ensure valid codes cannot be reused.

Posted by vn...@apache.org.
GUACAMOLE-96: Ensure valid codes cannot be reused.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/b1c23f20
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/b1c23f20
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/b1c23f20

Branch: refs/heads/master
Commit: b1c23f20d00b030cb8a8691f8aad1d53a341f8ff
Parents: 456b8a0
Author: Michael Jumper <mj...@apache.org>
Authored: Wed Nov 22 18:53:29 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:18 2018 -0800

----------------------------------------------------------------------
 .../auth/totp/TOTPAuthenticationProvider.java   |   3 +-
 .../totp/TOTPAuthenticationProviderModule.java  |   2 +
 .../totp/user/CodeUsageTrackingService.java     | 264 +++++++++++++++++++
 .../auth/totp/user/UserVerificationService.java |   9 +-
 4 files changed, 276 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b1c23f20/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
index 28e2380..4f18304 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
@@ -23,6 +23,7 @@ import org.apache.guacamole.auth.totp.user.UserVerificationService;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.auth.totp.user.CodeUsageTrackingService;
 import org.apache.guacamole.auth.totp.user.TOTPUserContext;
 import org.apache.guacamole.net.auth.AuthenticatedUser;
 import org.apache.guacamole.net.auth.AuthenticationProvider;
@@ -119,7 +120,7 @@ public class TOTPAuthenticationProvider implements AuthenticationProvider {
 
     @Override
     public void shutdown() {
-        // Do nothing
+        injector.getInstance(CodeUsageTrackingService.class).shutdown();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b1c23f20/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
index 94b7232..d1f7f96 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java
@@ -23,6 +23,7 @@ import org.apache.guacamole.auth.totp.user.UserVerificationService;
 import com.google.inject.AbstractModule;
 import org.apache.guacamole.GuacamoleException;
 import org.apache.guacamole.auth.totp.conf.ConfigurationService;
+import org.apache.guacamole.auth.totp.user.CodeUsageTrackingService;
 import org.apache.guacamole.environment.Environment;
 import org.apache.guacamole.environment.LocalEnvironment;
 import org.apache.guacamole.net.auth.AuthenticationProvider;
@@ -73,6 +74,7 @@ public class TOTPAuthenticationProviderModule extends AbstractModule {
         bind(Environment.class).toInstance(environment);
 
         // Bind TOTP-specific services
+        bind(CodeUsageTrackingService.class);
         bind(ConfigurationService.class);
         bind(UserVerificationService.class);
 

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b1c23f20/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/CodeUsageTrackingService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/CodeUsageTrackingService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/CodeUsageTrackingService.java
new file mode 100644
index 0000000..c9a94b4
--- /dev/null
+++ b/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();
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+
+            if (this == obj)
+                return true;
+
+            if (obj == null)
+                return false;
+
+            if (getClass() != obj.getClass())
+                return false;
+
+            final UsedCode other = (UsedCode) obj;
+            return username.equals(other.username) && code.equals(other.code);
+
+        }
+
+    }
+
+    /**
+     * Attempts to mark the given code as used. The code MUST have already been
+     * validated against the user's secret key, as this function only verifies
+     * whether the code has been previously used, not whether it is actually
+     * valid. If the code has not previously been used, the code is stored as
+     * having been used by the given user at the current time.
+     *
+     * @param username
+     *     The username of the user who has attempted to use the given valid
+     *     code.
+     *
+     * @param code
+     *     The otherwise-valid code given by the user.
+     *
+     * @return
+     *     true if the code has not previously been used by the given user and
+     *     has now been marked as previously used, false otherwise.
+     *
+     * @throws GuacamoleException
+     *     If configuration information necessary to determine the length of
+     *     time a code should be marked as invalid cannot be read from
+     *     guacamole.properties.
+     */
+    public boolean useCode(String username, String code)
+            throws GuacamoleException {
+
+        // Repeatedly attempt to use the given code until an explicit success
+        // or failure has occurred
+        UsedCode usedCode = new UsedCode(username, code);
+        for (;;) {
+
+            // Explicitly invalidate each used code for two periods after its
+            // first successful use
+            long current = System.currentTimeMillis();
+            long invalidUntil = current + confService.getPeriod() * 1000 * INVALID_INTERVAL;
+
+            // Try to use the given code, marking it as used within the map of
+            // now-invalidated codes
+            Long expires = invalidCodes.putIfAbsent(usedCode, invalidUntil);
+            if (expires == null)
+                return true;
+
+            // If the code was already used, fail to use the code if
+            // insufficient time has elapsed since it was last used
+            // successfully
+            if (expires > current)
+                return false;
+
+
+            // Otherwise, the code is actually valid - remove the invalidated
+            // code only if it still has the expected expiration time, and
+            // retry using the code
+            invalidCodes.remove(usedCode, expires);
+
+        }
+
+    }
+
+    /**
+     * Cleans up resources which may be in use by this service in the
+     * background, such as other threads. This function MUST be invoked during
+     * webapp shutdown to avoid leaking these resources.
+     */
+    public void shutdown() {
+        executor.shutdownNow();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b1c23f20/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
index 8264efd..30108e1 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
@@ -65,6 +65,12 @@ public class UserVerificationService {
     private ConfigurationService confService;
 
     /**
+     * Service for tracking whether TOTP codes have been used.
+     */
+    @Inject
+    private CodeUsageTrackingService codeService;
+
+    /**
      * Provider for AuthenticationCodeField instances.
      */
     @Inject
@@ -254,7 +260,8 @@ public class UserVerificationService {
                     confService.getMode(), confService.getDigits());
 
             // Verify provided TOTP against value produced by generator
-            if (code.equals(totp.generate()) || code.equals(totp.previous())) {
+            if ((code.equals(totp.generate()) || code.equals(totp.previous()))
+                    && codeService.useCode(username, code)) {
 
                 // Record key as confirmed, if it hasn't already been so recorded
                 if (!key.isConfirmed()) {