mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-1219: Merge support for disabling TOTP on a user or user group basis.
This commit is contained in:
@@ -33,6 +33,12 @@ import org.apache.guacamole.net.auth.User;
|
|||||||
*/
|
*/
|
||||||
public class TOTPUser extends DelegatingUser {
|
public class TOTPUser extends DelegatingUser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the user attribute which disables the TOTP requirement
|
||||||
|
* for that specific user.
|
||||||
|
*/
|
||||||
|
public static final String TOTP_KEY_DISABLED_ATTRIBUTE_NAME = "guac-totp-disabled";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the user attribute which stores the TOTP key.
|
* The name of the user attribute which stores the TOTP key.
|
||||||
*/
|
*/
|
||||||
@@ -56,13 +62,14 @@ public class TOTPUser extends DelegatingUser {
|
|||||||
* The string value used by TOTP user attributes to represent the boolean
|
* The string value used by TOTP user attributes to represent the boolean
|
||||||
* value "true".
|
* value "true".
|
||||||
*/
|
*/
|
||||||
private static final String TRUTH_VALUE = "true";
|
public static final String TRUTH_VALUE = "true";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The form which contains all configurable properties for this user.
|
* The form which contains all configurable properties for this user.
|
||||||
*/
|
*/
|
||||||
public static final Form TOTP_ENROLLMENT_STATUS = new Form("totp-enrollment-status",
|
public static final Form TOTP_ENROLLMENT_STATUS = new Form("totp-enrollment-status",
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
|
new BooleanField(TOTP_KEY_DISABLED_ATTRIBUTE_NAME, TRUTH_VALUE),
|
||||||
new BooleanField(TOTP_KEY_SECRET_GENERATED_ATTRIBUTE_NAME, TRUTH_VALUE),
|
new BooleanField(TOTP_KEY_SECRET_GENERATED_ATTRIBUTE_NAME, TRUTH_VALUE),
|
||||||
new BooleanField(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, TRUTH_VALUE)
|
new BooleanField(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, TRUTH_VALUE)
|
||||||
)
|
)
|
||||||
@@ -96,6 +103,9 @@ public class TOTPUser extends DelegatingUser {
|
|||||||
// Create independent, mutable copy of attributes
|
// Create independent, mutable copy of attributes
|
||||||
Map<String, String> attributes = new HashMap<>(super.getAttributes());
|
Map<String, String> attributes = new HashMap<>(super.getAttributes());
|
||||||
|
|
||||||
|
if (!attributes.containsKey(TOTP_KEY_DISABLED_ATTRIBUTE_NAME))
|
||||||
|
attributes.put(TOTP_KEY_DISABLED_ATTRIBUTE_NAME, null);
|
||||||
|
|
||||||
// Replace secret key with simple boolean attribute representing
|
// Replace secret key with simple boolean attribute representing
|
||||||
// whether a key has been generated at all
|
// whether a key has been generated at all
|
||||||
String secret = attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME);
|
String secret = attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME);
|
||||||
|
@@ -23,12 +23,14 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.auth.totp.usergroup.TOTPUserGroup;
|
||||||
import org.apache.guacamole.form.Form;
|
import org.apache.guacamole.form.Form;
|
||||||
import org.apache.guacamole.net.auth.DecoratingDirectory;
|
import org.apache.guacamole.net.auth.DecoratingDirectory;
|
||||||
import org.apache.guacamole.net.auth.DelegatingUserContext;
|
import org.apache.guacamole.net.auth.DelegatingUserContext;
|
||||||
import org.apache.guacamole.net.auth.Directory;
|
import org.apache.guacamole.net.auth.Directory;
|
||||||
import org.apache.guacamole.net.auth.User;
|
import org.apache.guacamole.net.auth.User;
|
||||||
import org.apache.guacamole.net.auth.UserContext;
|
import org.apache.guacamole.net.auth.UserContext;
|
||||||
|
import org.apache.guacamole.net.auth.UserGroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TOTP-specific UserContext implementation which wraps the UserContext of
|
* TOTP-specific UserContext implementation which wraps the UserContext of
|
||||||
@@ -65,6 +67,24 @@ public class TOTPUserContext extends DelegatingUserContext {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Directory<UserGroup> getUserGroupDirectory() throws GuacamoleException {
|
||||||
|
return new DecoratingDirectory<UserGroup>(super.getUserGroupDirectory()) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UserGroup decorate(UserGroup object) {
|
||||||
|
return new TOTPUserGroup(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UserGroup undecorate(UserGroup object) {
|
||||||
|
assert(object instanceof TOTPUserGroup);
|
||||||
|
return ((TOTPUserGroup) object).getUndecorated();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Form> getUserAttributes() {
|
public Collection<Form> getUserAttributes() {
|
||||||
Collection<Form> userAttrs = new HashSet<>(super.getUserAttributes());
|
Collection<Form> userAttrs = new HashSet<>(super.getUserAttributes());
|
||||||
@@ -72,4 +92,11 @@ public class TOTPUserContext extends DelegatingUserContext {
|
|||||||
return Collections.unmodifiableCollection(userAttrs);
|
return Collections.unmodifiableCollection(userAttrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Form> getUserGroupAttributes() {
|
||||||
|
Collection<Form> userGroupAttrs = new HashSet<>(super.getUserGroupAttributes());
|
||||||
|
userGroupAttrs.add(TOTPUserGroup.TOTP_USER_GROUP_CONFIG);
|
||||||
|
return Collections.unmodifiableCollection(userGroupAttrs);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -26,19 +26,23 @@ import java.security.InvalidKeyException;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
import org.apache.guacamole.GuacamoleSecurityException;
|
import org.apache.guacamole.GuacamoleSecurityException;
|
||||||
import org.apache.guacamole.GuacamoleUnsupportedException;
|
import org.apache.guacamole.GuacamoleUnsupportedException;
|
||||||
import org.apache.guacamole.auth.totp.conf.ConfigurationService;
|
import org.apache.guacamole.auth.totp.conf.ConfigurationService;
|
||||||
import org.apache.guacamole.auth.totp.form.AuthenticationCodeField;
|
import org.apache.guacamole.auth.totp.form.AuthenticationCodeField;
|
||||||
|
import org.apache.guacamole.auth.totp.usergroup.TOTPUserGroup;
|
||||||
import org.apache.guacamole.form.Field;
|
import org.apache.guacamole.form.Field;
|
||||||
import org.apache.guacamole.language.TranslatableGuacamoleClientException;
|
import org.apache.guacamole.language.TranslatableGuacamoleClientException;
|
||||||
import org.apache.guacamole.language.TranslatableGuacamoleInsufficientCredentialsException;
|
import org.apache.guacamole.language.TranslatableGuacamoleInsufficientCredentialsException;
|
||||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||||
import org.apache.guacamole.net.auth.Credentials;
|
import org.apache.guacamole.net.auth.Credentials;
|
||||||
|
import org.apache.guacamole.net.auth.Directory;
|
||||||
import org.apache.guacamole.net.auth.User;
|
import org.apache.guacamole.net.auth.User;
|
||||||
import org.apache.guacamole.net.auth.UserContext;
|
import org.apache.guacamole.net.auth.UserContext;
|
||||||
|
import org.apache.guacamole.net.auth.UserGroup;
|
||||||
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
|
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
|
||||||
import org.apache.guacamole.totp.TOTPGenerator;
|
import org.apache.guacamole.totp.TOTPGenerator;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -204,6 +208,65 @@ public class UserVerificationService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the user in question, via both UserContext and AuthenticatedUser,
|
||||||
|
* to see if TOTP has been disabled for this user, either directly or via
|
||||||
|
* membership in a group that has had TOTP marked as disabled.
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* The UserContext for the user being verified.
|
||||||
|
*
|
||||||
|
* @param authenticatedUser
|
||||||
|
* The AuthenticatedUser for the user being verified.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* True if TOTP access has been disabled for the user, otherwise
|
||||||
|
* false.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the extension handling storage fails internally while attempting
|
||||||
|
* to update the user.
|
||||||
|
*/
|
||||||
|
private boolean totpDisabled(UserContext context,
|
||||||
|
AuthenticatedUser authenticatedUser)
|
||||||
|
throws GuacamoleException {
|
||||||
|
|
||||||
|
// If TOTP is disabled for this user, return, allowing login to continue
|
||||||
|
Map<String, String> myAttributes = context.self().getAttributes();
|
||||||
|
if (myAttributes != null
|
||||||
|
&& TOTPUser.TRUTH_VALUE.equals(myAttributes.get(TOTPUser.TOTP_KEY_DISABLED_ATTRIBUTE_NAME))) {
|
||||||
|
|
||||||
|
logger.warn("TOTP validation has been disabled for user \"{}\"",
|
||||||
|
context.self().getIdentifier());
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any effective user groups have TOTP marked as disabled
|
||||||
|
Set<String> userGroups = authenticatedUser.getEffectiveUserGroups();
|
||||||
|
Directory<UserGroup> directoryGroups = context.getPrivileged().getUserGroupDirectory();
|
||||||
|
for (String userGroup : userGroups) {
|
||||||
|
UserGroup thisGroup = directoryGroups.get(userGroup);
|
||||||
|
if (thisGroup == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Map<String, String> grpAttributes = thisGroup.getAttributes();
|
||||||
|
if (grpAttributes != null
|
||||||
|
&& TOTPUserGroup.TRUTH_VALUE.equals(grpAttributes.get(TOTPUserGroup.TOTP_KEY_DISABLED_ATTRIBUTE_NAME))) {
|
||||||
|
|
||||||
|
logger.warn("TOTP validation will be bypassed for user \"{}\""
|
||||||
|
+ " because it has been disabled for group \"{}\"",
|
||||||
|
context.self().getIdentifier(), userGroup);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TOTP has not been disabled
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies the identity of the given user using TOTP. If a authentication
|
* 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
|
* code from the user's TOTP device has not already been provided, a code is
|
||||||
@@ -230,6 +293,10 @@ public class UserVerificationService {
|
|||||||
if (username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
|
if (username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Check if TOTP has been disabled for this user
|
||||||
|
if (totpDisabled(context, authenticatedUser))
|
||||||
|
return;
|
||||||
|
|
||||||
// Ignore users which do not have an associated key
|
// Ignore users which do not have an associated key
|
||||||
UserTOTPKey key = getKey(context, username);
|
UserTOTPKey key = getKey(context, username);
|
||||||
if (key == null)
|
if (key == null)
|
||||||
|
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* 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.usergroup;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.guacamole.form.BooleanField;
|
||||||
|
import org.apache.guacamole.form.Form;
|
||||||
|
import org.apache.guacamole.net.auth.DelegatingUserGroup;
|
||||||
|
import org.apache.guacamole.net.auth.UserGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A UserGroup that wraps another UserGroup implementation, decorating it with
|
||||||
|
* attributes that control TOTP configuration for users that are members of that
|
||||||
|
* group.
|
||||||
|
*/
|
||||||
|
public class TOTPUserGroup extends DelegatingUserGroup {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attribute associated with a group that disables the TOTP requirement
|
||||||
|
* for any users that are a member of that group, or are members of any
|
||||||
|
* groups that are members of this group.
|
||||||
|
*/
|
||||||
|
public static final String TOTP_KEY_DISABLED_ATTRIBUTE_NAME = "guac-totp-disabled";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The string value used by TOTP user attributes to represent the boolean
|
||||||
|
* value "true".
|
||||||
|
*/
|
||||||
|
public static final String TRUTH_VALUE = "true";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The form that contains fields for configuring TOTP for members of this
|
||||||
|
* group.
|
||||||
|
*/
|
||||||
|
public static final Form TOTP_USER_GROUP_CONFIG = new Form("totp-user-group-config",
|
||||||
|
Arrays.asList(
|
||||||
|
new BooleanField(TOTP_KEY_DISABLED_ATTRIBUTE_NAME, TRUTH_VALUE)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of this user group implementation, wrapping the
|
||||||
|
* provided UserGroup.
|
||||||
|
*
|
||||||
|
* @param userGroup
|
||||||
|
* The UserGroup to be wrapped.
|
||||||
|
*/
|
||||||
|
public TOTPUserGroup(UserGroup userGroup) {
|
||||||
|
super(userGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the original UserGroup that this implementation is wrapping.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The original UserGroup that this implementation wraps.
|
||||||
|
*/
|
||||||
|
public UserGroup getUndecorated() {
|
||||||
|
return getDelegateUserGroupGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not TOTP has been disabled for members of this group.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* True if TOTP has been disabled for members of this group, otherwise
|
||||||
|
* false.
|
||||||
|
*/
|
||||||
|
public boolean totpDisabled() {
|
||||||
|
return (TRUTH_VALUE.equals(getAttributes().get(TOTP_KEY_DISABLED_ATTRIBUTE_NAME)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getAttributes() {
|
||||||
|
|
||||||
|
// Create a mutable copy of the attributes
|
||||||
|
Map<String, String> attributes = new HashMap<>(super.getAttributes());
|
||||||
|
|
||||||
|
if (!attributes.containsKey(TOTP_KEY_DISABLED_ATTRIBUTE_NAME))
|
||||||
|
attributes.put(TOTP_KEY_DISABLED_ATTRIBUTE_NAME, null);
|
||||||
|
|
||||||
|
return attributes;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -33,11 +33,20 @@
|
|||||||
|
|
||||||
"USER_ATTRIBUTES" : {
|
"USER_ATTRIBUTES" : {
|
||||||
|
|
||||||
|
"FIELD_HEADER_GUAC_TOTP_DISABLED" : "Disable TOTP:",
|
||||||
"FIELD_HEADER_GUAC_TOTP_KEY_GENERATED" : "Secret key generated:",
|
"FIELD_HEADER_GUAC_TOTP_KEY_GENERATED" : "Secret key generated:",
|
||||||
"FIELD_HEADER_GUAC_TOTP_KEY_CONFIRMED" : "Authentication device confirmed:",
|
"FIELD_HEADER_GUAC_TOTP_KEY_CONFIRMED" : "Authentication device confirmed:",
|
||||||
|
|
||||||
"SECTION_HEADER_TOTP_ENROLLMENT_STATUS" : "TOTP Enrollment Status"
|
"SECTION_HEADER_TOTP_ENROLLMENT_STATUS" : "TOTP Enrollment Status"
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
"USER_GROUP_ATTRIBUTES" : {
|
||||||
|
|
||||||
|
"FIELD_HEADER_GUAC_TOTP_DISABLED" : "Disable TOTP:",
|
||||||
|
|
||||||
|
"SECTION_HEADER_TOTP_USER_GROUP_CONFIG" : "TOTP Configuration"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user