mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-31 09:03:21 +00:00 
			
		
		
		
	GUACAMOLE-96: Add configuration parameters for details of TOTP generation.
This commit is contained in:
		| @@ -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); | ||||
|  | ||||
|     } | ||||
|   | ||||
| @@ -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; | ||||
| @@ -66,6 +69,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, | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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\"."); | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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; | ||||
|  | ||||
| @@ -66,31 +68,34 @@ 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; | ||||
|  | ||||
|     /** | ||||
|      * 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. | ||||
|      */ | ||||
|     public AuthenticationCodeField(UserTOTPKey key) { | ||||
|         super(PARAMETER_NAME, FIELD_TYPE_NAME); | ||||
|         this.key = key; | ||||
|     } | ||||
|     private UserTOTPKey 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); | ||||
|         super(PARAMETER_NAME, FIELD_TYPE_NAME); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Exposes the given key to facilitate enrollment. | ||||
|      * | ||||
|      * @param key | ||||
|      *     The TOTP key to expose to the user for the sake of enrollment. | ||||
|      */ | ||||
|     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(); | ||||
|  | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user