diff --git a/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/ConfigurationService.java b/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/ConfigurationService.java index 2ab1350dc7..d9f49813c4 100644 --- a/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/ConfigurationService.java +++ b/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/ConfigurationService.java @@ -53,5 +53,26 @@ public String getHttpAuthHeader() throws GuacamoleException { "REMOTE_USER" ); } + + /** + * Returns true if the usernames provided to the header authentication + * module should be treated as case-sensitive, or false if usernames + * should be treated as case-insensitive. This will default to the global + * Guacamole configuration for case-sensitivity, which defaults to true, but + * can be overridden for this extension, if desired. + * + * @return + * true if usernames should be treated as case-sensitive, otherwise + * false. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + return environment.getProperty( + HTTPHeaderGuacamoleProperties.HTTP_AUTH_CASE_SENSITIVE_USERNAMES, + environment.getCaseSensitiveUsernames() + ); + } } diff --git a/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/HTTPHeaderGuacamoleProperties.java b/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/HTTPHeaderGuacamoleProperties.java index 733a1f8cdb..acf514a755 100644 --- a/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/HTTPHeaderGuacamoleProperties.java +++ b/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/HTTPHeaderGuacamoleProperties.java @@ -19,7 +19,7 @@ package org.apache.guacamole.auth.header; -import org.apache.guacamole.properties.IntegerGuacamoleProperty; +import org.apache.guacamole.properties.BooleanGuacamoleProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; @@ -36,7 +36,7 @@ public class HTTPHeaderGuacamoleProperties { private HTTPHeaderGuacamoleProperties() {} /** - * The header used for HTTP header authentication. + * A property used to configure the header used for HTTP header authentication. */ public static final StringGuacamoleProperty HTTP_AUTH_HEADER = new StringGuacamoleProperty() { @@ -44,5 +44,17 @@ private HTTPHeaderGuacamoleProperties() {} public String getName() { return "http-auth-header"; } }; + + /** + * A property used to configure whether or not usernames within the header + * module should be treated as case-sensitive. + */ + public static final BooleanGuacamoleProperty HTTP_AUTH_CASE_SENSITIVE_USERNAMES = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "http-auth-case-sensitive-usernames"; } + + }; } diff --git a/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/user/AuthenticatedUser.java b/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/user/AuthenticatedUser.java index 793fef01e6..d498c5c4da 100644 --- a/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/user/AuthenticatedUser.java +++ b/extensions/guacamole-auth-header/src/main/java/org/apache/guacamole/auth/header/user/AuthenticatedUser.java @@ -20,9 +20,13 @@ package org.apache.guacamole.auth.header.user; import com.google.inject.Inject; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.header.ConfigurationService; import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An HTTP header implementation of AuthenticatedUser, associating a @@ -31,12 +35,23 @@ */ public class AuthenticatedUser extends AbstractAuthenticatedUser { + /** + * Logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticatedUser.class); + /** * Reference to the authentication provider associated with this * authenticated user. */ @Inject private AuthenticationProvider authProvider; + + /** + * Service for retrieving header configuration information. + */ + @Inject + private ConfigurationService confService; /** * The credentials provided when this user was authenticated. @@ -58,6 +73,19 @@ public void init(String username, Credentials credentials) { setIdentifier(username.toLowerCase()); } + @Override + public boolean isCaseSensitive() { + try { + return confService.getCaseSensitiveUsernames(); + } + catch (GuacamoleException e) { + LOGGER.error("Error when trying to retrieve header configuration: {}." + + " Usernames comparison will be case-sensitive.", e); + LOGGER.debug("Exception caught when retrieving header configuration.", e); + return true; + } + } + @Override public AuthenticationProvider getAuthenticationProvider() { return authProvider; diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/HistoryTrackingConnection.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/HistoryTrackingConnection.java index f25620bda6..cdd50e3bc0 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/HistoryTrackingConnection.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/HistoryTrackingConnection.java @@ -19,6 +19,7 @@ package org.apache.guacamole.auth.jdbc; +import com.google.inject.Inject; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -55,6 +56,12 @@ public class HistoryTrackingConnection extends DelegatingConnection { * established connections. */ private final ConnectionRecordMapper connectionRecordMapper; + + /** + * The Guacamole server environment. + */ + @Inject + private JDBCEnvironment environment; /** * Creates a new HistoryConnection that wraps the given connection, @@ -98,7 +105,8 @@ public GuacamoleTunnel connect(GuacamoleClientInformation info, connectionRecordModel.setConnectionName(this.getDelegateConnection().getName()); // Insert the connection history record to mark the start of this connection - connectionRecordMapper.insert(connectionRecordModel); + connectionRecordMapper.insert(connectionRecordModel, + environment.getCaseSensitiveUsernames()); // Include history record UUID as token ModeledConnectionRecord modeledRecord = new ModeledConnectionRecord(connectionRecordModel); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ActivityRecordMapper.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ActivityRecordMapper.java index 52fbf9e640..17b4571ed8 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ActivityRecordMapper.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ActivityRecordMapper.java @@ -38,11 +38,16 @@ public interface ActivityRecordMapper { * * @param record * The activity record to insert. + * + * @param caseSensitive + * Whether or not string comparisons should be done in a case-sensitive + * manner. * * @return * The number of rows inserted. */ - int insert(@Param("record") ModelType record); + int insert(@Param("record") ModelType record, + @Param("caseSensitive") boolean caseSensitive); /** * Updates the given activity record in the database, assigning an end @@ -85,6 +90,10 @@ public interface ActivityRecordMapper { * * @param limit * The maximum number of records that should be returned. + * + * @param caseSensitive + * Whether or not string comparisons should be done in a case-sensitive + * manner. * * @return * The results of the search performed with the given parameters. @@ -93,7 +102,8 @@ List search(@Param("identifier") String identifier, @Param("recordIdentifier") String recordIdentifier, @Param("terms") Collection terms, @Param("sortPredicates") List sortPredicates, - @Param("limit") int limit); + @Param("limit") int limit, + @Param("caseSensitive") boolean caseSensitive); /** * Searches for up to limit activity records that contain @@ -132,6 +142,10 @@ List search(@Param("identifier") String identifier, * when determining the permissions effectively granted to the user. If * no groups are given, only permissions directly granted to the user * will be used. + * + * @param caseSensitive + * Whether or not string comparisons should be done in a case-sensitive + * manner. * * @return * The results of the search performed with the given parameters. @@ -142,6 +156,7 @@ List searchReadable(@Param("identifier") String identifier, @Param("terms") Collection terms, @Param("sortPredicates") List sortPredicates, @Param("limit") int limit, - @Param("effectiveGroups") Collection effectiveGroups); + @Param("effectiveGroups") Collection effectiveGroups, + @Param("caseSensitive") boolean caseSensitive); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionService.java index 31f4df49c7..e08d13b0cd 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionService.java @@ -28,19 +28,20 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser; -import org.apache.guacamole.language.TranslatableGuacamoleClientOverrunException; -import org.apache.guacamole.language.TranslatableMessage; -import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper; -import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService; import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; +import org.apache.guacamole.auth.jdbc.JDBCEnvironment; import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm; import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate; +import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser; import org.apache.guacamole.auth.jdbc.base.ModeledChildDirectoryObjectService; +import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper; import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionMapper; import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionMapper; +import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService; +import org.apache.guacamole.language.TranslatableGuacamoleClientOverrunException; +import org.apache.guacamole.language.TranslatableMessage; import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionRecord; @@ -85,6 +86,12 @@ public class ConnectionService extends ModeledChildDirectoryObjectService connectionProvider; + + /** + * The server environment for retrieving configuration. + */ + @Inject + private JDBCEnvironment environment; /** * Service for creating and tracking tunnels. @@ -486,14 +493,16 @@ public List retrieveHistory(String identifier, // Bypass permission checks if the user is privileged or has System-level audit permissions if (user.isPrivileged() || user.getUser().getEffectivePermissions().getSystemPermissions().hasPermission(SystemPermission.Type.AUDIT)) searchResults = connectionRecordMapper.search(identifier, - recordIdentifier, requiredContents, sortPredicates, limit); + recordIdentifier, requiredContents, sortPredicates, limit, + environment.getCaseSensitiveUsernames()); // Otherwise only return explicitly readable history records else searchResults = connectionRecordMapper.searchReadable(identifier, user.getUser().getModel(), recordIdentifier, requiredContents, sortPredicates, limit, - user.getEffectiveUserGroups()); + user.getEffectiveUserGroups(), + environment.getCaseSensitiveUsernames()); return getObjectInstances(searchResults); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.java index f7412a4fd0..c3489e5080 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.java @@ -19,7 +19,142 @@ package org.apache.guacamole.auth.jdbc.permission; +import java.util.Collection; +import org.apache.guacamole.auth.jdbc.base.EntityModel; +import org.apache.guacamole.net.auth.permission.ObjectPermission; +import org.apache.ibatis.annotations.Param; + /** * Mapper for user permissions. */ -public interface UserPermissionMapper extends ObjectPermissionMapper {} +public interface UserPermissionMapper extends ObjectPermissionMapper { + + /** + * Deletes the given permissions from the database. If any permissions do + * not exist, they will be ignored. + * + * @param permissions + * The permissions to delete. + * + * @param caseSensitive + * Whether or not string comparisons for usernames will be done in a + * case-sensitive manner. + * + * @return + * The number of rows deleted. + */ + int delete(@Param("permissions") Collection permissions, + @Param("caseSensitive") boolean caseSensitive); + + /** + * Inserts the given permissions into the database. If any permissions + * already exist, they will be ignored. + * + * @param permissions + * The permissions to insert. + * + * @param caseSensitive + * Whether or not string comparisons for usernames will be done in a + * case-sensitive manner. + * + * @return + * The number of rows inserted. + */ + int insert(@Param("permissions") Collection permissions, + @Param("caseSensitive") boolean caseSensitive); + + /** + * Retrieves all permissions associated with the given entity (user or user + * group). + * + * @param entity + * The entity to retrieve permissions for. + * + * @param effectiveGroups + * The identifiers of all groups that should be taken into account + * when determining the permissions effectively granted to the user. If + * no groups are given, only permissions directly granted to the user + * will be used. + * + * @param caseSensitive + * Whether or not string comparisons for usernames will be done in a + * case-sensitive manner. + * + * @return + * All permissions associated with the given entity. + */ + Collection select(@Param("entity") EntityModel entity, + @Param("effectiveGroups") Collection effectiveGroups, + @Param("caseSensitive") boolean caseSensitive); + + /** + * Retrieve the permission of the given type associated with the given + * entity and object, if it exists. If no such permission exists, null is + * returned. + * + * @param entity + * The entity to retrieve permissions for. + * + * @param type + * The type of permission to return. + * + * @param identifier + * The identifier of the object affected by the permission to return. + * + * @param effectiveGroups + * The identifiers of all groups that should be taken into account + * when determining the permissions effectively granted to the user. If + * no groups are given, only permissions directly granted to the user + * will be used. + * + * @param caseSensitive + * Whether or not string comparisons for usernames will be done in a + * case-sensitive manner. + * + * @return + * The requested permission, or null if no such permission is granted + * to the given entity for the given object. + */ + ObjectPermissionModel selectOne(@Param("entity") EntityModel entity, + @Param("type") ObjectPermission.Type type, + @Param("identifier") String identifier, + @Param("effectiveGroups") Collection effectiveGroups, + @Param("caseSensitive") boolean caseSensitive); + + /** + * Retrieves the subset of the given identifiers for which the given entity + * has at least one of the given permissions. + * + * @param entity + * The entity to check permissions of. + * + * @param permissions + * The permissions to check. An identifier will be included in the + * resulting collection if at least one of these permissions is granted + * for the associated object + * + * @param identifiers + * The identifiers of the objects affected by the permissions being + * checked. + * + * @param effectiveGroups + * The identifiers of all groups that should be taken into account + * when determining the permissions effectively granted to the user. If + * no groups are given, only permissions directly granted to the user + * will be used. + * + * @param caseSensitive + * Whether or not string comparisons for usernames will be done in a + * case-sensitive manner. + * + * @return + * A collection containing the subset of identifiers for which at least + * one of the specified permissions is granted. + */ + Collection selectAccessibleIdentifiers(@Param("entity") EntityModel entity, + @Param("permissions") Collection permissions, + @Param("identifiers") Collection identifiers, + @Param("effectiveGroups") Collection effectiveGroups, + @Param("caseSenstive") boolean caseSensitive); + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java index 148d5a2f94..639ed5e43c 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java @@ -131,16 +131,21 @@ private boolean matches(String str, Pattern... patterns) { * @return * true if the given password matches any of the user's previous * passwords, up to the specified limit, false otherwise. + * + * @throws GuacamoleException + * If an error occurs accessing environment information used for + * retrieving configuration values. */ private boolean matchesPreviousPasswords(String password, String username, - int historySize) { + int historySize) throws GuacamoleException { // No need to compare if no history is relevant if (historySize <= 0) return false; // Check password against all recorded hashes - List history = passwordRecordMapper.select(username, historySize); + List history = passwordRecordMapper.select(username, + historySize, environment.getCaseSensitiveUsernames()); for (PasswordRecordModel record : history) { byte[] hash = encryptionService.createPasswordHash(password, record.getPasswordSalt()); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java index 236d184854..e0a6091b07 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java @@ -34,6 +34,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.guacamole.auth.jdbc.JDBCEnvironment; import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser; import org.apache.guacamole.auth.jdbc.connection.ModeledConnection; import org.apache.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; @@ -167,6 +168,12 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS */ @Inject private SharedConnectionMap connectionMap; + + /** + * The Guacamole server environment. + */ + @Inject + private JDBCEnvironment environment; /** * All active connections through the tunnel having a given UUID. @@ -470,7 +477,9 @@ private GuacamoleTunnel assignGuacamoleTunnel(ActiveConnectionRecord activeConne // Record new active connection Runnable cleanupTask = new ConnectionCleanupTask(activeConnection); try { - connectionRecordMapper.insert(activeConnection.getModel()); // This MUST happen before getUUID() is invoked, to ensure the ID driving the UUID exists + // This MUST happen before getUUID() is invoked, to ensure the ID driving the UUID exists + connectionRecordMapper.insert(activeConnection.getModel(), + environment.getCaseSensitiveUsernames()); activeTunnels.put(activeConnection.getUUID().toString(), activeConnection); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledAuthenticatedUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledAuthenticatedUser.java index 7ede92c6cd..6c5ca16f4d 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledAuthenticatedUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledAuthenticatedUser.java @@ -194,5 +194,10 @@ public Set getEffectiveUserGroups() { public boolean isPrivileged() throws GuacamoleException { return getUser().isPrivileged(); } + + @Override + public boolean isCaseSensitive() { + return user.isCaseSensitive(); + } } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUser.java index 811ccaa311..3b0ae67f4f 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUser.java @@ -36,6 +36,7 @@ import org.apache.guacamole.auth.jdbc.security.PasswordEncryptionService; import org.apache.guacamole.auth.jdbc.security.SaltService; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.jdbc.JDBCEnvironment; import org.apache.guacamole.auth.jdbc.base.ModeledPermissions; import org.apache.guacamole.form.BooleanField; import org.apache.guacamole.form.DateField; @@ -180,6 +181,13 @@ public class ModeledUser extends ModeledPermissions implements User { */ @Inject private Provider userRecordSetProvider; + + /** + * The environment associated with this instance of the JDBC authentication + * module. + */ + @Inject + private JDBCEnvironment environment; /** * Whether attributes which control access restrictions should be exposed @@ -780,5 +788,18 @@ public Permissions getEffectivePermissions() throws GuacamoleException { public boolean isSkeleton() { return (getModel().getEntityID() == null); } + + @Override + public boolean isCaseSensitive() { + try { + return environment.getCaseSensitiveUsernames(); + } + catch (GuacamoleException e) { + logger.error("Failed to retrieve the configuration for case-sensitive usernames: {}." + + " Usernames comparisons will be case-sensitive.", e.getMessage()); + logger.debug("Exception caught when attempting to read the configuration.", e); + return true; + } + } } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUserContext.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUserContext.java index e5ad5d2eae..f90c158552 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUserContext.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUserContext.java @@ -176,8 +176,12 @@ public void init(ModeledAuthenticatedUser currentUser) { * invocation has any effect. If this function is never invoked, no * activity record will be recorded, including when this UserContext is * invalidated. + * + * @throws GuacamoleException + * If an error occurs retrieving configuration information from the + * environment. */ - public void recordUserLogin() { + public void recordUserLogin() throws GuacamoleException { // Do nothing if invoked multiple times if (userRecord != null) @@ -190,7 +194,7 @@ public void recordUserLogin() { userRecord.setRemoteHost(getCurrentUser().getCredentials().getRemoteAddress()); // Insert record representing login - userRecordMapper.insert(userRecord); + userRecordMapper.insert(userRecord, environment.getCaseSensitiveUsernames()); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.java index 903ff6562d..c505afcc9e 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.java @@ -38,6 +38,10 @@ public interface PasswordRecordMapper extends ModeledDirectoryObjectMapper select(@Param("username") String username, - @Param("maxHistorySize") int maxHistorySize); + @Param("maxHistorySize") int maxHistorySize, + @Param("caseSensitive") boolean caseSensitive); /** * Inserts the given password record. Old records exceeding the maximum diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserMapper.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserMapper.java index cf829be588..243618c2b0 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserMapper.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserMapper.java @@ -19,6 +19,7 @@ package org.apache.guacamole.auth.jdbc.user; +import java.util.Collection; import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper; import org.apache.ibatis.annotations.Param; @@ -33,10 +34,82 @@ public interface UserMapper extends ModeledDirectoryObjectMapper { * * @param username * The username of the user to return. + * + * @param caseSensitive + * true if the search should evaluate usernames in a case-sensitive + * manner, otherwise false. * * @return * The user having the given username, or null if no such user exists. */ - UserModel selectOne(@Param("username") String username); + UserModel selectOne(@Param("username") String username, + @Param("caseSensitive") boolean caseSensitive); + + /** + * Selects all users which have the given identifiers. If an identifier + * has no corresponding object, it will be ignored. This should only be + * called on behalf of a system administrator. If users are needed by a + * non-administrative user who must have explicit read rights, use + * selectReadable() instead. + * + * @param identifiers + * The identifiers of the users to return. + * + * @param caseSensitive + * true if the query should evaluate username identifiers in a + * case-sensitive manner, otherwise false. + * + * @return + * A Collection of all objects having the given identifiers. + */ + Collection select(@Param("identifiers") Collection identifiers, + @Param("caseSensitive") boolean caseSensitive); + + /** + * Selects all users which have the given identifiers and are explicitly + * readable by the given user. If an identifier has no corresponding + * object, or the corresponding user is unreadable, it will be ignored. + * If users are needed by a system administrator (who, by definition, + * does not need explicit read rights), use select() instead. + * + * @param user + * The user whose permissions should determine whether an object + * is returned. + * + * @param identifiers + * The identifiers of the users to return. + * + * @param effectiveGroups + * The identifiers of any known effective groups that should be taken + * into account, such as those defined externally to the database. + * + * @param caseSensitive + * true if the query should evaluate username identifiers in a + * case-sensitive manner, otherwise false. + * + * @return + * A Collection of all objects having the given identifiers. + */ + Collection selectReadable(@Param("user") UserModel user, + @Param("identifiers") Collection identifiers, + @Param("effectiveGroups") Collection effectiveGroups, + @Param("caseSensitive") boolean caseSensitive); + + /** + * Deletes the given user from the database. If the user does not + * exist, this operation has no effect. + * + * @param identifier + * The identifier of the user to delete. + * + * @param caseSensitive + * true if the query should evaluate username identifiers in a + * case-sensitive manner, otherwise false. + * + * @return + * The number of rows deleted. + */ + int delete(@Param("identifier") String identifier, + @Param("caseSensitive") boolean caseSensitive); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java index 08acff2a65..9478fd3719 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java @@ -33,6 +33,7 @@ import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleUnsupportedException; +import org.apache.guacamole.auth.jdbc.JDBCEnvironment; import org.apache.guacamole.auth.jdbc.base.ActivityRecordModel; import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm; import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate; @@ -155,6 +156,12 @@ public class UserService extends ModeledDirectoryObjectService getObjectMapper() { @@ -241,7 +248,8 @@ protected void beforeCreate(ModeledAuthenticatedUser user, User object, throw new GuacamoleClientException("The username must not be blank."); // Do not create duplicate users - Collection existing = userMapper.select(Collections.singleton(model.getIdentifier())); + Collection existing = userMapper.select(Collections.singleton( + model.getIdentifier()), environment.getCaseSensitiveUsernames()); if (!existing.isEmpty()) throw new GuacamoleClientException("User \"" + model.getIdentifier() + "\" already exists."); @@ -277,7 +285,8 @@ protected void beforeUpdate(ModeledAuthenticatedUser user, throw new GuacamoleClientException("The username must not be blank."); // Check whether such a user is already present - UserModel existing = userMapper.selectOne(model.getIdentifier()); + UserModel existing = userMapper.selectOne(model.getIdentifier(), + environment.getCaseSensitiveUsernames()); if (existing != null) { // Do not rename to existing user @@ -337,6 +346,17 @@ protected void beforeDelete(ModeledAuthenticatedUser user, String identifier) th throw new GuacamoleUnsupportedException("Deleting your own user is not allowed."); } + + @Override + public void deleteObject(ModeledAuthenticatedUser user, String identifier) + throws GuacamoleException { + + beforeDelete(user, identifier); + + // Delete object + userMapper.delete(identifier, environment.getCaseSensitiveUsernames()); + + } @Override protected boolean isValidIdentifier(String identifier) { @@ -375,7 +395,8 @@ public ModeledAuthenticatedUser retrieveAuthenticatedUser(AuthenticationProvider String password = credentials.getPassword(); // Retrieve corresponding user model, if such a user exists - UserModel userModel = userMapper.selectOne(username); + UserModel userModel = userMapper.selectOne(username, + environment.getCaseSensitiveUsernames()); if (userModel == null) return null; @@ -416,7 +437,8 @@ public ModeledUser retrieveUser(AuthenticationProvider authenticationProvider, AuthenticatedUser authenticatedUser) throws GuacamoleException { // Retrieve corresponding user model, if such a user exists - UserModel userModel = userMapper.selectOne(authenticatedUser.getIdentifier()); + UserModel userModel = userMapper.selectOne(authenticatedUser.getIdentifier(), + environment.getCaseSensitiveUsernames()); if (userModel == null) return null; @@ -614,14 +636,16 @@ public List retrieveHistory(String username, // Bypass permission checks if the user is privileged or has System-level audit permissions if (user.isPrivileged() || user.getUser().getEffectivePermissions().getSystemPermissions().hasPermission(SystemPermission.Type.AUDIT)) searchResults = userRecordMapper.search(username, recordIdentifier, - requiredContents, sortPredicates, limit); + requiredContents, sortPredicates, limit, + environment.getCaseSensitiveUsernames()); // Otherwise only return explicitly readable history records else searchResults = userRecordMapper.searchReadable(username, user.getUser().getModel(), recordIdentifier, requiredContents, sortPredicates, limit, - user.getEffectiveUserGroups()); + user.getEffectiveUserGroups(), + environment.getCaseSensitiveUsernames()); return getObjectInstances(searchResults); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.java index b668d07fec..3b2c3175b1 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.java @@ -19,10 +19,67 @@ package org.apache.guacamole.auth.jdbc.usergroup; +import java.util.Collection; import org.apache.guacamole.auth.jdbc.base.ObjectRelationMapper; +import org.apache.ibatis.annotations.Param; /** * Mapper for the one-to-many relationship between a user group and its user * members. */ -public interface UserGroupMemberUserMapper extends ObjectRelationMapper {} +public interface UserGroupMemberUserMapper extends ObjectRelationMapper { + + /** + * Inserts rows as necessary to establish the one-to-many relationship + * represented by the RelatedObjectSet between the given parent and + * children. If the relation for any parent/child pair is already present, + * no attempt is made to insert a new row for that relation. + * + * @param parent + * The model of the object on the parent side of the one-to-many + * relationship represented by the RelatedObjectSet. + * + * @param children + * The identifiers of the objects on the child side of the one-to-many + * relationship represented by the RelatedObjectSet. + * + * @param caseSensitive + * True if username case should be respected when looking up the username + * in the guacamole_entity table, or false if the query to the + * guacamole_entity table should be done case-insensitively. + * + * @return + * The number of rows inserted. + */ + int insert(@Param("parent") UserGroupModel parent, + @Param("children") Collection children, + @Param("caseSensitive") boolean caseSensitive); + + /** + * Deletes rows as necessary to modify the one-to-many relationship + * represented by the RelatedObjectSet between the given parent and + * children. If the relation for any parent/child pair does not exist, + * that specific relation is ignored, and deletion proceeds with the + * remaining relations. + * + * @param parent + * The model of the object on the parent side of the one-to-many + * relationship represented by the RelatedObjectSet. + * + * @param children + * The identifiers of the objects on the child side of the one-to-many + * relationship represented by the RelatedObjectSet. + * + * @param caseSensitive + * True if username case should be respected when looking up the username + * in the guacamole_entity table, or false if the query to the + * guacamole_entity table should be done case-insensitively. + * + * @return + * The number of rows deleted. + */ + int delete(@Param("parent") UserGroupModel parent, + @Param("children") Collection children, + @Param("caseSesitive") boolean caseSensitive); + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLEnvironment.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLEnvironment.java index a3dea8964d..08ccf54043 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLEnvironment.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLEnvironment.java @@ -439,7 +439,30 @@ public boolean enforceAccessWindowsForActiveSessions() throws GuacamoleException // Enforce access window restrictions for active sessions unless explicitly disabled return getProperty( MySQLGuacamoleProperties.MYSQL_ENFORCE_ACCESS_WINDOWS_FOR_ACTIVE_SESSIONS, - true); + true + ); + } + + @Override + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + + // Get the configured value for the property. + boolean caseSensitiveUsernames = getProperty( + MySQLGuacamoleProperties.MYSQL_CASE_SENSITIVE_USERNAMES, + super.getCaseSensitiveUsernames() + ); + + // If property has been set to true, warn the admin. + if (caseSensitiveUsernames) + logger.warn("You have enabled case-sensitive usernames; however, " + + "MySQL's default collations do not support case-sensitive " + + "string comparisons. If you really want case-sensitive " + + "usernames you will need to configure your database " + + "appropriately."); + + // Return the configured setting. + return caseSensitiveUsernames; + } } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLGuacamoleProperties.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLGuacamoleProperties.java index 5e20b52c63..bbb1a29b01 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLGuacamoleProperties.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLGuacamoleProperties.java @@ -301,6 +301,20 @@ private MySQLGuacamoleProperties() {} @Override public String getName() { return "mysql-batch-size"; } - }; + }; + + /** + * A property used to configure whether or not usernames within the MySQL + * JDBC module should be treated as case-sensitive. Be aware that MySQL's + * default database collations do not do case-sensitive comparisons, so in + * many cases they will effectively be case-insensitive. + */ + public static final BooleanGuacamoleProperty MYSQL_CASE_SENSITIVE_USERNAMES = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "mysql-case-sensitive-usernames"; } + + }; } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml index 11ca348ddc..f9e2e600ca 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml @@ -61,7 +61,14 @@ (SELECT user_id FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - guacamole_entity.name = #{record.username,jdbcType=VARCHAR} + + + guacamole_entity.name = #{record.username,jdbcType=VARCHAR} + + + LOWER(guacamole_entity.name) = LOWER(#{record.username,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER'), #{record.username,jdbcType=VARCHAR}, #{record.startDate,jdbcType=TIMESTAMP}, @@ -112,7 +119,15 @@ guacamole_connection_history.user_id IN ( SELECT user_id FROM guacamole_user - WHERE POSITION(#{term.term,jdbcType=VARCHAR} IN username) > 0 + WHERE + + + POSITION(#{term.term,jdbcType=VARCHAR} IN username) > 0 + + + POSITION(LOWER(#{term.term,jdbcType=VARCHAR}) IN LOWER(username)) > 0 + + ) OR guacamole_connection_history.connection_id IN ( @@ -200,7 +215,14 @@ FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - POSITION(#{term.term,jdbcType=VARCHAR} IN guacamole_entity.name) > 0 + + + POSITION(#{term.term,jdbcType=VARCHAR} IN guacamole_entity.name) > 0 + + + POSITION(LOWER(#{term.term,jdbcType=VARCHAR}) IN LOWER(guacamole_entity.name)) > 0 + + AND guacamole_entity.type = 'USER' ) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml index 58fba1430a..f4cfab502c 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml @@ -68,7 +68,15 @@ AND permission = #{type,jdbcType=VARCHAR} - AND affected_entity.name = #{identifier,jdbcType=VARCHAR} + AND + + + affected_entity.name = #{identifier,jdbcType=VARCHAR} + + + LOWER(affected_entity.name) = LOWER(#{identifier,jdbcType=VARCHAR}) + + AND affected_entity.type = 'USER' @@ -86,11 +94,23 @@ - AND affected_entity.name IN - + + affected_entity.name IN + - #{identifier,jdbcType=VARCHAR} - + #{identifier,jdbcType=VARCHAR} + + + + LOWER(affected_entity.name) IN + + LOWER(#{identifier,jdbcType=VARCHAR}) + + + AND permission IN @@ -108,13 +128,26 @@ JOIN guacamole_user affected_user ON guacamole_user_permission.affected_user_id = affected_user.user_id JOIN guacamole_entity affected_entity ON affected_user.entity_id = affected_entity.entity_id WHERE - (guacamole_user_permission.entity_id, permission, affected_entity.name) IN - - (#{permission.entityID,jdbcType=INTEGER}, - #{permission.type,jdbcType=VARCHAR}, - #{permission.objectIdentifier,jdbcType=VARCHAR}) - + + + (guacamole_user_permission.entity_id, permission, affected_entity.name) IN + + (#{permission.entityID,jdbcType=INTEGER}, + #{permission.type,jdbcType=VARCHAR}, + #{permission.objectIdentifier,jdbcType=VARCHAR}) + + + + AND (guacamole_user_permission.entity_id, permission, LOWER(affected_entity.name)) IN + + (#{permission.entityID,jdbcType=INTEGER}, + #{permission.type,jdbcType=VARCHAR}, + LOWER(#{permission.objectIdentifier,jdbcType=VARCHAR})) + + + AND affected_entity.type = 'USER' @@ -140,7 +173,14 @@ AS permissions JOIN guacamole_entity affected_entity ON - affected_entity.name = permissions.affected_name + + + affected_entity.name = permissions.affected_name + + + LOWER(affected_entity.name) = LOWER(permissions.affected_name) + + AND affected_entity.type = 'USER' JOIN guacamole_user affected_user ON affected_user.entity_id = affected_entity.entity_id diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml index f3772d7f91..30f6bc6764 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml @@ -43,7 +43,14 @@ JOIN guacamole_user ON guacamole_user_password_history.user_id = guacamole_user.user_id JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - guacamole_entity.name = #{username,jdbcType=VARCHAR} + + + guacamole_entity.name = #{username,jdbcType=VARCHAR} + + + LOWER(guacamole_entity.name) = LOWER(#{username,jdbcType=VARCHAR}) + + ORDER BY guacamole_user_password_history.password_date DESC LIMIT #{maxHistorySize} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml index 0dcfa2efcc..e6e613f4c9 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml @@ -130,10 +130,26 @@ FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id LEFT JOIN guacamole_user_history ON guacamole_user_history.user_id = guacamole_user.user_id - WHERE guacamole_entity.name IN + WHERE + + + guacamole_entity.name + + + LOWER(guacamole_entity.name) + + + IN - #{identifier,jdbcType=VARCHAR} + open="(" separator="," close=")"> + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER' GROUP BY guacamole_user.user_id, guacamole_entity.entity_id; @@ -145,10 +161,26 @@ FROM guacamole_user_attribute JOIN guacamole_user ON guacamole_user.user_id = guacamole_user_attribute.user_id JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id - WHERE guacamole_entity.name IN + WHERE + + + guacamole_entity.name + + + LOWER(guacamole_entity.name) + + + IN - #{identifier,jdbcType=VARCHAR} + open="(" separator="," close=")"> + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER'; @@ -180,10 +212,26 @@ FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id LEFT JOIN guacamole_user_history ON guacamole_user_history.user_id = guacamole_user.user_id - WHERE guacamole_entity.name IN + WHERE + + + guacamole_entity.name + + + LOWER(guacamole_entity.name) + + + IN - #{identifier,jdbcType=VARCHAR} + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER' AND guacamole_user.user_id IN ( @@ -201,10 +249,26 @@ FROM guacamole_user_attribute JOIN guacamole_user ON guacamole_user.user_id = guacamole_user_attribute.user_id JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id - WHERE guacamole_entity.name IN + WHERE + + + guacamole_entity.name + + + LOWER(guacamole_entity.name) + + + IN - #{identifier,jdbcType=VARCHAR} + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER' AND guacamole_user.user_id IN ( @@ -243,7 +307,14 @@ JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id LEFT JOIN guacamole_user_history ON guacamole_user_history.user_id = guacamole_user.user_id WHERE - guacamole_entity.name = #{username,jdbcType=VARCHAR} + + + guacamole_entity.name = #{username,jdbcType=VARCHAR} + + + LOWER(guacamole_entity.name) = LOWER(#{username,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER' GROUP BY guacamole_user.user_id, guacamole_entity.entity_id; @@ -255,7 +326,14 @@ JOIN guacamole_user ON guacamole_user.user_id = guacamole_user_attribute.user_id JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - guacamole_entity.name = #{username,jdbcType=VARCHAR} + + + guacamole_entity.name = #{username,jdbcType=VARCHAR} + + + LOWER(guacamole_entity.name) = LOWER(#{username,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER' @@ -264,7 +342,14 @@ DELETE FROM guacamole_entity WHERE - name = #{identifier,jdbcType=VARCHAR} + + + name = #{identifier,jdbcType=VARCHAR} + + + LOWER(name) = LOWER(#{identifier,jdbcType=VARCHAR}) + + AND type = 'USER' diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserRecordMapper.xml index 70742df4c1..69d6a01623 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserRecordMapper.xml @@ -49,7 +49,14 @@ (SELECT user_id FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - guacamole_entity.name = #{record.username,jdbcType=VARCHAR} + + + guacamole_entity.name = #{record.username,jdbcType=VARCHAR} + + + LOWER(guacamole_entity.name) = LOWER(#{record.username,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER'), #{record.username,jdbcType=VARCHAR}, #{record.startDate,jdbcType=TIMESTAMP}, @@ -81,7 +88,14 @@ - guacamole_user_history.username = #{identifier,jdbcType=VARCHAR} + + + guacamole_user_history.username = #{identifier,jdbcType=VARCHAR} + + + LOWER(guacamole_user_history.username) = LOWER(#{identifier,jdbcType=VARCHAR}) + + @@ -92,7 +106,14 @@ FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - POSITION(#{term.term,jdbcType=VARCHAR} IN guacamole_entity.name) > 0 + + + POSITION(#{term.term,jdbcType=VARCHAR} IN guacamole_entity.name) > 0 + + + POSITION(LOWER(#{term.term,jdbcType=VARCHAR}) IN LOWER(guacamole_entity.name)) > 0 + + AND guacamole_entity.type = 'USER'), ) @@ -146,7 +167,15 @@ ) - AND guacamole_entity.name = #{identifier,jdbcType=VARCHAR} + AND + + + guacamole_entity.name = #{identifier,jdbcType=VARCHAR} + + + LOWER(guacamole_entity.name) = LOWER(#{identifier,jdbcType=VARCHAR}) + + @@ -157,7 +186,14 @@ FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - POSITION(#{term.term,jdbcType=VARCHAR} IN guacamole_entity.name) > 0 + + + POSITION(#{term.term,jdbcType=VARCHAR} IN guacamole_entity.name) > 0 + + + POSITION(LOWER(#{term.term,jdbcType=VARCHAR}) IN LOWER(guacamole_entity.name)) > 0 + + AND guacamole_entity.type = 'USER' ) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.xml index 609d907f5a..c5f7030b92 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.xml @@ -58,10 +58,26 @@ WHERE user_group_id = #{parent.objectID,jdbcType=INTEGER} AND guacamole_entity.type = 'USER' - AND guacamole_entity.name IN + AND + + + guacamole_entity.name + + + LOWER(guacamole_entity.name) + + + IN - #{identifier,jdbcType=VARCHAR} + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + @@ -76,10 +92,25 @@ guacamole_entity.entity_id FROM guacamole_entity WHERE - guacamole_entity.name IN + + + guacamole_entity.name + + + LOWER(guacamole_entity.name) + + + IN - #{identifier} + + + #{identifier} + + + LOWER(#{identifier}) + + AND guacamole_entity.type = 'USER' AND guacamole_entity.entity_id NOT IN ( diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLEnvironment.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLEnvironment.java index a0e166aa62..d620521fb5 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLEnvironment.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLEnvironment.java @@ -398,5 +398,20 @@ public boolean enforceAccessWindowsForActiveSessions() throws GuacamoleException PostgreSQLGuacamoleProperties.POSTGRESQL_ENFORCE_ACCESS_WINDOWS_FOR_ACTIVE_SESSIONS, true); } + + @Override + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + + // By default, PostgreSQL does perform case-sensitive string comparisons. + // Even though usernames are generally not case-sensitive across + // most authenticaiton systems, we've elected to maintain case- + // sensitivity in this module in order to avoid surprising anyone who + // may be relying upon it. + return getProperty( + PostgreSQLGuacamoleProperties.POSTGRESQL_CASE_SENSITIVE_USERNAMES, + super.getCaseSensitiveUsernames() + ); + + } } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLGuacamoleProperties.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLGuacamoleProperties.java index 707b83ca19..74db342f26 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLGuacamoleProperties.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLGuacamoleProperties.java @@ -314,5 +314,17 @@ private PostgreSQLGuacamoleProperties() {} public String getName() { return "postgresql-batch-size"; } }; + + /** + * A property used to configure whether or not usernames within the Postgres + * JDBC module should be treated as case-sensitive. + */ + public static final BooleanGuacamoleProperty POSTGRESQL_CASE_SENSITIVE_USERNAMES = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "postgresql-case-sensitive-usernames"; } + + }; } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml index 9857193b44..0889485f83 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml @@ -61,7 +61,14 @@ (SELECT user_id FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - guacamole_entity.name = #{record.username,jdbcType=VARCHAR} + + + guacamole_entity.name = #{record.username,jdbcType=VARCHAR} + + + LOWER(guacamole_entity.name) = LOWER(#{record.username,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER'::guacamole_entity_type), #{record.username,jdbcType=VARCHAR}, #{record.startDate,jdbcType=TIMESTAMP}, @@ -110,7 +117,15 @@ guacamole_connection_history.user_id IN ( SELECT user_id FROM guacamole_user - WHERE POSITION(#{term.term,jdbcType=VARCHAR} IN username) > 0 + WHERE + + + POSITION(#{term.term,jdbcType=VARCHAR} IN username) > 0 + + + POSITION(LOWER(#{term.term,jdbcType=VARCHAR}) IN LOWER(username)) > 0 + + ) OR guacamole_connection_history.connection_id IN ( @@ -198,7 +213,14 @@ FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - POSITION(#{term.term,jdbcType=VARCHAR} IN guacamole_entity.name) > 0 + + + POSITION(#{term.term,jdbcType=VARCHAR} IN guacamole_entity.name) > 0 + + + POSITION(LOWER(#{term.term,jdbcType=VARCHAR}) IN LOWER(guacamole_entity.name)) > 0 + + AND guacamole_entity.type = 'USER'::guacamole_entity_type ) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml index a4b5f1b7c4..f96cc16a58 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml @@ -28,7 +28,7 @@ - + @@ -68,7 +68,16 @@ AND permission = #{type,jdbcType=VARCHAR}::guacamole_object_permission_type - AND affected_entity.name = #{identifier,jdbcType=VARCHAR} + AND + + + affected_entity.name = #{identifier,jdbcType=VARCHAR} + + + LOWER(affected_entity.name) = LOWER(#{identifier,jdbcType=VARCHAR}) + + + AND affected_entity.type = 'USER'::guacamole_entity_type @@ -86,11 +95,23 @@ - AND affected_entity.name IN - + + affected_entity.name IN + - #{identifier,jdbcType=VARCHAR} - + #{identifier,jdbcType=VARCHAR} + + + + LOWER(affected_entity.name) IN + + LOWER(#{identifier,jdbcType=VARCHAR}) + + + AND permission IN @@ -108,13 +129,26 @@ WHERE guacamole_user_permission.affected_user_id = affected_user.user_id AND affected_user.entity_id = affected_entity.entity_id - AND (guacamole_user_permission.entity_id, permission, affected_entity.name) IN - - (#{permission.entityID,jdbcType=INTEGER}, - #{permission.type,jdbcType=VARCHAR}::guacamole_object_permission_type, - #{permission.objectIdentifier,jdbcType=INTEGER}) - + + + AND (guacamole_user_permission.entity_id, permission, affected_entity.name) IN + + (#{permission.entityID,jdbcType=INTEGER}, + #{permission.type,jdbcType=VARCHAR}::guacamole_object_permission_type, + #{permission.objectIdentifier,jdbcType=VARCHAR}) + + + + AND (guacamole_user_permission.entity_id, permission, LOWER(affected_entity.name)) IN + + (#{permission.entityID,jdbcType=INTEGER}, + #{permission.type,jdbcType=VARCHAR}::guacamole_object_permission_type, + LOWER(#{permission.objectIdentifier,jdbcType=VARCHAR})) + + + AND affected_entity.type = 'USER'::guacamole_entity_type @@ -140,7 +174,14 @@ AS permissions JOIN guacamole_entity affected_entity ON - affected_entity.name = permissions.affected_name + + + affected_entity.name = permissions.affected_name + + + LOWER(affected_entity.name) = LOWER(permissions.affected_name) + + AND affected_entity.type = 'USER'::guacamole_entity_type JOIN guacamole_user affected_user ON affected_user.entity_id = affected_entity.entity_id WHERE (permissions.entity_id, permissions.permission, affected_user.user_id) NOT IN ( diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml index e9c857afd4..0e03a36169 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml @@ -43,7 +43,14 @@ JOIN guacamole_user ON guacamole_user_password_history.user_id = guacamole_user.user_id JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - guacamole_entity.name = #{username,jdbcType=VARCHAR} + + + guacamole_entity.name = #{username,jdbcType=VARCHAR} + + + LOWER(guacamole_entity.name) = LOWER(#{username,jdbcType=VARCHAR}) + + ORDER BY guacamole_user_password_history.password_date DESC LIMIT #{maxHistorySize} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml index 94d607e337..d70a3cb9e1 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml @@ -130,10 +130,26 @@ FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id LEFT JOIN guacamole_user_history ON guacamole_user_history.user_id = guacamole_user.user_id - WHERE guacamole_entity.name IN + WHERE + + + guacamole_entity.name + + + LOWER(guacamole_entity.name) + + + IN - #{identifier,jdbcType=VARCHAR} + open="(" separator="," close=")"> + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER'::guacamole_entity_type GROUP BY guacamole_user.user_id, guacamole_entity.entity_id; @@ -145,10 +161,26 @@ FROM guacamole_user_attribute JOIN guacamole_user ON guacamole_user.user_id = guacamole_user_attribute.user_id JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id - WHERE guacamole_entity.name IN + WHERE + + + guacamole_entity.name + + + LOWER(guacamole_entity.name) + + + IN - #{identifier,jdbcType=VARCHAR} + open="(" separator="," close=")"> + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER'::guacamole_entity_type; @@ -180,10 +212,26 @@ FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id LEFT JOIN guacamole_user_history ON guacamole_user_history.user_id = guacamole_user.user_id - WHERE guacamole_entity.name IN + WHERE + + + guacamole_entity.name + + + LOWER(guacamole_entity.name) + + + IN - #{identifier,jdbcType=VARCHAR} + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER'::guacamole_entity_type AND guacamole_user.user_id IN ( @@ -201,10 +249,26 @@ FROM guacamole_user_attribute JOIN guacamole_user ON guacamole_user.user_id = guacamole_user_attribute.user_id JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id - WHERE guacamole_entity.name IN + WHERE + + + guacamole_entity.name + + + LOWER(guacamole_entity.name) + + + IN - #{identifier,jdbcType=VARCHAR} + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER'::guacamole_entity_type AND guacamole_user.user_id IN ( @@ -243,7 +307,14 @@ JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id LEFT JOIN guacamole_user_history ON guacamole_user_history.user_id = guacamole_user.user_id WHERE - guacamole_entity.name = #{username,jdbcType=VARCHAR} + + + guacamole_entity.name = #{username,jdbcType=VARCHAR} + + + LOWER(guacamole_entity.name) = LOWER(#{username,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER'::guacamole_entity_type GROUP BY guacamole_user.user_id, guacamole_entity.entity_id; @@ -255,16 +326,29 @@ JOIN guacamole_user ON guacamole_user.user_id = guacamole_user_attribute.user_id JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - guacamole_entity.name = #{username,jdbcType=VARCHAR} + + + guacamole_entity.name = #{username,jdbcType=VARCHAR} + + + LOWER(guacamole_entity.name) = LOWER(#{username,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER'::guacamole_entity_type - DELETE FROM guacamole_entity WHERE - name = #{identifier,jdbcType=VARCHAR} + + + name = #{identifier,jdbcType=VARCHAR} + + + LOWER(name) = LOWER(#{identifier,jdbcType=VARCHAR}) + + AND type = 'USER'::guacamole_entity_type @@ -326,7 +410,7 @@ email_address = #{object.emailAddress,jdbcType=VARCHAR}, organization = #{object.organization,jdbcType=VARCHAR}, organizational_role = #{object.organizationalRole,jdbcType=VARCHAR} - WHERE user_id = #{object.objectID,jdbcType=VARCHAR} + WHERE user_id = #{object.objectID,jdbcType=INTEGER} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserRecordMapper.xml index a372087ebd..e13a2d2a40 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserRecordMapper.xml @@ -49,7 +49,14 @@ (SELECT user_id FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - guacamole_entity.name = #{record.username,jdbcType=VARCHAR} + + + guacamole_entity.name = #{record.username,jdbcType=VARCHAR} + + + LOWER(guacamole_entity.name) = LOWER(#{record.username,jdbcType=VARCHAR}) + + AND guacamole_entity.type = 'USER'::guacamole_entity_type), #{record.username,jdbcType=VARCHAR}, #{record.startDate,jdbcType=TIMESTAMP}, @@ -81,7 +88,14 @@ - guacamole_user_history.username = #{identifier,jdbcType=VARCHAR} + + + guacamole_user_history.username = #{identifier,jdbcType=VARCHAR} + + + LOWER(guacamole_user_history.username) = LOWER(#{identifier,jdbcType=VARCHAR}) + + @@ -92,7 +106,14 @@ FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - POSITION(#{term.term,jdbcType=VARCHAR} IN guacamole_entity.name) > 0 + + + POSITION(#{term.term,jdbcType=VARCHAR} IN guacamole_entity.name) > 0 + + + POSITION(LOWER(#{term.term,jdbcType=VARCHAR}) IN LOWER(guacamole_entity.name)) > 0 + + AND guacamole_entity.type = 'USER'::guacamole_entity_type), ) @@ -146,7 +167,15 @@ ) - AND guacamole_entity.name = #{identifier,jdbcType=VARCHAR} + AND + + + guacamole_entity.name = #{identifier,jdbcType=VARCHAR} + + + LOWER(guacamole_entity.name) = LOWER(#{identifier,jdbcType=VARCHAR}) + + @@ -157,7 +186,14 @@ FROM guacamole_user JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id WHERE - POSITION(#{term.term,jdbcType=VARCHAR} IN guacamole_entity.name) > 0 + + + POSITION(#{term.term,jdbcType=VARCHAR} IN guacamole_entity.name) > 0 + + + POSITION(LOWER(#{term.term,jdbcType=VARCHAR}) IN LOWER(guacamole_entity.name)) > 0 + + AND guacamole_entity.type = 'USER'::guacamole_entity_type ) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.xml index a3ad5a78d1..5e74d4b8aa 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.xml @@ -58,11 +58,27 @@ user_group_id = #{parent.objectID,jdbcType=INTEGER} AND guacamole_entity.entity_id = member_entity_id AND guacamole_entity.type = 'USER'::guacamole_entity_type - AND guacamole_entity.name IN - - #{identifier,jdbcType=VARCHAR} - + AND + + + guacamole_entity.name + + + LOWER(guacamole_entity.name) + + + IN + + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + + @@ -76,11 +92,26 @@ guacamole_entity.entity_id FROM guacamole_entity WHERE - guacamole_entity.name IN - - #{identifier} - + + + guacamole_entity.name + + + LOWER(guacamole_entity.name) + + + IN + + + + #{identifier} + + + LOWER(#{identifier}) + + + AND guacamole_entity.type = 'USER'::guacamole_entity_type AND guacamole_entity.entity_id NOT IN ( SELECT guacamole_user_group_member.member_entity_id diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerEnvironment.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerEnvironment.java index 498479d795..9e2d540bbb 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerEnvironment.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerEnvironment.java @@ -328,5 +328,28 @@ public boolean trustAllServerCertificates() throws GuacamoleException { SQLServerGuacamoleProperties.SQLSERVER_TRUST_ALL_SERVER_CERTIFICATES, false); } + + @Override + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + + // Get the configured or default value of the property. + boolean caseSensitiveUsernames = getProperty( + SQLServerGuacamoleProperties.SQLSERVER_CASE_SENSITIVE_USERNAMES, + super.getCaseSensitiveUsernames() + ); + + // If property has been set to true, warn the admin. + if (caseSensitiveUsernames) + logger.warn("You have configured this extension for case-sensitive " + + "username comparisons, however, the default collations " + + "for SQL Server databases do not support case-sensitive " + + "string comparisons. Further database configuration may " + + "be required in order for case-sensitive username " + + "comparisons to function correctly."); + + // Return as configured + return caseSensitiveUsernames; + + } } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerGuacamoleProperties.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerGuacamoleProperties.java index c4df81381f..8a4e1ee28f 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerGuacamoleProperties.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerGuacamoleProperties.java @@ -257,5 +257,20 @@ private SQLServerGuacamoleProperties() {} public String getName() { return "sqlserver-trust-all-server-certificates"; } }; + + /** + * A property used to configure whether or not usernames within the SQL + * Server JDBC module should be treated as case-sensitive. While Guacamole + * will treat usernames as case-sensitive by default, SQL Server's default + * database collations do not do case-sensitive string comparisons, so in + * many cases this will effectively result in case-insensitive usernames. + */ + public static final BooleanGuacamoleProperty SQLSERVER_CASE_SENSITIVE_USERNAMES = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-case-sensitive-usernames" ; } + + }; } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml index 81f31d428b..0a67bf31cf 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml @@ -68,7 +68,14 @@ (SELECT user_id FROM [guacamole_user] JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id WHERE - [guacamole_entity].name = #{record.username,jdbcType=VARCHAR} + + + [guacamole_entity].name = #{record.username,jdbcType=VARCHAR} + + + LOWER([guacamole_entity].name) = LOWER(#{record.username,jdbcType=VARCHAR}) + + AND [guacamole_entity].type = 'USER'), #{record.username,jdbcType=VARCHAR}, #{record.startDate,jdbcType=TIMESTAMP}, @@ -110,7 +117,15 @@ [guacamole_connection_history].user_id IN ( SELECT user_id FROM [guacamole_user] - WHERE CHARINDEX(#{term.term,jdbcType=VARCHAR}, username) > 0 + WHERE + + + CHARINDEX(#{term.term,jdbcType=VARCHAR} IN username) > 0 + + + CHARINDEX(LOWER(#{term.term,jdbcType=VARCHAR}) IN LOWER(username)) > 0 + + ) OR [guacamole_connection_history].connection_id IN ( @@ -196,7 +211,14 @@ FROM [guacamole_user] JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id WHERE - CHARINDEX(#{term.term,jdbcType=VARCHAR}, [guacamole_entity].name) > 0 + + + CHARINDEX(#{term.term,jdbcType=VARCHAR} IN [guacamole_entity].name) > 0 + + + CHARINDEX(LOWER(#{term.term,jdbcType=VARCHAR}) IN LOWER(guacamole_entity.name)) > 0 + + AND [guacamole_entity].type = 'USER' ) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml index 5c088045eb..a2f9ff8adf 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.xml @@ -68,7 +68,15 @@ AND permission = #{type,jdbcType=VARCHAR} - AND affected_entity.name = #{identifier,jdbcType=VARCHAR} + AND + + + affected_entity.name = #{identifier,jdbcType=VARCHAR} + + + LOWER(affected_entity.name) = LOWER(#{identifier,jdbcType=VARCHAR}) + + AND affected_entity.type = 'USER' @@ -86,11 +94,23 @@ - AND affected_entity.name IN - + + affected_entity.name IN + - #{identifier,jdbcType=VARCHAR} - + #{identifier,jdbcType=VARCHAR} + + + + LOWER(affected_entity.name) IN + + LOWER(#{identifier,jdbcType=VARCHAR}) + + + AND permission IN @@ -112,8 +132,16 @@ open="(" separator=" OR " close=")"> ([guacamole_user_permission].entity_id = #{permission.entityID,jdbcType=INTEGER} AND permission = #{permission.type,jdbcType=VARCHAR} AND - affected_entity.name = #{permission.objectIdentifier,jdbcType=VARCHAR} AND - affected_entity.type = 'USER') + + + affected_entity.name = #{permission.objectIdentifier,jdbcType=VARCHAR} + + + LOWER(affected_entity.name = LOWER(#{permission.objectIdentifier,jdbcType=VARCHAR}) + + + AND + affected_entity.type = 'USER') @@ -139,7 +167,14 @@ AS permissions JOIN [guacamole_entity] affected_entity ON - affected_entity.name = permissions.affected_name + + + affected_entity.name = permissions.affected_name + + + LOWER(affected_entity.name) = LOWER(permissions.affected_name) + + AND affected_entity.type = 'USER' JOIN [guacamole_user] affected_user ON affected_user.entity_id = affected_entity.entity_id WHERE NOT EXISTS (SELECT 1 FROM [guacamole_user_permission] diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml index 21fd986b18..da453d44d4 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/PasswordRecordMapper.xml @@ -43,7 +43,14 @@ JOIN [guacamole_user] ON [guacamole_user_password_history].user_id = [guacamole_user].user_id JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id WHERE - [guacamole_entity].name = #{username,jdbcType=VARCHAR} + + + [guacamole_entity].name = #{username,jdbcType=VARCHAR} + + + LOWER([guacamole_entity].name) = LOWER(#{username,jdbcType=VARCHAR}) + + ORDER BY [guacamole_user_password_history].password_date DESC diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml index 80344968bb..a4530335ad 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserMapper.xml @@ -133,10 +133,26 @@ ) AS last_active FROM [guacamole_user] JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id - WHERE [guacamole_entity].name IN + WHERE + + + [guacamole_entity].name + + + LOWER([guacamole_entity].name) + + + IN - #{identifier,jdbcType=VARCHAR} + open="(" separator="," close=")"> + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + AND [guacamole_entity].type = 'USER'; @@ -147,10 +163,26 @@ FROM [guacamole_user_attribute] JOIN [guacamole_user] ON [guacamole_user].user_id = [guacamole_user_attribute].user_id JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id - WHERE [guacamole_entity].name IN + WHERE + + + [guacamole_entity].name + + + LOWER([guacamole_entity].name) + + + IN - #{identifier,jdbcType=VARCHAR} + open="(" separator="," close=")"> + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + AND [guacamole_entity].type = 'USER'; @@ -185,10 +217,26 @@ ) AS last_active FROM [guacamole_user] JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id - WHERE [guacamole_entity].name IN + WHERE + + + [guacamole_entity].name + + + LOWER([guacamole_entity].name) + + + IN - #{identifier,jdbcType=VARCHAR} + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + AND [guacamole_entity].type = 'USER' AND [guacamole_user].user_id IN ( @@ -205,10 +253,26 @@ FROM [guacamole_user_attribute] JOIN [guacamole_user] ON [guacamole_user].user_id = [guacamole_user_attribute].user_id JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id - WHERE [guacamole_entity].name IN + WHERE + + + [guacamole_entity].name + + + LOWER([guacamole_entity].name) + + + IN - #{identifier,jdbcType=VARCHAR} + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + AND [guacamole_entity].type = 'USER' AND [guacamole_user].user_id IN ( @@ -250,7 +314,14 @@ FROM [guacamole_user] JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id WHERE - [guacamole_entity].name = #{username,jdbcType=VARCHAR} + + + [guacamole_entity].name = #{username,jdbcType=VARCHAR} + + + LOWER([guacamole_entity].name) = LOWER(#{username,jdbcType=VARCHAR}) + + AND [guacamole_entity].type = 'USER'; SELECT @@ -261,7 +332,14 @@ JOIN [guacamole_user] ON [guacamole_user].user_id = [guacamole_user_attribute].user_id JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id WHERE - [guacamole_entity].name = #{username,jdbcType=VARCHAR} + + + [guacamole_entity].name = #{username,jdbcType=VARCHAR} + + + LOWER([guacamole_entity].name) = LOWER(#{username,jdbcType=VARCHAR}) + + AND [guacamole_entity].type = 'USER' @@ -270,7 +348,14 @@ DELETE FROM [guacamole_entity] WHERE - name = #{identifier,jdbcType=VARCHAR} + + + name = #{identifier,jdbcType=VARCHAR} + + + LOWER(name) = LOWER(#{identifier,jdbcType=VARCHAR}) + + AND type = 'USER' @@ -332,7 +417,7 @@ email_address = #{object.emailAddress,jdbcType=VARCHAR}, organization = #{object.organization,jdbcType=VARCHAR}, organizational_role = #{object.organizationalRole,jdbcType=VARCHAR} - WHERE user_id = #{object.objectID,jdbcType=VARCHAR} + WHERE user_id = #{object.objectID,jdbcType=INTEGER} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserRecordMapper.xml index 4fb64491ad..5bcc3b95f6 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/user/UserRecordMapper.xml @@ -49,8 +49,15 @@ (SELECT user_id FROM [guacamole_user] JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id WHERE - [guacamole_entity].name = #{record.username,jdbcType=VARCHAR} - AND [guacamole_entity].type = 'USER'), + + + [guacamole_entity].name = #{record.username,jdbcType=VARCHAR} + + + LOWER([guacamole_entity].name) = LOWER(#{record.username,jdbcType=VARCHAR}) + + + AND [guacamole_entity].type = 'USER'), #{record.username,jdbcType=VARCHAR}, #{record.startDate,jdbcType=TIMESTAMP}, #{record.endDate,jdbcType=TIMESTAMP} @@ -81,7 +88,14 @@ - [guacamole_user_history].username = #{identifier,jdbcType=VARCHAR} + + + [guacamole_user_history].username = #{identifier,jdbcType=VARCHAR} + + + LOWER([guacamole_user_history].username) = LOWER(#{identifier,jdbcType=VARCHAR}) + + @@ -92,7 +106,14 @@ FROM [guacamole_user] JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id WHERE - CHARINDEX(#{term.term,jdbcType=VARCHAR}, [guacamole_entity].name) > 0 + + + CHARINDEX(#{term.term,jdbcType=VARCHAR} IN [guacamole_entity].name) > 0 + + + CHARINDEX(LOWER(#{term.term,jdbcType=VARCHAR}) IN LOWER([guacamole_entity].name)) > 0 + + AND [guacamole_entity].type = 'USER'), ) @@ -144,7 +165,15 @@ ) - AND [guacamole_entity].name = #{identifier,jdbcType=VARCHAR} + AND + + + [guacamole_entity].name = #{identifier,jdbcType=VARCHAR} + + + LOWER([guacamole_entity].name) = LOWER(#{identifier,jdbcType=VARCHAR}) + + @@ -155,7 +184,14 @@ FROM [guacamole_user] JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id WHERE - CHARINDEX(#{term.term,jdbcType=VARCHAR}, [guacamole_entity].name) > 0 + + + CHARINDEX(#{term.term,jdbcType=VARCHAR} IN [guacamole_entity].name) > 0 + + + CHARINDEX(LOWER(#{term.term,jdbcType=VARCHAR}) IN LOWER([guacamole_entity].name)) > 0 + + AND [guacamole_entity].type = 'USER' ) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.xml index b0682b0411..70fe520da1 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/resources/org/apache/guacamole/auth/jdbc/usergroup/UserGroupMemberUserMapper.xml @@ -58,11 +58,27 @@ WHERE user_group_id = #{parent.objectID,jdbcType=INTEGER} AND [guacamole_entity].type = 'USER' - AND [guacamole_entity].name IN - - #{identifier,jdbcType=VARCHAR} - + AND + + + [guacamole_entity].name + + + LOWER([guacamole_entity].name) + + + IN + + + + #{identifier,jdbcType=VARCHAR} + + + LOWER(#{identifier,jdbcType=VARCHAR}) + + + @@ -76,11 +92,26 @@ [guacamole_entity].entity_id FROM [guacamole_entity] WHERE - [guacamole_entity].name IN - - #{identifier} - + + + [guacamole_entity].name + + + LOWER([guacamole_entity].name) + + + IN + + + + #{identifier} + + + LOWER(#{identifier}) + + + AND [guacamole_entity].type = 'USER' AND [guacamole_entity].entity_id NOT IN ( SELECT [guacamole_user_group_member].member_entity_id diff --git a/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/ConfigurationService.java b/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/ConfigurationService.java index 2705e61ed3..6fb2cd403a 100644 --- a/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/ConfigurationService.java +++ b/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/ConfigurationService.java @@ -24,6 +24,7 @@ import java.util.Collections; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.BooleanGuacamoleProperty; import org.apache.guacamole.properties.ByteArrayProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; @@ -39,6 +40,20 @@ public class ConfigurationService { @Inject private Environment environment; + /** + * A property used to configure whether or not usernames within the JSON + * module should be treated as case-sensitive. + */ + private static final BooleanGuacamoleProperty JSON_CASE_SENSITIVE_USERNAMES = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { + return "json-case-sensitive-usernames"; + } + + }; + /** * The encryption key to use for all decryption and signature verification. */ @@ -64,6 +79,25 @@ public String getName() { } }; + + /** + * Returns true if the usernames provided to the JSON authentication + * module should be treated as case-sensitive, or false if usernames + * should be treated as case-insensitive. The default will be taken from + * the global Guacamole configuration, which defaults to true, but + * can be overridden for this extension. + * + * @return + * true if usernames should be treated as case-sensitive, otherwise + * false. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + return environment.getProperty(JSON_CASE_SENSITIVE_USERNAMES, + environment.getCaseSensitiveUsernames()); + } /** * Returns the symmetric key which will be used to encrypt and sign all diff --git a/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/user/AuthenticatedUser.java b/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/user/AuthenticatedUser.java index 562d6a4268..0eeab697a2 100644 --- a/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/user/AuthenticatedUser.java +++ b/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/user/AuthenticatedUser.java @@ -20,9 +20,13 @@ package org.apache.guacamole.auth.json.user; import com.google.inject.Inject; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.json.ConfigurationService; import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An implementation of AuthenticatedUser specific to the @@ -31,12 +35,24 @@ */ public class AuthenticatedUser extends AbstractAuthenticatedUser { + /** + * Logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticatedUser.class); + /** * Reference to the authentication provider associated with this * authenticated user. */ @Inject private AuthenticationProvider authProvider; + + /** + * Reference to the configuration service associated with this + * authentication provider. + */ + @Inject + private ConfigurationService confService; /** * The credentials provided when this user was authenticated. @@ -66,6 +82,19 @@ public void init(Credentials credentials, UserData userData) { this.userData = userData; setIdentifier(userData.getUsername()); } + + @Override + public boolean isCaseSensitive() { + try { + return confService.getCaseSensitiveUsernames(); + } + catch (GuacamoleException e) { + LOGGER.error("Error when attempting to get the JSON configuration: {}. " + + "Username comparisons will be case-sensitive.", e.getMessage()); + LOGGER.debug("Exception caught while retrieving JSON configuration.", e); + return true; + } + } @Override public AuthenticationProvider getAuthenticationProvider() { diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConnectedLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConnectedLDAPConfiguration.java index c41114c027..3f555ce5d2 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConnectedLDAPConfiguration.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConnectedLDAPConfiguration.java @@ -223,5 +223,10 @@ public String getMemberAttribute() throws GuacamoleException { public MemberAttributeType getMemberAttributeType() throws GuacamoleException { return config.getMemberAttributeType(); } + + @Override + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + return config.getCaseSensitiveUsernames(); + } } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DefaultLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DefaultLDAPConfiguration.java index cb2db9b6b7..63cd64ab1a 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DefaultLDAPConfiguration.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DefaultLDAPConfiguration.java @@ -19,6 +19,7 @@ package org.apache.guacamole.auth.ldap.conf; +import com.google.inject.Inject; import java.util.Collections; import java.util.List; import org.apache.directory.api.ldap.model.filter.ExprNode; @@ -27,6 +28,7 @@ import org.apache.directory.api.ldap.model.name.Dn; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.environment.Environment; /** * LDAPConfiguration implementation that returns the default values for all @@ -35,6 +37,12 @@ */ public class DefaultLDAPConfiguration implements LDAPConfiguration { + /** + * The environment in which Guacamole is running. + */ + @Inject + private Environment environment; + @Override public String appliesTo(String username) { return null; @@ -150,5 +158,10 @@ public MemberAttributeType getMemberAttributeType() throws GuacamoleException { return MemberAttributeType.DN; } + + @Override + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + return environment.getCaseSensitiveUsernames(); + } } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EnvironmentLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EnvironmentLDAPConfiguration.java index 5ffeb203b8..44a9464173 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EnvironmentLDAPConfiguration.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EnvironmentLDAPConfiguration.java @@ -233,5 +233,19 @@ public MemberAttributeType getMemberAttributeType() DEFAULT.getMemberAttributeType() ); } + + @Override + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + + // Most LDAP directories do not factor in case when comparing usernames, + // however, in order to avoid surprising anyone who may rely on this + // behavior in Guacamole, this is currently defaulted the overall + // Guacamole configuration (default of true), but can be over-ridden + // for the LDAP extension specifically, if desired. + return environment.getProperty( + LDAPGuacamoleProperties.LDAP_CASE_SENSITIVE_USERNAMES, + environment.getCaseSensitiveUsernames() + ); + } } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java index bddccd871f..bb9b474ee5 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java @@ -203,6 +203,13 @@ public class JacksonLDAPConfiguration implements LDAPConfiguration { */ @JsonProperty("member-attribute-type") private String memberAttributeType; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USERNAMES_CASE_SENSITIVE}. + * If not set within the YAML, this will currently default to true. + */ + @JsonProperty("case-sensitive-usernames") + private String caseSensitiveUsernames; /** * The default configuration options for all parameters. @@ -439,5 +446,11 @@ public MemberAttributeType getMemberAttributeType() throws GuacamoleException { return withDefault(LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE_TYPE, memberAttributeType, defaultConfig::getMemberAttributeType); } + + @Override + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + return withDefault(LDAPGuacamoleProperties.LDAP_CASE_SENSITIVE_USERNAMES, + caseSensitiveUsernames, defaultConfig::getCaseSensitiveUsernames); + } } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPConfiguration.java index e57049b26f..31a8475662 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPConfiguration.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPConfiguration.java @@ -20,7 +20,6 @@ package org.apache.guacamole.auth.ldap.conf; import java.util.Collection; -import java.util.List; import org.apache.directory.api.ldap.model.filter.ExprNode; import org.apache.directory.api.ldap.model.message.AliasDerefMode; import org.apache.directory.api.ldap.model.name.Dn; @@ -334,5 +333,21 @@ public interface LDAPConfiguration { * retrieved. */ MemberAttributeType getMemberAttributeType() throws GuacamoleException; + + /** + * Returns true if the usernames provided to the LDAP authentication + * module should be treated as case-sensitive, or false if usernames + * should be treated as case-insensitive. The default is true, usernames + * will be case-sensitive in keeping with the past behavior of Guacamole + * prior to the addition of this option. + * + * @return + * true if usernames should be treated as case-sensitive, otherwise + * false. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + boolean getCaseSensitiveUsernames() throws GuacamoleException; } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java index 7349356b94..072128ecfe 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java @@ -306,5 +306,17 @@ private LDAPGuacamoleProperties() {} public String getName() { return "ldap-member-attribute-type"; } }; + + /** + * A property used to configure whether or not usernames within the LDAP + * module should be treated as case-sensitive. + */ + public static final BooleanGuacamoleProperty LDAP_CASE_SENSITIVE_USERNAMES = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "ldap-case-sensitive-usernames"; } + + }; } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java index 2abe4aef09..f934afd575 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java @@ -24,10 +24,13 @@ import java.util.Map; import java.util.Set; import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.ldap.ConnectedLDAPConfiguration; import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An LDAP-specific implementation of AuthenticatedUser, associating a @@ -35,6 +38,11 @@ */ public class LDAPAuthenticatedUser extends AbstractAuthenticatedUser { + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(LDAPAuthenticatedUser.class); + /** * Reference to the authentication provider associated with this * authenticated user. @@ -135,6 +143,23 @@ public ConnectedLDAPConfiguration getLDAPConfiguration() { return config; } + @Override + public boolean isCaseSensitive() { + try { + return config.getCaseSensitiveUsernames(); + } + catch (GuacamoleException e) { + // LDAP authentication is almost universally case-insensitive, + // however, we're maintaining case-sensitivity within Guacamole + // at the moment in order to avoid surprising anyone with this change. + // Case-sensitivity can be disabled as a configuration option. + LOGGER.error("Error retrieving configuration for username case-sensitivity: {}. " + + "Username comparisons will be done case-sensitively.", e.getMessage()); + LOGGER.debug("Caught exception when retrieving case-sensitivity configuration.", e); + return true; + } + } + @Override public AuthenticationProvider getAuthenticationProvider() { return authProvider; diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/ConfigurationService.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/ConfigurationService.java index 78738ed537..614464142c 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/ConfigurationService.java @@ -362,5 +362,26 @@ public InetAddress getRadiusNasIp() throws GuacamoleException { throw new GuacamoleServerException("Unknown host specified for NAS IP.", e); } } + + /** + * Returns true if the usernames provided to the RADIUS authentication + * module should be treated as case-sensitive, or false if usernames + * should be treated as case-insensitive. The default value is read from + * Guacamole's global configuration, which defaults to true, but can be + * overridden for the RADIUS extension, if desired. + * + * @return + * true if usernames should be treated as case-sensitive, otherwise + * false. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + return environment.getProperty( + RadiusGuacamoleProperties.RADIUS_CASE_SENSITIVE_USERNAMES, + environment.getCaseSensitiveUsernames() + ); + } } diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusGuacamoleProperties.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusGuacamoleProperties.java index 06e186f238..459dc5859f 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusGuacamoleProperties.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusGuacamoleProperties.java @@ -204,6 +204,18 @@ private RadiusGuacamoleProperties() {} public String getName() { return "radius-nas-ip"; } }; + + /** + * A property used to configure whether or not usernames within the RADIUS + * module should be treated as case-sensitive. + */ + public static final BooleanGuacamoleProperty RADIUS_CASE_SENSITIVE_USERNAMES = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "radius-case-sensitive-usernames"; } + + }; } diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/user/AuthenticatedUser.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/user/AuthenticatedUser.java index a42925357c..ddedb860df 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/user/AuthenticatedUser.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/user/AuthenticatedUser.java @@ -23,6 +23,8 @@ import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An RADIUS-specific implementation of AuthenticatedUser, associating a @@ -30,12 +32,23 @@ */ public class AuthenticatedUser extends AbstractAuthenticatedUser { + /** + * Logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticatedUser.class); + /** * Reference to the authentication provider associated with this * authenticated user. */ @Inject private AuthenticationProvider authProvider; + + /** + * A reference to the configuration service associated with this module. + */ + @Inject + private ConfigurationService confService; /** * The credentials provided when this user was authenticated. @@ -62,5 +75,18 @@ public AuthenticationProvider getAuthenticationProvider() { public Credentials getCredentials() { return credentials; } + + @Override + public boolean isCaseSensitive() { + try { + return confService.getCaseSensitiveUsernames(); + } + catch (GuacamoleException e) { + LOGGER.error("Error retrieving configuration for username case sensiivity. " + + "Usernames will be processed as case-sensitive."); + LOGGER.debug("Exception caught while retrieving RADIUS configuration.", e); + return true; + } + } } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/user/SSOAuthenticatedUser.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/user/SSOAuthenticatedUser.java index 1e46f6d25b..5f3e5d07af 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/user/SSOAuthenticatedUser.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/user/SSOAuthenticatedUser.java @@ -23,9 +23,13 @@ import java.util.Collections; import java.util.Map; import java.util.Set; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.Environment; import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An AuthenticatedUser whose identity has been supplied by an arbitrary SSO @@ -35,12 +39,23 @@ */ public class SSOAuthenticatedUser extends AbstractAuthenticatedUser { + /** + * Logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(SSOAuthenticatedUser.class); + /** * Reference to the authentication provider associated with this * authenticated user. */ @Inject private AuthenticationProvider authProvider; + + /** + * The environment in which this instance of Guacamole is running. + */ + @Inject + private Environment environment; /** * The credentials provided when this user was authenticated. @@ -112,5 +127,22 @@ public Credentials getCredentials() { public Set getEffectiveUserGroups() { return effectiveGroups; } + + @Override + public boolean isCaseSensitive() { + try { + return environment.getCaseSensitiveUsernames(); + } + catch (GuacamoleException e) { + // Most SSO systems do not consider usernames to be case-sensitive; + // however, in order to avoid any surprises created by the introduction + // of case-sensitivity, we've opted to continue to evaluate these + // usernames in a case-sensitive manner by default. + LOGGER.error("Error occurred when trying to retrieve case-sensitivity configuration: {}. " + + "Usernames comparisons will be done in a case-sensitive manner.", e.getMessage()); + LOGGER.debug("Exception caught when trying to access the case-sensitivity property.", e); + return true; + } + } } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/CASAuthenticationProviderModule.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/CASAuthenticationProviderModule.java index 0cfeacd424..a9568b6bb5 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/CASAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/CASAuthenticationProviderModule.java @@ -20,18 +20,28 @@ package org.apache.guacamole.auth.cas; import com.google.inject.AbstractModule; +import org.apache.guacamole.auth.cas.conf.CASEnvironment; import org.apache.guacamole.auth.cas.conf.ConfigurationService; import org.apache.guacamole.auth.cas.ticket.TicketValidationService; +import org.apache.guacamole.environment.Environment; /** * Guice module which configures CAS-specific injections. */ public class CASAuthenticationProviderModule extends AbstractModule { + /** + * The configuration environment for this server and extension. + */ + private final Environment environment = new CASEnvironment(); + @Override protected void configure() { bind(ConfigurationService.class); bind(TicketValidationService.class); + + bind(Environment.class).toInstance(environment); + } } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASEnvironment.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASEnvironment.java new file mode 100644 index 0000000000..45973f3fc9 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASEnvironment.java @@ -0,0 +1,53 @@ +/* + * 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.cas.conf; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.DelegatingEnvironment; +import org.apache.guacamole.environment.LocalEnvironment; + +/** + * An environment for retrieving CAS-related properties from the Guacamole + * configuration. + */ +public class CASEnvironment extends DelegatingEnvironment { + + /** + * Create a new instance of the configuration environment for the + * CAS SSO module, pulling the default instance of the LocalEnvironment. + */ + public CASEnvironment() { + super(LocalEnvironment.getInstance()); + } + + @Override + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + + // While most SSO systems do not consider usernames case-sensitive, + // this defaults to the global Guacamole configuration, which defaults + // to true, in order to avoid surprising or breaking environments that + // may rely on this behavior. This can be overridden for the entire + // Guacamole instance or for this extension. + return getProperty(CASGuacamoleProperties.CAS_CASE_SENSITIVE_USERNAMES, + super.getCaseSensitiveUsernames()); + + } + +} diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java index 7bb363f9c2..cb40b97405 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java @@ -20,6 +20,7 @@ package org.apache.guacamole.auth.cas.conf; import org.apache.guacamole.auth.cas.group.GroupFormat; +import org.apache.guacamole.properties.BooleanGuacamoleProperty; import org.apache.guacamole.properties.EnumGuacamoleProperty; import org.apache.guacamole.properties.URIGuacamoleProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; @@ -117,5 +118,17 @@ private CASGuacamoleProperties() {} public String getName() { return "cas-group-ldap-attribute"; } }; + + /** + * A property used to configure whether or not usernames within the CAS SSO + * module should be treated as case-sensitive. + */ + public static final BooleanGuacamoleProperty CAS_CASE_SENSITIVE_USERNAMES = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "cas-case-sensitive-usernames"; } + + }; } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java index 2fce2a7192..6dc45f7f97 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java @@ -22,19 +22,28 @@ import com.google.inject.AbstractModule; import com.google.inject.Scopes; import org.apache.guacamole.auth.openid.conf.ConfigurationService; +import org.apache.guacamole.auth.openid.conf.OpenIDEnvironment; import org.apache.guacamole.auth.sso.NonceService; import org.apache.guacamole.auth.openid.token.TokenValidationService; +import org.apache.guacamole.environment.Environment; /** * Guice module which configures OpenID-specific injections. */ public class OpenIDAuthenticationProviderModule extends AbstractModule { + /** + * The configuration environment for this server and extension. + */ + private final Environment environment = new OpenIDEnvironment(); + @Override protected void configure() { bind(ConfigurationService.class); bind(NonceService.class).in(Scopes.SINGLETON); bind(TokenValidationService.class); + + bind(Environment.class).toInstance(environment); } } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java index 444bd7706b..58204277a8 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java @@ -26,6 +26,7 @@ import java.util.List; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.BooleanGuacamoleProperty; import org.apache.guacamole.properties.IntegerGuacamoleProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; import org.apache.guacamole.properties.URIGuacamoleProperty; @@ -217,7 +218,19 @@ public class ConfigurationService { @Override public String getName() { return "openid-redirect-uri"; } - + + }; + + /** + * A property used to configure whether or not usernames within the OpenID + * SSO module should be treated as case-sensitive. + */ + public static final BooleanGuacamoleProperty OPENID_CASE_SENSITIVE_USERNAMES = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "openid-case-sensitive-usernames"; } + }; /** diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/OpenIDEnvironment.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/OpenIDEnvironment.java new file mode 100644 index 0000000000..a8ea4d081f --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/OpenIDEnvironment.java @@ -0,0 +1,53 @@ +/* + * 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.openid.conf; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.DelegatingEnvironment; +import org.apache.guacamole.environment.LocalEnvironment; + +/** + * An environment for retrieving OpenID-related properties from the Guacamole + * configuration. + */ +public class OpenIDEnvironment extends DelegatingEnvironment { + + /** + * Create a new instance of the configuration environment for the + * OpenID SSO module, pulling the default instance of the LocalEnvironment. + */ + public OpenIDEnvironment() { + super(LocalEnvironment.getInstance()); + } + + @Override + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + + // While most SSO systems do not consider usernames case-sensitive, + // this defaults to the global Guacamole configuration, which defaults + // to true, in order to avoid surprising or breaking environments that + // may rely on this behavior. This can be overridden for the entire + // Guacamole instance or for this extension. + return getProperty(ConfigurationService.OPENID_CASE_SENSITIVE_USERNAMES, + super.getCaseSensitiveUsernames()); + + } + +} diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProviderModule.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProviderModule.java index e01880a62f..a223392fa2 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProviderModule.java @@ -24,12 +24,19 @@ import org.apache.guacamole.auth.saml.acs.AssertionConsumerServiceResource; import org.apache.guacamole.auth.saml.acs.SAMLAuthenticationSessionManager; import org.apache.guacamole.auth.saml.acs.SAMLService; +import org.apache.guacamole.auth.saml.conf.SAMLEnvironment; +import org.apache.guacamole.environment.Environment; /** * Guice module which configures SAML-specific injections. */ public class SAMLAuthenticationProviderModule extends AbstractModule { + /** + * The environment for this server and extension. + */ + private final Environment environment = new SAMLEnvironment(); + @Override protected void configure() { bind(AssertionConsumerServiceResource.class); @@ -37,6 +44,8 @@ protected void configure() { bind(SAMLAuthenticationSessionManager.class); bind(SAMLService.class); + bind(Environment.class).toInstance(environment); + requestStaticInjection(SAMLAuthenticationEventListener.class); } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/conf/ConfigurationService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/conf/ConfigurationService.java index 47ead88208..8419658ec7 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/conf/ConfigurationService.java @@ -189,6 +189,18 @@ public class ConfigurationService { public String getName() { return "saml-private-key-path"; } }; + + /** + * A property used to configure whether or not usernames within the SAML SSO + * module should be treated as case-sensitive. + */ + public static final BooleanGuacamoleProperty SAML_CASE_SENSITIVE_USERNAMES = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "saml-case-sensitive-usernames"; } + + }; /** * The Guacamole server environment. diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/conf/SAMLEnvironment.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/conf/SAMLEnvironment.java new file mode 100644 index 0000000000..b294db5289 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/conf/SAMLEnvironment.java @@ -0,0 +1,53 @@ +/* + * 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.saml.conf; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.DelegatingEnvironment; +import org.apache.guacamole.environment.LocalEnvironment; + +/** + * An environment for retrieving SAML-related properties from the Guacamole + * configuration. + */ +public class SAMLEnvironment extends DelegatingEnvironment { + + /** + * Create a new instance of the configuration environment for the + * SAML SSO module, pulling the default instance of the LocalEnvironment. + */ + public SAMLEnvironment() { + super(LocalEnvironment.getInstance()); + } + + @Override + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + + // While most SSO systems do not consider usernames case-sensitive, + // this defaults to the global Guacamole configuration, which defaults + // to true, in order to avoid surprising or breaking environments that + // may rely on this behavior. This can be overridden for the entire + // Guacamole instance or for this extension. + return getProperty(ConfigurationService.SAML_CASE_SENSITIVE_USERNAMES, + super.getCaseSensitiveUsernames()); + + } + +} diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/SSLAuthenticationProviderModule.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/SSLAuthenticationProviderModule.java index 9f7b15affc..bcf7368fb8 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/SSLAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/SSLAuthenticationProviderModule.java @@ -22,7 +22,9 @@ import com.google.inject.AbstractModule; import com.google.inject.Scopes; import org.apache.guacamole.auth.ssl.conf.ConfigurationService; +import org.apache.guacamole.auth.ssl.conf.SSLEnvironment; import org.apache.guacamole.auth.sso.NonceService; +import org.apache.guacamole.environment.Environment; /** * Guice module which configures injections specific to SSO using SSL/TLS @@ -30,12 +32,19 @@ */ public class SSLAuthenticationProviderModule extends AbstractModule { + /** + * The configuration environment of this server and extension. + */ + private final Environment environment = new SSLEnvironment(); + @Override protected void configure() { bind(ConfigurationService.class); bind(NonceService.class).in(Scopes.SINGLETON); bind(SSLAuthenticationSessionManager.class); + bind(Environment.class).toInstance(environment); + requestStaticInjection(SSLAuthenticationEventListener.class); } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/conf/ConfigurationService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/conf/ConfigurationService.java index 7931c0dad0..9a165cbcce 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/conf/ConfigurationService.java @@ -28,6 +28,7 @@ import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.BooleanGuacamoleProperty; import org.apache.guacamole.properties.IntegerGuacamoleProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; import org.apache.guacamole.properties.URIGuacamoleProperty; @@ -186,6 +187,18 @@ public class ConfigurationService { public String getName() { return "ssl-max-domain-validity"; } }; + + /** + * A property used to configure whether or not usernames within the SSL SSO + * module should be treated as case-sensitive. + */ + public static final BooleanGuacamoleProperty SSL_CASE_SENSITIVE_USERNAMES = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "ssl-case-sensitive-usernames"; } + + }; /** * The Guacamole server environment. diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/conf/SSLEnvironment.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/conf/SSLEnvironment.java new file mode 100644 index 0000000000..bf544dd0d9 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/conf/SSLEnvironment.java @@ -0,0 +1,53 @@ +/* + * 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.ssl.conf; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.DelegatingEnvironment; +import org.apache.guacamole.environment.LocalEnvironment; + +/** + * An environment for retrieving SSL-related properties from the Guacamole + * configuration. + */ +public class SSLEnvironment extends DelegatingEnvironment { + + /** + * Create a new instance of the configuration environment for the + * SSL SSO module, pulling the default instance of the LocalEnvironment. + */ + public SSLEnvironment() { + super(LocalEnvironment.getInstance()); + } + + @Override + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + + // While most SSO systems do not consider usernames case-sensitive, + // this defaults to the global Guacamole configuration, which defaults + // to true, in order to avoid surprising or breaking environments that + // may rely on this behavior. This can be overridden for the entire + // Guacamole instance or for this extension. + return getProperty(ConfigurationService.SSL_CASE_SENSITIVE_USERNAMES, + super.getCaseSensitiveUsernames()); + + } + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/environment/DelegatingEnvironment.java b/guacamole-ext/src/main/java/org/apache/guacamole/environment/DelegatingEnvironment.java index 936612a214..5dc3266c4f 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/environment/DelegatingEnvironment.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/environment/DelegatingEnvironment.java @@ -113,5 +113,10 @@ public GuacamoleProxyConfiguration getDefaultGuacamoleProxyConfiguration() throw public void addGuacamoleProperties(GuacamoleProperties properties) throws GuacamoleException { environment.addGuacamoleProperties(properties); } + + @Override + public boolean getCaseSensitiveUsernames() throws GuacamoleException { + return environment.getCaseSensitiveUsernames(); + } } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/environment/Environment.java b/guacamole-ext/src/main/java/org/apache/guacamole/environment/Environment.java index 22b95149bc..e6154a17cc 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/environment/Environment.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/environment/Environment.java @@ -69,6 +69,18 @@ public interface Environment { public String getName() { return "guacd-ssl"; } }; + + /** + * A property that configures whether or not Guacamole will take case + * into account when comparing and processing usernames. + */ + public static final BooleanGuacamoleProperty CASE_SENSITIVE_USERNAMES = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "case-sensitive-usernames"; } + + }; /** * Returns the Guacamole home directory as determined when this Environment @@ -367,5 +379,23 @@ public default void addGuacamoleProperties(GuacamoleProperties properties) + "support dynamic definition of Guacamole properties.", getClass())); } + + /** + * Returns true if Guacamole should consider case when comparing and + * processing usernames (case-sensitive), or false if case should not be + * considered (case-insensitive). Because the past behavior of Guacamole, + * prior to the introduction of this option, was case-sensitive, the default + * value is true. + * + * @return + * true if Guacamole should consider usernames case-sensitive, otherwise + * false. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public default boolean getCaseSensitiveUsernames() throws GuacamoleException { + return getProperty(CASE_SENSITIVE_USERNAMES, true); + } } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractIdentifiable.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractIdentifiable.java index 3401719d42..f0cd2ed1a0 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractIdentifiable.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractIdentifiable.java @@ -19,7 +19,6 @@ package org.apache.guacamole.net.auth; - /** * Abstract implementation of Identifiable which provides equals() and * hashCode() implementations which use the identifier to determine equality. @@ -34,12 +33,18 @@ public abstract class AbstractIdentifiable implements Identifiable { @Override public String getIdentifier() { - return identifier; + if (identifier == null || isCaseSensitive()) + return identifier; + + return identifier.toLowerCase(); } @Override public void setIdentifier(String identifier) { - this.identifier = identifier; + if (isCaseSensitive() || identifier == null) + this.identifier = identifier; + else + this.identifier = identifier.toLowerCase(); } @Override @@ -48,7 +53,10 @@ public int hashCode() { if (identifier == null) return 0; - return identifier.hashCode(); + if (isCaseSensitive()) + return identifier.hashCode(); + + return identifier.toLowerCase().hashCode(); } @Override @@ -65,8 +73,12 @@ public boolean equals(Object other) { if (otherIdentifier == null) return identifier == null; - // Otherwise, equal only if strings are identical - return otherIdentifier.equals(identifier); + // If this identifier is case-sensitive, evaluate with case-sensitivity. + if (isCaseSensitive()) + return otherIdentifier.equals(identifier); + + // The identifier should not be evaluated in a case-sensitive manner. + return otherIdentifier.equalsIgnoreCase(identifier); } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractUser.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractUser.java index f4e85ae6fd..472a3325c2 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractUser.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractUser.java @@ -49,6 +49,14 @@ public String getPassword() { public void setPassword(String password) { this.password = password; } + + @Override + public boolean isCaseSensitive() { + + // In order to avoid causing incompatibility with other extensions, + // this class maintains case-sensitive comparisons. + return true; + } /** * {@inheritDoc} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Identifiable.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Identifiable.java index d32fec0636..b5e0136c2a 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Identifiable.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Identifiable.java @@ -43,5 +43,18 @@ public interface Identifiable { * The identifier to assign. */ public void setIdentifier(String identifier); + + /** + * Whether or not this identifier should be evaluated in a case-sensitive + * manner or not. By default this returns true and the identifier will + * be evaluated in a case-sensitive manner. + * + * @return + * True if the comparisons of this identifier should be case-sensitive, + * otherwise false. + */ + default public boolean isCaseSensitive() { + return true; + } }