Skip to content

Commit feba550

Browse files
committed
GUACAMOLE-1239: Update JDBC queries to handle case-sensitivity.
1 parent 498e6a6 commit feba550

37 files changed

Lines changed: 1227 additions & 169 deletions

File tree

‎extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/HistoryTrackingConnection.java‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.apache.guacamole.auth.jdbc;
2121

22+
import com.google.inject.Inject;
2223
import java.util.Date;
2324
import java.util.HashMap;
2425
import java.util.Map;
@@ -55,6 +56,12 @@ public class HistoryTrackingConnection extends DelegatingConnection {
5556
* established connections.
5657
*/
5758
private final ConnectionRecordMapper connectionRecordMapper;
59+
60+
/**
61+
* The Guacamole server environment.
62+
*/
63+
@Inject
64+
private JDBCEnvironment environment;
5865

5966
/**
6067
* Creates a new HistoryConnection that wraps the given connection,
@@ -98,7 +105,8 @@ public GuacamoleTunnel connect(GuacamoleClientInformation info,
98105
connectionRecordModel.setConnectionName(this.getDelegateConnection().getName());
99106

100107
// Insert the connection history record to mark the start of this connection
101-
connectionRecordMapper.insert(connectionRecordModel);
108+
connectionRecordMapper.insert(connectionRecordModel,
109+
environment.getCaseSensitiveUsernames());
102110

103111
// Include history record UUID as token
104112
ModeledConnectionRecord modeledRecord = new ModeledConnectionRecord(connectionRecordModel);

‎extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCEnvironment.java‎

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
* intended for use within JDBC based authentication providers.
3131
*/
3232
public abstract class JDBCEnvironment extends DelegatingEnvironment {
33-
33+
3434
/**
3535
* Constructs a new JDBCEnvironment using an underlying LocalEnviroment to
3636
* read properties from the file system.
@@ -273,16 +273,30 @@ public boolean shouldUseBatchExecutor() {
273273
}
274274

275275
/**
276-
* Returns a boolean value that indicates whether or not usernames should
277-
* be treated as case-sensitive.
276+
* Returns true if the usernames provided to the JDBC authentication
277+
* module should be treated as case-sensitive, or false if usernames
278+
* should be treated as case-insensitive. The default is true, usernames
279+
* will be case-sensitive in keeping with the past behavior of Guacamole
280+
* prior to the addition of this option.
278281
*
279282
* @return
280-
* true if usernames should be treated as case-sensitive, or false if
281-
* usernames should be treated as case-insensitive.
283+
* true if usernames should be treated as case-sensitive, otherwise
284+
* false.
282285
*
283286
* @throws GuacamoleException
284287
* If guacamole.properties cannot be parsed.
285288
*/
286-
public abstract boolean getCaseSensitiveUsernames() throws GuacamoleException;
289+
public boolean getCaseSensitiveUsernames() throws GuacamoleException {
290+
291+
// In keeping with past behavior of Guacamole prior to the introduction
292+
// of this feature, we default to case-sensitive username comparisons.
293+
// However, it is worth noting that some databases do not do
294+
// case-sensitive string comparisons by default, and additional
295+
// configuration is required to make string comparisons case-sensitive
296+
// If you need that functionality, please test with your database and
297+
// configure both that and Guacamole appropriately.
298+
return true;
299+
300+
}
287301

288302
}

‎extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ActivityRecordMapper.java‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,16 @@ public interface ActivityRecordMapper<ModelType> {
3838
*
3939
* @param record
4040
* The activity record to insert.
41+
*
42+
* @param caseSensitive
43+
* Whether or not string comparisons should be done in a case-sensitive
44+
* manner.
4145
*
4246
* @return
4347
* The number of rows inserted.
4448
*/
45-
int insert(@Param("record") ModelType record);
49+
int insert(@Param("record") ModelType record,
50+
@Param("caseSensitive") boolean caseSensitive);
4651

4752
/**
4853
* Updates the given activity record in the database, assigning an end
@@ -85,6 +90,10 @@ public interface ActivityRecordMapper<ModelType> {
8590
*
8691
* @param limit
8792
* The maximum number of records that should be returned.
93+
*
94+
* @param caseSensitive
95+
* Whether or not string comparisons should be done in a case-sensitive
96+
* manner.
8897
*
8998
* @return
9099
* The results of the search performed with the given parameters.
@@ -93,7 +102,8 @@ List<ModelType> search(@Param("identifier") String identifier,
93102
@Param("recordIdentifier") String recordIdentifier,
94103
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
95104
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
96-
@Param("limit") int limit);
105+
@Param("limit") int limit,
106+
@Param("caseSensitive") boolean caseSensitive);
97107

98108
/**
99109
* Searches for up to <code>limit</code> activity records that contain
@@ -132,6 +142,10 @@ List<ModelType> search(@Param("identifier") String identifier,
132142
* when determining the permissions effectively granted to the user. If
133143
* no groups are given, only permissions directly granted to the user
134144
* will be used.
145+
*
146+
* @param caseSensitive
147+
* Whether or not string comparisons should be done in a case-sensitive
148+
* manner.
135149
*
136150
* @return
137151
* The results of the search performed with the given parameters.
@@ -142,6 +156,7 @@ List<ModelType> searchReadable(@Param("identifier") String identifier,
142156
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
143157
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
144158
@Param("limit") int limit,
145-
@Param("effectiveGroups") Collection<String> effectiveGroups);
159+
@Param("effectiveGroups") Collection<String> effectiveGroups,
160+
@Param("caseSensitive") boolean caseSensitive);
146161

147162
}

‎extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionService.java‎

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,20 @@
2828
import java.util.List;
2929
import java.util.Map;
3030
import java.util.Set;
31-
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
32-
import org.apache.guacamole.language.TranslatableGuacamoleClientOverrunException;
33-
import org.apache.guacamole.language.TranslatableMessage;
34-
import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper;
35-
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
3631
import org.apache.guacamole.GuacamoleClientException;
3732
import org.apache.guacamole.GuacamoleException;
3833
import org.apache.guacamole.GuacamoleSecurityException;
34+
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
3935
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm;
4036
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate;
37+
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
4138
import org.apache.guacamole.auth.jdbc.base.ModeledChildDirectoryObjectService;
39+
import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper;
4240
import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionMapper;
4341
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionMapper;
42+
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
43+
import org.apache.guacamole.language.TranslatableGuacamoleClientOverrunException;
44+
import org.apache.guacamole.language.TranslatableMessage;
4445
import org.apache.guacamole.net.GuacamoleTunnel;
4546
import org.apache.guacamole.net.auth.Connection;
4647
import org.apache.guacamole.net.auth.ConnectionRecord;
@@ -85,6 +86,12 @@ public class ConnectionService extends ModeledChildDirectoryObjectService<Modele
8586
*/
8687
@Inject
8788
private Provider<ModeledConnection> connectionProvider;
89+
90+
/**
91+
* The server environment for retrieving configuration.
92+
*/
93+
@Inject
94+
private JDBCEnvironment environment;
8895

8996
/**
9097
* Service for creating and tracking tunnels.
@@ -486,14 +493,16 @@ public List<ConnectionRecord> retrieveHistory(String identifier,
486493
// Bypass permission checks if the user is privileged or has System-level audit permissions
487494
if (user.isPrivileged() || user.getUser().getEffectivePermissions().getSystemPermissions().hasPermission(SystemPermission.Type.AUDIT))
488495
searchResults = connectionRecordMapper.search(identifier,
489-
recordIdentifier, requiredContents, sortPredicates, limit);
496+
recordIdentifier, requiredContents, sortPredicates, limit,
497+
environment.getCaseSensitiveUsernames());
490498

491499
// Otherwise only return explicitly readable history records
492500
else
493501
searchResults = connectionRecordMapper.searchReadable(identifier,
494502
user.getUser().getModel(), recordIdentifier,
495503
requiredContents, sortPredicates, limit,
496-
user.getEffectiveUserGroups());
504+
user.getEffectiveUserGroups(),
505+
environment.getCaseSensitiveUsernames());
497506

498507
return getObjectInstances(searchResults);
499508

‎extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/permission/UserPermissionMapper.java‎

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,142 @@
1919

2020
package org.apache.guacamole.auth.jdbc.permission;
2121

22+
import java.util.Collection;
23+
import org.apache.guacamole.auth.jdbc.base.EntityModel;
24+
import org.apache.guacamole.net.auth.permission.ObjectPermission;
25+
import org.apache.ibatis.annotations.Param;
26+
2227
/**
2328
* Mapper for user permissions.
2429
*/
25-
public interface UserPermissionMapper extends ObjectPermissionMapper {}
30+
public interface UserPermissionMapper extends ObjectPermissionMapper {
31+
32+
/**
33+
* Deletes the given permissions from the database. If any permissions do
34+
* not exist, they will be ignored.
35+
*
36+
* @param permissions
37+
* The permissions to delete.
38+
*
39+
* @param caseSensitive
40+
* Whether or not string comparisons for usernames will be done in a
41+
* case-sensitive manner.
42+
*
43+
* @return
44+
* The number of rows deleted.
45+
*/
46+
int delete(@Param("permissions") Collection<ObjectPermission.Type> permissions,
47+
@Param("caseSensitive") boolean caseSensitive);
48+
49+
/**
50+
* Inserts the given permissions into the database. If any permissions
51+
* already exist, they will be ignored.
52+
*
53+
* @param permissions
54+
* The permissions to insert.
55+
*
56+
* @param caseSensitive
57+
* Whether or not string comparisons for usernames will be done in a
58+
* case-sensitive manner.
59+
*
60+
* @return
61+
* The number of rows inserted.
62+
*/
63+
int insert(@Param("permissions") Collection<ObjectPermission.Type> permissions,
64+
@Param("caseSensitive") boolean caseSensitive);
65+
66+
/**
67+
* Retrieves all permissions associated with the given entity (user or user
68+
* group).
69+
*
70+
* @param entity
71+
* The entity to retrieve permissions for.
72+
*
73+
* @param effectiveGroups
74+
* The identifiers of all groups that should be taken into account
75+
* when determining the permissions effectively granted to the user. If
76+
* no groups are given, only permissions directly granted to the user
77+
* will be used.
78+
*
79+
* @param caseSensitive
80+
* Whether or not string comparisons for usernames will be done in a
81+
* case-sensitive manner.
82+
*
83+
* @return
84+
* All permissions associated with the given entity.
85+
*/
86+
Collection<ObjectPermission.Type> select(@Param("entity") EntityModel entity,
87+
@Param("effectiveGroups") Collection<String> effectiveGroups,
88+
@Param("caseSensitive") boolean caseSensitive);
89+
90+
/**
91+
* Retrieve the permission of the given type associated with the given
92+
* entity and object, if it exists. If no such permission exists, null is
93+
* returned.
94+
*
95+
* @param entity
96+
* The entity to retrieve permissions for.
97+
*
98+
* @param type
99+
* The type of permission to return.
100+
*
101+
* @param identifier
102+
* The identifier of the object affected by the permission to return.
103+
*
104+
* @param effectiveGroups
105+
* The identifiers of all groups that should be taken into account
106+
* when determining the permissions effectively granted to the user. If
107+
* no groups are given, only permissions directly granted to the user
108+
* will be used.
109+
*
110+
* @param caseSensitive
111+
* Whether or not string comparisons for usernames will be done in a
112+
* case-sensitive manner.
113+
*
114+
* @return
115+
* The requested permission, or null if no such permission is granted
116+
* to the given entity for the given object.
117+
*/
118+
ObjectPermissionModel selectOne(@Param("entity") EntityModel entity,
119+
@Param("type") ObjectPermission.Type type,
120+
@Param("identifier") String identifier,
121+
@Param("effectiveGroups") Collection<String> effectiveGroups,
122+
@Param("caseSensitive") boolean caseSensitive);
123+
124+
/**
125+
* Retrieves the subset of the given identifiers for which the given entity
126+
* has at least one of the given permissions.
127+
*
128+
* @param entity
129+
* The entity to check permissions of.
130+
*
131+
* @param permissions
132+
* The permissions to check. An identifier will be included in the
133+
* resulting collection if at least one of these permissions is granted
134+
* for the associated object
135+
*
136+
* @param identifiers
137+
* The identifiers of the objects affected by the permissions being
138+
* checked.
139+
*
140+
* @param effectiveGroups
141+
* The identifiers of all groups that should be taken into account
142+
* when determining the permissions effectively granted to the user. If
143+
* no groups are given, only permissions directly granted to the user
144+
* will be used.
145+
*
146+
* @param caseSensitive
147+
* Whether or not string comparisons for usernames will be done in a
148+
* case-sensitive manner.
149+
*
150+
* @return
151+
* A collection containing the subset of identifiers for which at least
152+
* one of the specified permissions is granted.
153+
*/
154+
Collection<String> selectAccessibleIdentifiers(@Param("entity") EntityModel entity,
155+
@Param("permissions") Collection<ObjectPermission.Type> permissions,
156+
@Param("identifiers") Collection<String> identifiers,
157+
@Param("effectiveGroups") Collection<String> effectiveGroups,
158+
@Param("caseSenstive") boolean caseSensitive);
159+
160+
}

‎extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/security/PasswordPolicyService.java‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,16 +131,21 @@ private boolean matches(String str, Pattern... patterns) {
131131
* @return
132132
* true if the given password matches any of the user's previous
133133
* passwords, up to the specified limit, false otherwise.
134+
*
135+
* @throws GuacamoleException
136+
* If an error occurs accessing environment information used for
137+
* retrieving configuration values.
134138
*/
135139
private boolean matchesPreviousPasswords(String password, String username,
136-
int historySize) {
140+
int historySize) throws GuacamoleException {
137141

138142
// No need to compare if no history is relevant
139143
if (historySize <= 0)
140144
return false;
141145

142146
// Check password against all recorded hashes
143-
List<PasswordRecordModel> history = passwordRecordMapper.select(username, historySize);
147+
List<PasswordRecordModel> history = passwordRecordMapper.select(username,
148+
historySize, environment.getCaseSensitiveUsernames());
144149
for (PasswordRecordModel record : history) {
145150

146151
byte[] hash = encryptionService.createPasswordHash(password, record.getPasswordSalt());

‎extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.UUID;
3535
import java.util.concurrent.ConcurrentHashMap;
3636
import java.util.concurrent.atomic.AtomicBoolean;
37+
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
3738
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
3839
import org.apache.guacamole.auth.jdbc.connection.ModeledConnection;
3940
import org.apache.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
@@ -167,6 +168,12 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
167168
*/
168169
@Inject
169170
private SharedConnectionMap connectionMap;
171+
172+
/**
173+
* The Guacamole server environment.
174+
*/
175+
@Inject
176+
private JDBCEnvironment environment;
170177

171178
/**
172179
* All active connections through the tunnel having a given UUID.
@@ -470,7 +477,9 @@ private GuacamoleTunnel assignGuacamoleTunnel(ActiveConnectionRecord activeConne
470477
// Record new active connection
471478
Runnable cleanupTask = new ConnectionCleanupTask(activeConnection);
472479
try {
473-
connectionRecordMapper.insert(activeConnection.getModel()); // This MUST happen before getUUID() is invoked, to ensure the ID driving the UUID exists
480+
// This MUST happen before getUUID() is invoked, to ensure the ID driving the UUID exists
481+
connectionRecordMapper.insert(activeConnection.getModel(),
482+
environment.getCaseSensitiveUsernames());
474483
activeTunnels.put(activeConnection.getUUID().toString(), activeConnection);
475484
}
476485

0 commit comments

Comments
 (0)