diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/user/SharedUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/user/SharedUser.java index 697b2ca7f..f0a48b87d 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/user/SharedUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/user/SharedUser.java @@ -35,9 +35,6 @@ import org.apache.guacamole.net.auth.RelatedObjectSet; import org.apache.guacamole.net.auth.User; import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; import org.apache.guacamole.net.auth.permission.SystemPermissionSet; -import org.apache.guacamole.net.auth.simple.SimpleObjectPermissionSet; -import org.apache.guacamole.net.auth.simple.SimpleRelatedObjectSet; -import org.apache.guacamole.net.auth.simple.SimpleSystemPermissionSet; /** * An immutable implementation of User which defines READ permission for each of @@ -122,7 +119,7 @@ public class SharedUser implements User { @Override public SystemPermissionSet getSystemPermissions() throws GuacamoleException { - return new SimpleSystemPermissionSet(); + return SystemPermissionSet.EMPTY_SET; } @Override @@ -145,22 +142,22 @@ public class SharedUser implements User { @Override public ObjectPermissionSet getUserGroupPermissions() throws GuacamoleException { - return new SimpleObjectPermissionSet(); + return ObjectPermissionSet.EMPTY_SET; } @Override public ObjectPermissionSet getSharingProfilePermissions() throws GuacamoleException { - return new SimpleObjectPermissionSet(); + return ObjectPermissionSet.EMPTY_SET; } @Override public ObjectPermissionSet getActiveConnectionPermissions() throws GuacamoleException { - return new SimpleObjectPermissionSet(); + return ObjectPermissionSet.EMPTY_SET; } @Override public RelatedObjectSet getUserGroups() throws GuacamoleException { - return new SimpleRelatedObjectSet(); + return RelatedObjectSet.EMPTY_SET; } @Override diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java index 4f5d76c6e..d9d3ab150 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java @@ -30,10 +30,12 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser; -import org.apache.guacamole.auth.ldap.user.LDAPUserContext; +import java.util.Set; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.auth.ldap.group.UserGroupService; +import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser; +import org.apache.guacamole.auth.ldap.user.LDAPUserContext; import org.apache.guacamole.auth.ldap.user.UserService; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; @@ -71,6 +73,12 @@ public class AuthenticationProviderService { @Inject private UserService userService; + /** + * Service for retrieving user groups. + */ + @Inject + private UserGroupService userGroupService; + /** * Provider for AuthenticatedUser objects. */ @@ -231,10 +239,15 @@ public class AuthenticationProviderService { throw new GuacamoleInvalidCredentialsException("Permission denied.", CredentialsInfo.USERNAME_PASSWORD); try { + + // Retrieve group membership of the user that just authenticated + Set effectiveGroups = + userGroupService.getParentUserGroupIdentifiers(ldapConnection, + ldapConnection.getAuthenticationDN()); + // Return AuthenticatedUser if bind succeeds LDAPAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); - authenticatedUser.init(credentials, getAttributeTokens(ldapConnection, credentials.getUsername())); - + authenticatedUser.init(credentials, getAttributeTokens(ldapConnection, credentials.getUsername()), effectiveGroups); return authenticatedUser; } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConfigurationService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConfigurationService.java index 4347d4570..e8ea0ace5 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConfigurationService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConfigurationService.java @@ -138,6 +138,24 @@ public class ConfigurationService { ); } + /** + * Returns all attributes which should be used to determine the unique + * identifier of each user group. By default, this will be "cn". + * + * @return + * The attributes which should be used to determine the unique + * identifier of each group. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public List getGroupNameAttributes() throws GuacamoleException { + return environment.getProperty( + LDAPGuacamoleProperties.LDAP_GROUP_NAME_ATTRIBUTE, + Collections.singletonList("cn") + ); + } + /** * Returns the base DN under which all Guacamole role based access control * (RBAC) groups will be stored within the LDAP directory. If RBAC will not diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProviderModule.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProviderModule.java index ead26bcd2..23decec6d 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProviderModule.java @@ -23,6 +23,7 @@ import com.google.inject.AbstractModule; import org.apache.guacamole.auth.ldap.connection.ConnectionService; import org.apache.guacamole.auth.ldap.user.UserService; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.ldap.group.UserGroupService; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.environment.LocalEnvironment; import org.apache.guacamole.net.auth.AuthenticationProvider; @@ -77,6 +78,8 @@ public class LDAPAuthenticationProviderModule extends AbstractModule { bind(ConnectionService.class); bind(EscapingService.class); bind(LDAPConnectionService.class); + bind(ObjectQueryService.class); + bind(UserGroupService.class); bind(UserService.class); } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPGuacamoleProperties.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPGuacamoleProperties.java index 120b09ea2..75299563b 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPGuacamoleProperties.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPGuacamoleProperties.java @@ -86,6 +86,18 @@ public class LDAPGuacamoleProperties { }; + /** + * The attribute or attributes which identify user groups. One of these + * attributes must be present within each Guacamole user group's record in + * the LDAP directory for that group to be visible. + */ + public static final StringListProperty LDAP_GROUP_NAME_ATTRIBUTE = new StringListProperty() { + + @Override + public String getName() { return "ldap-group-name-attribute"; } + + }; + /** * The port on the LDAP server to connect to when authenticating users. */ diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ObjectQueryService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ObjectQueryService.java new file mode 100644 index 000000000..94550a2a5 --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ObjectQueryService.java @@ -0,0 +1,326 @@ +/* + * 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.ldap; + +import com.google.inject.Inject; +import com.novell.ldap.LDAPAttribute; +import com.novell.ldap.LDAPConnection; +import com.novell.ldap.LDAPEntry; +import com.novell.ldap.LDAPException; +import com.novell.ldap.LDAPReferralException; +import com.novell.ldap.LDAPSearchResults; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.net.auth.Identifiable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service for executing queries against an LDAP directory intended to retrieve + * Guacamole-related objects. Referrals are automatically handled. Convenience + * functions are provided for generating the LDAP queries typically required + * for retrieving Guacamole objects, as well as for converting the results of a + * query into a {@link Map} of Guacamole objects. + */ +public class ObjectQueryService { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(ObjectQueryService.class); + + /** + * Service for escaping parts of LDAP queries. + */ + @Inject + private EscapingService escapingService; + + /** + * Service for retrieving LDAP server configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Returns the identifier of the object represented by the given LDAP + * entry. Multiple attributes may be declared as containing the identifier + * of the object when present on an LDAP entry. If multiple such attributes + * are present on the same LDAP entry, the value of the attribute with + * highest priority is used. If multiple copies of the same attribute are + * present on the same LDAPentry, the first value of that attribute is + * used. + * + * @param entry + * The entry representing the Guacamole object whose unique identifier + * should be determined. + * + * @param attributes + * A collection of all attributes which may be used to specify the + * unique identifier of the Guacamole object represented by an LDAP + * entry, in order of decreasing priority. + * + * @return + * The identifier of the object represented by the given LDAP entry, or + * null if no attributes declared as containing the identifier of the + * object are present on the entry. + */ + public String getIdentifier(LDAPEntry entry, Collection attributes) { + + // Retrieve the first value of the highest priority identifier attribute + for (String identifierAttribute : attributes) { + LDAPAttribute identifier = entry.getAttribute(identifierAttribute); + if (identifier != null) + return identifier.getStringValue(); + } + + // No identifier attribute is present on the entry + return null; + + } + + /** + * Generates a properly-escaped LDAP query which finds all objects which + * match the given LDAP filter and which have at least one of the given + * attributes set to the specified value. + * + * @param filter + * The LDAP filter to apply to reduce the results of the query in + * addition to testing the values of the given attributes. + * + * @param attributes + * A collection of all attributes to test for equivalence to the given + * value, in order of decreasing priority. + * + * @param attributeValue + * The value that the resulting LDAP query should search for within the + * attributes of objects within the LDAP directory. If null, the + * resulting LDAP query will search for the presence of at least one of + * the given attributes on each object, regardless of the value of + * those attributes. + * + * @return + * An LDAP query which will search for arbitrary LDAP objects having at + * least one of the given attributes set to the specified value. + */ + public String generateQuery(String filter, + Collection attributes, String attributeValue) { + + // Build LDAP query for objects having at least one attribute and with + // the given search filter + StringBuilder ldapQuery = new StringBuilder(); + ldapQuery.append("(&"); + ldapQuery.append(filter); + + // Include all attributes within OR clause if there are more than one + if (attributes.size() > 1) + ldapQuery.append("(|"); + + // Add equality comparison for each possible attribute + for (String attribute : attributes) { + ldapQuery.append("("); + ldapQuery.append(escapingService.escapeLDAPSearchFilter(attribute)); + + if (attributeValue != null) { + ldapQuery.append("="); + ldapQuery.append(escapingService.escapeLDAPSearchFilter(attributeValue)); + ldapQuery.append(")"); + } + else + ldapQuery.append("=*)"); + + } + + // Close OR clause, if any + if (attributes.size() > 1) + ldapQuery.append(")"); + + // Close overall query (AND clause) + ldapQuery.append(")"); + + return ldapQuery.toString(); + + } + + /** + * Executes an arbitrary LDAP query using the given connection, returning a + * list of all results. Only objects beneath the given base DN are + * included in the search. + * + * @param ldapConnection + * The current connection to the LDAP server, associated with the + * current user. + * + * @param baseDN + * The base DN to search using the given LDAP query. + * + * @param query + * The LDAP query to execute. + * + * @return + * A list of all results accessible to the user currently bound under + * the given LDAP connection. + * + * @throws GuacamoleException + * If an error occurs executing the query, or if configuration + * information required to execute the query cannot be read from + * guacamole.properties. + */ + public List search(LDAPConnection ldapConnection, + String baseDN, String query) throws GuacamoleException { + + logger.debug("Searching \"{}\" for objects matching \"{}\".", baseDN, query); + + try { + + // Search within subtree of given base DN + LDAPSearchResults results = ldapConnection.search(baseDN, + LDAPConnection.SCOPE_SUB, query, null, false, + confService.getLDAPSearchConstraints()); + + // Produce list of all entries in the search result, automatically + // following referrals if configured to do so + List entries = new ArrayList<>(results.getCount()); + while (results.hasMore()) { + + try { + entries.add(results.next()); + } + + // Warn if referrals cannot be followed + catch (LDAPReferralException e) { + if (confService.getFollowReferrals()) { + logger.error("Could not follow referral: {}", e.getFailedReferral()); + logger.debug("Error encountered trying to follow referral.", e); + throw new GuacamoleServerException("Could not follow LDAP referral.", e); + } + else { + logger.warn("Given a referral, but referrals are disabled. Error was: {}", e.getMessage()); + logger.debug("Got a referral, but configured to not follow them.", e); + } + } + + } + + return entries; + + } + catch (LDAPException | GuacamoleException e) { + throw new GuacamoleServerException("Unable to query list of " + + "objects from LDAP directory.", e); + } + + } + + /** + * Executes the query which would be returned by generateQuery() using the + * given connection, returning a list of all results. Only objects beneath + * the given base DN are included in the search. + * + * @param ldapConnection + * The current connection to the LDAP server, associated with the + * current user. + * + * @param baseDN + * The base DN to search using the given LDAP query. + * + * @param filter + * The LDAP filter to apply to reduce the results of the query in + * addition to testing the values of the given attributes. + * + * @param attributes + * A collection of all attributes to test for equivalence to the given + * value, in order of decreasing priority. + * + * @param attributeValue + * The value that should be searched search for within the attributes + * of objects within the LDAP directory. If null, the search will test + * only for the presence of at least one of the given attributes on + * each object, regardless of the value of those attributes. + * + * @return + * A list of all results accessible to the user currently bound under + * the given LDAP connection. + * + * @throws GuacamoleException + * If an error occurs executing the query, or if configuration + * information required to execute the query cannot be read from + * guacamole.properties. + */ + public List search(LDAPConnection ldapConnection, String baseDN, + String filter, Collection attributes, String attributeValue) + throws GuacamoleException { + String query = generateQuery(filter, attributes, attributeValue); + return search(ldapConnection, baseDN, query); + } + + /** + * Converts a given list of LDAP entries to a {@link Map} of Guacamole + * objects stored by their identifiers. + * + * @param + * The type of object to store within the {@link Map}. + * + * @param entries + * A list of LDAP entries to convert to Guacamole objects. + * + * @param mapper + * A mapping function which converts a given LDAP entry to its + * corresponding Guacamole object. If the LDAP entry cannot be + * converted, null should be returned. + * + * @return + * A new {@link Map} containing Guacamole object versions of each of + * the given LDAP entries, where each object is stored within the + * {@link Map} under its corresponding identifier. + */ + public Map + asMap(List entries, Function mapper) { + + // Convert each entry to the corresponding Guacamole API object + Map objects = new HashMap<>(entries.size()); + for (LDAPEntry entry : entries) { + + ObjectType object = mapper.apply(entry); + if (object == null) { + logger.debug("Ignoring object \"{}\".", entry.getDN()); + continue; + } + + // Attempt to add object to map, warning if the object appears + // to be a duplicate + String identifier = object.getIdentifier(); + if (objects.putIfAbsent(identifier, object) != null) + logger.warn("Multiple objects ambiguously map to the " + + "same identifier (\"{}\"). Ignoring \"{}\" as " + + "a duplicate.", identifier, entry.getDN()); + + } + + return objects; + + } + +} diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java index b69a13ae3..6a96d5be4 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java @@ -24,17 +24,17 @@ import com.novell.ldap.LDAPAttribute; import com.novell.ldap.LDAPConnection; import com.novell.ldap.LDAPEntry; import com.novell.ldap.LDAPException; -import com.novell.ldap.LDAPReferralException; -import com.novell.ldap.LDAPSearchResults; import java.util.Collections; import java.util.Enumeration; -import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.guacamole.auth.ldap.LDAPAuthenticationProvider; import org.apache.guacamole.auth.ldap.ConfigurationService; import org.apache.guacamole.auth.ldap.EscapingService; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.auth.ldap.ObjectQueryService; +import org.apache.guacamole.auth.ldap.group.UserGroupService; import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Connection; @@ -67,6 +67,18 @@ public class ConnectionService { @Inject private ConfigurationService confService; + /** + * Service for executing LDAP queries. + */ + @Inject + private ObjectQueryService queryService; + + /** + * Service for retrieving user groups. + */ + @Inject + private UserGroupService userGroupService; + /** * Returns all Guacamole connections accessible to the user currently bound * under the given LDAP connection. @@ -113,101 +125,71 @@ public class ConnectionService { // looking for direct membership in the guacConfigGroup // and possibly any groups the user is a member of that are // referred to in the seeAlso attribute of the guacConfigGroup. - LDAPSearchResults results = ldapConnection.search( - configurationBaseDN, - LDAPConnection.SCOPE_SUB, - connectionSearchFilter, - null, - false, - confService.getLDAPSearchConstraints() - ); + List results = queryService.search(ldapConnection, configurationBaseDN, connectionSearchFilter); - // Produce connections for each readable configuration - Map connections = new HashMap(); - while (results.hasMore()) { + // Return a map of all readable connections + return queryService.asMap(results, (entry) -> { - try { + // Get common name (CN) + LDAPAttribute cn = entry.getAttribute("cn"); + if (cn == null) { + logger.warn("guacConfigGroup is missing a cn."); + return null; + } - LDAPEntry entry = results.next(); + // Get associated protocol + LDAPAttribute protocol = entry.getAttribute("guacConfigProtocol"); + if (protocol == null) { + logger.warn("guacConfigGroup \"{}\" is missing the " + + "required \"guacConfigProtocol\" attribute.", + cn.getStringValue()); + return null; + } - // Get common name (CN) - LDAPAttribute cn = entry.getAttribute("cn"); - if (cn == null) { - logger.warn("guacConfigGroup is missing a cn."); - continue; - } + // Set protocol + GuacamoleConfiguration config = new GuacamoleConfiguration(); + config.setProtocol(protocol.getStringValue()); - // Get associated protocol - LDAPAttribute protocol = entry.getAttribute("guacConfigProtocol"); - if (protocol == null) { - logger.warn("guacConfigGroup \"{}\" is missing the " - + "required \"guacConfigProtocol\" attribute.", - cn.getStringValue()); - continue; - } + // Get parameters, if any + LDAPAttribute parameterAttribute = entry.getAttribute("guacConfigParameter"); + if (parameterAttribute != null) { - // Set protocol - GuacamoleConfiguration config = new GuacamoleConfiguration(); - config.setProtocol(protocol.getStringValue()); + // For each parameter + Enumeration parameters = parameterAttribute.getStringValues(); + while (parameters.hasMoreElements()) { - // Get parameters, if any - LDAPAttribute parameterAttribute = entry.getAttribute("guacConfigParameter"); - if (parameterAttribute != null) { + String parameter = (String) parameters.nextElement(); - // For each parameter - Enumeration parameters = parameterAttribute.getStringValues(); - while (parameters.hasMoreElements()) { + // Parse parameter + int equals = parameter.indexOf('='); + if (equals != -1) { - String parameter = (String) parameters.nextElement(); + // Parse name + String name = parameter.substring(0, equals); + String value = parameter.substring(equals+1); - // Parse parameter - int equals = parameter.indexOf('='); - if (equals != -1) { - - // Parse name - String name = parameter.substring(0, equals); - String value = parameter.substring(equals+1); - - config.setParameter(name, value); - - } + config.setParameter(name, value); } } - // Store connection using cn for both identifier and name - String name = cn.getStringValue(); - Connection connection = new SimpleConnection(name, name, config); - connection.setParentIdentifier(LDAPAuthenticationProvider.ROOT_CONNECTION_GROUP); - - // Inject LDAP-specific tokens only if LDAP handled user - // authentication - if (user instanceof LDAPAuthenticatedUser) - connection = new TokenInjectingConnection(connection, - ((LDAPAuthenticatedUser) user).getTokens()); - - connections.put(name, connection); - } - // Deal with issues following LDAP referrals - catch (LDAPReferralException e) { - if (confService.getFollowReferrals()) { - logger.error("Could not follow referral: {}", e.getFailedReferral()); - logger.debug("Error encountered trying to follow referral.", e); - throw new GuacamoleServerException("Could not follow LDAP referral.", e); - } - else { - logger.warn("Given a referral, but referrals are disabled. Error was: {}", e.getMessage()); - logger.debug("Got a referral, but configured to not follow them.", e); - } - } + // Store connection using cn for both identifier and name + String name = cn.getStringValue(); + Connection connection = new SimpleConnection(name, name, config); + connection.setParentIdentifier(LDAPAuthenticationProvider.ROOT_CONNECTION_GROUP); - } + // Inject LDAP-specific tokens only if LDAP handled user + // authentication + if (user instanceof LDAPAuthenticatedUser) + connection = new TokenInjectingConnection(connection, + ((LDAPAuthenticatedUser) user).getTokens()); - // Return map of all connections - return connections; + return connection; + + }); } catch (LDAPException e) { @@ -253,47 +235,12 @@ public class ConnectionService { connectionSearchFilter.append(escapingService.escapeLDAPSearchFilter(userDN)); connectionSearchFilter.append(")"); - // If group base DN is specified search for user groups - String groupBaseDN = confService.getGroupBaseDN(); - if (groupBaseDN != null) { - - // Get all groups the user is a member of starting at the groupBaseDN, excluding guacConfigGroups - LDAPSearchResults userRoleGroupResults = ldapConnection.search( - groupBaseDN, - LDAPConnection.SCOPE_SUB, - "(&(!(objectClass=guacConfigGroup))(" - + escapingService.escapeLDAPSearchFilter( - confService.getMemberAttribute()) - + "=" + escapingService.escapeLDAPSearchFilter(userDN) - + "))", - null, - false, - confService.getLDAPSearchConstraints() - ); - - // Append the additional user groups to the LDAP filter - // Now the filter will also look for guacConfigGroups that refer - // to groups the user is a member of - // The guacConfig group uses the seeAlso attribute to refer - // to these other groups - while (userRoleGroupResults.hasMore()) { - try { - LDAPEntry entry = userRoleGroupResults.next(); - connectionSearchFilter.append("(seeAlso=").append(escapingService.escapeLDAPSearchFilter(entry.getDN())).append(")"); - } - - catch (LDAPReferralException e) { - if (confService.getFollowReferrals()) { - logger.error("Could not follow referral: {}", e.getFailedReferral()); - logger.debug("Error encountered trying to follow referral.", e); - throw new GuacamoleServerException("Could not follow LDAP referral.", e); - } - else { - logger.warn("Given a referral, but referrals are disabled. Error was: {}", e.getMessage()); - logger.debug("Got a referral, but configured to not follow them.", e); - } - } - } + // Additionally filter by group membership if the current user is a + // member of any user groups + List userGroups = userGroupService.getParentUserGroupEntries(ldapConnection, userDN); + if (!userGroups.isEmpty()) { + for (LDAPEntry entry : userGroups) + connectionSearchFilter.append("(seeAlso=").append(escapingService.escapeLDAPSearchFilter(entry.getDN())).append(")"); } // Complete the search filter. diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java new file mode 100644 index 000000000..3315beb78 --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java @@ -0,0 +1,224 @@ +/* + * 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.ldap.group; + +import com.google.inject.Inject; +import com.novell.ldap.LDAPConnection; +import com.novell.ldap.LDAPEntry; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.guacamole.auth.ldap.ConfigurationService; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.ldap.ObjectQueryService; +import org.apache.guacamole.net.auth.UserGroup; +import org.apache.guacamole.net.auth.simple.SimpleUserGroup; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service for querying user group membership and retrieving user groups + * visible to a particular Guacamole user. + */ +public class UserGroupService { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(UserGroupService.class); + + /** + * Service for retrieving LDAP server configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Service for executing LDAP queries. + */ + @Inject + private ObjectQueryService queryService; + + /** + * Returns the base search filter which should be used to retrieve user + * groups which do not represent Guacamole connections. As excluding the + * guacConfigGroup object class may not work as expected if it is not + * defined (may always return zero results), it should only be explicitly + * excluded if it is expected to have been defined. + * + * @return + * The base search filter which should be used to retrieve user groups. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + private String getGroupSearchFilter() throws GuacamoleException { + + // Explicitly exclude guacConfigGroup object class only if it should + // be assumed to be defined (query may fail due to no such object + // class existing otherwise) + if (confService.getConfigurationBaseDN() != null) + return "(!(objectClass=guacConfigGroup))"; + + // Read any object as a group if LDAP is not being used for connection + // storage (guacConfigGroup) + return "(objectClass=*)"; + + } + + /** + * Returns all Guacamole user groups accessible to the user currently bound + * under the given LDAP connection. + * + * @param ldapConnection + * The current connection to the LDAP server, associated with the + * current user. + * + * @return + * All user groups accessible to the user currently bound under the + * given LDAP connection, as a map of user group identifier to + * corresponding UserGroup object. + * + * @throws GuacamoleException + * If an error occurs preventing retrieval of user groups. + */ + public Map getUserGroups(LDAPConnection ldapConnection) + throws GuacamoleException { + + // Do not return any user groups if base DN is not specified + String groupBaseDN = confService.getGroupBaseDN(); + if (groupBaseDN == null) + return Collections.emptyMap(); + + // Retrieve all visible user groups which are not guacConfigGroups + Collection attributes = confService.getGroupNameAttributes(); + List results = queryService.search( + ldapConnection, + groupBaseDN, + getGroupSearchFilter(), + attributes, + null + ); + + // Convert retrieved user groups to map of identifier to Guacamole + // user group object + return queryService.asMap(results, entry -> { + + // Translate entry into UserGroup object having proper identifier + String name = queryService.getIdentifier(entry, attributes); + if (name != null) + return new SimpleUserGroup(name); + + // Ignore user groups which lack a name attribute + logger.debug("User group \"{}\" is missing a name attribute " + + "and will be ignored.", entry.getDN()); + return null; + + }); + + } + + /** + * Returns the LDAP entries representing all user groups that the given + * user is a member of. Only user groups which are readable by the current + * user will be retrieved. + * + * @param ldapConnection + * The current connection to the LDAP server, associated with the + * current user. + * + * @param userDN + * The DN of the user whose group membership should be retrieved. + * + * @return + * The LDAP entries representing all readable parent user groups of the + * user having the given DN. + * + * @throws GuacamoleException + * If an error occurs preventing retrieval of user groups. + */ + public List getParentUserGroupEntries(LDAPConnection ldapConnection, + String userDN) throws GuacamoleException { + + // Do not return any user groups if base DN is not specified + String groupBaseDN = confService.getGroupBaseDN(); + if (groupBaseDN == null) + return Collections.emptyList(); + + // Get all groups the user is a member of starting at the groupBaseDN, + // excluding guacConfigGroups + return queryService.search( + ldapConnection, + groupBaseDN, + getGroupSearchFilter(), + Collections.singleton(confService.getMemberAttribute()), + userDN + ); + + } + + /** + * Returns the identifiers of all user groups that the given user is a + * member of. Only identifiers of user groups which are readable by the + * current user will be retrieved. + * + * @param ldapConnection + * The current connection to the LDAP server, associated with the + * current user. + * + * @param userDN + * The DN of the user whose group membership should be retrieved. + * + * @return + * The identifiers of all readable parent user groups of the user + * having the given DN. + * + * @throws GuacamoleException + * If an error occurs preventing retrieval of user groups. + */ + public Set getParentUserGroupIdentifiers(LDAPConnection ldapConnection, + String userDN) throws GuacamoleException { + + Collection attributes = confService.getGroupNameAttributes(); + List userGroups = getParentUserGroupEntries(ldapConnection, userDN); + + Set identifiers = new HashSet<>(userGroups.size()); + userGroups.forEach(entry -> { + + // Determine unique identifier for user group + String name = queryService.getIdentifier(entry, attributes); + if (name != null) + identifiers.add(name); + + // Ignore user groups which lack a name attribute + else + logger.debug("User group \"{}\" is missing a name attribute " + + "and will be ignored.", entry.getDN()); + + }); + + return identifiers; + + } + +} 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 36f6695eb..cafc461d6 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 @@ -22,6 +22,7 @@ package org.apache.guacamole.auth.ldap.user; import com.google.inject.Inject; import java.util.Collections; import java.util.Map; +import java.util.Set; import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; @@ -51,8 +52,14 @@ public class LDAPAuthenticatedUser extends AbstractAuthenticatedUser { private Map tokens; /** - * Initializes this AuthenticatedUser using the given credentials and - * connection parameter tokens. + * The unique identifiers of all user groups which affect the permissions + * available to this user. + */ + private Set effectiveGroups; + + /** + * Initializes this AuthenticatedUser with the given credentials, + * connection parameter tokens. and set of effective user groups. * * @param credentials * The credentials provided when this user was authenticated. @@ -60,10 +67,15 @@ public class LDAPAuthenticatedUser extends AbstractAuthenticatedUser { * @param tokens * A Map of all name/value pairs that should be applied as parameter * tokens when connections are established using the AuthenticatedUser. + * + * @param effectiveGroups + * The unique identifiers of all user groups which affect the + * permissions available to this user. */ - public void init(Credentials credentials, Map tokens) { + public void init(Credentials credentials, Map tokens, Set effectiveGroups) { this.credentials = credentials; this.tokens = Collections.unmodifiableMap(tokens); + this.effectiveGroups = effectiveGroups; setIdentifier(credentials.getUsername()); } @@ -91,4 +103,9 @@ public class LDAPAuthenticatedUser extends AbstractAuthenticatedUser { return credentials; } + @Override + public Set getEffectiveUserGroups() { + return effectiveGroups; + } + } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPUserContext.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPUserContext.java index 8f29a9694..5505f7ec1 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPUserContext.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPUserContext.java @@ -25,6 +25,7 @@ import java.util.Collections; import org.apache.guacamole.auth.ldap.connection.ConnectionService; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.ldap.LDAPAuthenticationProvider; +import org.apache.guacamole.auth.ldap.group.UserGroupService; import org.apache.guacamole.net.auth.AbstractUserContext; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; @@ -32,8 +33,11 @@ import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.User; +import org.apache.guacamole.net.auth.UserGroup; +import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; import org.apache.guacamole.net.auth.simple.SimpleConnectionGroup; import org.apache.guacamole.net.auth.simple.SimpleDirectory; +import org.apache.guacamole.net.auth.simple.SimpleObjectPermissionSet; import org.apache.guacamole.net.auth.simple.SimpleUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,6 +65,12 @@ public class LDAPUserContext extends AbstractUserContext { @Inject private UserService userService; + /** + * Service for retrieving user groups. + */ + @Inject + private UserGroupService userGroupService; + /** * Reference to the AuthenticationProvider associated with this * UserContext. @@ -80,6 +90,12 @@ public class LDAPUserContext extends AbstractUserContext { */ private Directory userDirectory; + /** + * Directory containing all UserGroup objects accessible to the user + * associated with this UserContext. + */ + private Directory userGroupDirectory; + /** * Directory containing all Connection objects accessible to the user * associated with this UserContext. @@ -112,12 +128,17 @@ public class LDAPUserContext extends AbstractUserContext { throws GuacamoleException { // Query all accessible users - userDirectory = new SimpleDirectory( + userDirectory = new SimpleDirectory<>( userService.getUsers(ldapConnection) ); + // Query all accessible user groups + userGroupDirectory = new SimpleDirectory<>( + userGroupService.getUserGroups(ldapConnection) + ); + // Query all accessible connections - connectionDirectory = new SimpleDirectory( + connectionDirectory = new SimpleDirectory<>( connectionService.getConnections(user, ldapConnection) ); @@ -130,12 +151,29 @@ public class LDAPUserContext extends AbstractUserContext { ); // Init self with basic permissions - self = new SimpleUser( - user.getIdentifier(), - userDirectory.getIdentifiers(), - connectionDirectory.getIdentifiers(), - Collections.singleton(LDAPAuthenticationProvider.ROOT_CONNECTION_GROUP) - ); + self = new SimpleUser(user.getIdentifier()) { + + @Override + public ObjectPermissionSet getUserPermissions() throws GuacamoleException { + return new SimpleObjectPermissionSet(userDirectory.getIdentifiers()); + } + + @Override + public ObjectPermissionSet getUserGroupPermissions() throws GuacamoleException { + return new SimpleObjectPermissionSet(userGroupDirectory.getIdentifiers()); + } + + @Override + public ObjectPermissionSet getConnectionPermissions() throws GuacamoleException { + return new SimpleObjectPermissionSet(connectionDirectory.getIdentifiers()); + } + + @Override + public ObjectPermissionSet getConnectionGroupPermissions() throws GuacamoleException { + return new SimpleObjectPermissionSet(Collections.singleton(LDAPAuthenticationProvider.ROOT_CONNECTION_GROUP)); + } + + }; } @@ -154,6 +192,11 @@ public class LDAPUserContext extends AbstractUserContext { return userDirectory; } + @Override + public Directory getUserGroupDirectory() throws GuacamoleException { + return userGroupDirectory; + } + @Override public Directory getConnectionDirectory() throws GuacamoleException { diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java index 9d27f1e00..3f12ae829 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java @@ -20,21 +20,17 @@ package org.apache.guacamole.auth.ldap.user; import com.google.inject.Inject; -import com.novell.ldap.LDAPAttribute; import com.novell.ldap.LDAPConnection; import com.novell.ldap.LDAPEntry; -import com.novell.ldap.LDAPException; -import com.novell.ldap.LDAPReferralException; -import com.novell.ldap.LDAPSearchResults; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collection; import java.util.List; import java.util.Map; import org.apache.guacamole.auth.ldap.ConfigurationService; import org.apache.guacamole.auth.ldap.EscapingService; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.auth.ldap.LDAPGuacamoleProperties; +import org.apache.guacamole.auth.ldap.ObjectQueryService; import org.apache.guacamole.net.auth.User; import org.apache.guacamole.net.auth.simple.SimpleUser; import org.slf4j.Logger; @@ -64,89 +60,10 @@ public class UserService { private ConfigurationService confService; /** - * Adds all Guacamole users accessible to the user currently bound under - * the given LDAP connection to the provided map. Only users with the - * specified attribute are added. If the same username is encountered - * multiple times, warnings about possible ambiguity will be logged. - * - * @param ldapConnection - * The current connection to the LDAP server, associated with the - * current user. - * - * @return - * All users accessible to the user currently bound under the given - * LDAP connection, as a map of connection identifier to corresponding - * user object. - * - * @throws GuacamoleException - * If an error occurs preventing retrieval of users. + * Service for executing LDAP queries. */ - private void putAllUsers(Map users, LDAPConnection ldapConnection, - String usernameAttribute) throws GuacamoleException { - - try { - - // Build a filter using the configured or default user search filter - // to find all user objects in the LDAP tree - StringBuilder userSearchFilter = new StringBuilder(); - userSearchFilter.append("(&"); - userSearchFilter.append(confService.getUserSearchFilter()); - userSearchFilter.append("("); - userSearchFilter.append(escapingService.escapeLDAPSearchFilter(usernameAttribute)); - userSearchFilter.append("=*))"); - - // Find all Guacamole users underneath base DN - LDAPSearchResults results = ldapConnection.search( - confService.getUserBaseDN(), - LDAPConnection.SCOPE_SUB, - userSearchFilter.toString(), - null, - false, - confService.getLDAPSearchConstraints() - ); - - // Read all visible users - while (results.hasMore()) { - - try { - - LDAPEntry entry = results.next(); - - // Get username from record - LDAPAttribute username = entry.getAttribute(usernameAttribute); - if (username == null) { - logger.warn("Queried user is missing the username attribute \"{}\".", usernameAttribute); - continue; - } - - // Store user using their username as the identifier - String identifier = username.getStringValue(); - if (users.put(identifier, new SimpleUser(identifier)) != null) - logger.warn("Possibly ambiguous user account: \"{}\".", identifier); - - } - - // Deal with errors trying to follow referrals - catch (LDAPReferralException e) { - if (confService.getFollowReferrals()) { - logger.error("Could not follow referral: {}", e.getFailedReferral()); - logger.debug("Error encountered trying to follow referral.", e); - throw new GuacamoleServerException("Could not follow LDAP referral.", e); - } - else { - logger.warn("Given a referral, but referrals are disabled. Error was: {}", e.getMessage()); - logger.debug("Got a referral, but configured to not follow them.", e); - } - } - - } - - } - catch (LDAPException e) { - throw new GuacamoleServerException("Error while querying users.", e); - } - - } + @Inject + private ObjectQueryService queryService; /** * Returns all Guacamole users accessible to the user currently bound under @@ -167,80 +84,28 @@ public class UserService { public Map getUsers(LDAPConnection ldapConnection) throws GuacamoleException { - // Build map of users by querying each username attribute separately - Map users = new HashMap(); - for (String usernameAttribute : confService.getUsernameAttributes()) { + // Retrieve all visible user objects + Collection attributes = confService.getUsernameAttributes(); + List results = queryService.search(ldapConnection, + confService.getUserBaseDN(), + confService.getUserSearchFilter(), + attributes, + null); - // Attempt to pull all users with given attribute - try { - putAllUsers(users, ldapConnection, usernameAttribute); + // Convert retrieved users to map of identifier to Guacamole user object + return queryService.asMap(results, entry -> { + + // Get username from record + String username = queryService.getIdentifier(entry, attributes); + if (username == null) { + logger.warn("User \"{}\" is missing a username attribute " + + "and will be ignored.", entry.getDN()); + return null; } - // Log any errors non-fatally - catch (GuacamoleException e) { - logger.warn("Could not query list of all users for attribute \"{}\": {}", - usernameAttribute, e.getMessage()); - logger.debug("Error querying list of all users.", e); - } + return new SimpleUser(username); - } - - // Return map of all users - return users; - - } - - /** - * Generates a properly-escaped LDAP query which finds all objects having - * at least one username attribute set to the specified username, where - * the possible username attributes are defined within - * guacamole.properties. - * - * @param username - * The username that the resulting LDAP query should search for within - * objects within the LDAP directory. - * - * @return - * An LDAP query which will search for arbitrary LDAP objects - * containing at least one username attribute set to the specified - * username. - * - * @throws GuacamoleException - * If the LDAP query cannot be generated because the list of username - * attributes cannot be parsed from guacamole.properties. - */ - private String generateLDAPQuery(String username) - throws GuacamoleException { - - List usernameAttributes = confService.getUsernameAttributes(); - - // Build LDAP query for users having at least one username attribute - // and with the configured or default search filter - StringBuilder ldapQuery = new StringBuilder(); - ldapQuery.append("(&"); - ldapQuery.append(confService.getUserSearchFilter()); - - // Include all attributes within OR clause if there are more than one - if (usernameAttributes.size() > 1) - ldapQuery.append("(|"); - - // Add equality comparison for each possible username attribute - for (String usernameAttribute : usernameAttributes) { - ldapQuery.append("("); - ldapQuery.append(escapingService.escapeLDAPSearchFilter(usernameAttribute)); - ldapQuery.append("="); - ldapQuery.append(escapingService.escapeLDAPSearchFilter(username)); - ldapQuery.append(")"); - } - - // Close OR clause, if any - if (usernameAttributes.size() > 1) - ldapQuery.append(")"); - - // Close overall query (AND clause) - ldapQuery.append(")"); - - return ldapQuery.toString(); + }); } @@ -268,49 +133,18 @@ public class UserService { public List getUserDNs(LDAPConnection ldapConnection, String username) throws GuacamoleException { - try { - - List userDNs = new ArrayList(); - - // Find all Guacamole users underneath base DN and matching the - // specified username - LDAPSearchResults results = ldapConnection.search( + // Retrieve user objects having a matching username + List results = queryService.search(ldapConnection, confService.getUserBaseDN(), - LDAPConnection.SCOPE_SUB, - generateLDAPQuery(username), - null, - false, - confService.getLDAPSearchConstraints() - ); + confService.getUserSearchFilter(), + confService.getUsernameAttributes(), + username); - // Add all DNs for found users - while (results.hasMore()) { - try { - LDAPEntry entry = results.next(); - userDNs.add(entry.getDN()); - } - - // Deal with errors following referrals - catch (LDAPReferralException e) { - if (confService.getFollowReferrals()) { - logger.error("Error trying to follow a referral: {}", e.getFailedReferral()); - logger.debug("Encountered an error trying to follow a referral.", e); - throw new GuacamoleServerException("Failed while trying to follow referrals.", e); - } - else { - logger.warn("Given a referral, not following it. Error was: {}", e.getMessage()); - logger.debug("Given a referral, but configured to not follow them.", e); - } - } - } + // Build list of all DNs for retrieved users + List userDNs = new ArrayList<>(results.size()); + results.forEach(entry -> userDNs.add(entry.getDN())); - // Return all discovered DNs (if any) - return userDNs; - - } - catch (LDAPException e) { - throw new GuacamoleServerException("Error while query user DNs.", e); - } + return userDNs; } diff --git a/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/QuickConnectUserContext.java b/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/QuickConnectUserContext.java index d7e23edb4..dad050556 100644 --- a/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/QuickConnectUserContext.java +++ b/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/QuickConnectUserContext.java @@ -26,6 +26,8 @@ import org.apache.guacamole.net.auth.AbstractUserContext; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.ConnectionGroup; import org.apache.guacamole.net.auth.User; +import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; +import org.apache.guacamole.net.auth.simple.SimpleObjectPermissionSet; import org.apache.guacamole.net.auth.simple.SimpleUser; /** @@ -93,10 +95,19 @@ public class QuickConnectUserContext extends AbstractUserContext { // Initialize the user to a SimpleUser with the provided username, // no connections, and the single root group. - this.self = new SimpleUser(username, - connectionDirectory.getIdentifiers(), - Collections.singleton(ROOT_IDENTIFIER) - ); + this.self = new SimpleUser(username) { + + @Override + public ObjectPermissionSet getConnectionPermissions() throws GuacamoleException { + return new SimpleObjectPermissionSet(connectionDirectory.getIdentifiers()); + } + + @Override + public ObjectPermissionSet getConnectionGroupPermissions() throws GuacamoleException { + return new SimpleObjectPermissionSet(Collections.singleton(ROOT_IDENTIFIER)); + } + + }; // Set the authProvider to the calling authProvider object. this.authProvider = authProvider; 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 2dae70235..b502f7a1b 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 @@ -19,10 +19,17 @@ package org.apache.guacamole.net.auth; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; +import org.apache.guacamole.net.auth.permission.SystemPermissionSet; /** - * Basic implementation of a Guacamole user which uses the username to - * determine equality. Username comparison is case-sensitive. + * Base implementation of User which provides default implementations of + * most functions. */ public abstract class AbstractUser extends AbstractIdentifiable implements User { @@ -44,4 +51,164 @@ public abstract class AbstractUser extends AbstractIdentifiable this.password = password; } + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty map. Implementations + * that wish to expose custom attributes should override this function. + */ + @Override + public Map getAttributes() { + return Collections.emptyMap(); + } + + /** + * {@inheritDoc} + * + *

This implementation simply ignores all attributes given. + * Implementations that wish to support modification of custom attributes + * should override this function. + */ + @Override + public void setAttributes(Map attributes) { + // Ignore all attributes by default + } + + /** + * {@inheritDoc} + * + *

This implementation simply returns {@code null}. Implementations that + * wish to expose the date and time that a user was last active should + * override this function. + */ + @Override + public Date getLastActive() { + return null; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty list. Implementations + * that wish to expose user login history should override this function. + */ + @Override + public List getHistory() throws GuacamoleException { + return Collections.emptyList(); + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public SystemPermissionSet getSystemPermissions() + throws GuacamoleException { + return SystemPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public ObjectPermissionSet getConnectionPermissions() + throws GuacamoleException { + return ObjectPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public ObjectPermissionSet getConnectionGroupPermissions() + throws GuacamoleException { + return ObjectPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public ObjectPermissionSet getUserPermissions() + throws GuacamoleException { + return ObjectPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public ObjectPermissionSet getUserGroupPermissions() + throws GuacamoleException { + return ObjectPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public ObjectPermissionSet getActiveConnectionPermissions() + throws GuacamoleException { + return ObjectPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public ObjectPermissionSet getSharingProfilePermissions() { + return ObjectPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty related object set. + * Implementations that wish to expose group membership should override + * this function. + */ + @Override + public RelatedObjectSet getUserGroups() throws GuacamoleException { + return RelatedObjectSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply returns {@code this}. Implementations that + * wish to expose permissions which apply indirectly (such as through + * group inheritance) should override this function. + */ + @Override + public Permissions getEffectivePermissions() throws GuacamoleException { + return this; + } + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractUserGroup.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractUserGroup.java new file mode 100644 index 000000000..f63bfae7f --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractUserGroup.java @@ -0,0 +1,183 @@ +/* + * 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.net.auth; + +import java.util.Collections; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; +import org.apache.guacamole.net.auth.permission.SystemPermissionSet; + +/** + * Base implementation of UserGroup which provides default implementations of + * most functions. + */ +public class AbstractUserGroup extends AbstractIdentifiable implements UserGroup { + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty map. Implementations + * that wish to expose custom attributes should override this function. + */ + @Override + public Map getAttributes() { + return Collections.emptyMap(); + } + + /** + * {@inheritDoc} + * + *

This implementation simply ignores all attributes given. + * Implementations that wish to support modification of custom attributes + * should override this function. + */ + @Override + public void setAttributes(Map attributes) { + // Ignore all attributes by default + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public SystemPermissionSet getSystemPermissions() + throws GuacamoleException { + return SystemPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public ObjectPermissionSet getConnectionPermissions() + throws GuacamoleException { + return ObjectPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public ObjectPermissionSet getConnectionGroupPermissions() + throws GuacamoleException { + return ObjectPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public ObjectPermissionSet getUserPermissions() + throws GuacamoleException { + return ObjectPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public ObjectPermissionSet getUserGroupPermissions() + throws GuacamoleException { + return ObjectPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public ObjectPermissionSet getActiveConnectionPermissions() + throws GuacamoleException { + return ObjectPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty permission set. + * Implementations that wish to expose permissions should override this + * function. + */ + @Override + public ObjectPermissionSet getSharingProfilePermissions() { + return ObjectPermissionSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty related object set. + * Implementations that wish to expose group membership should override + * this function. + */ + @Override + public RelatedObjectSet getUserGroups() throws GuacamoleException { + return RelatedObjectSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty related object set. + * Implementations that wish to expose group membership should override + * this function. + */ + @Override + public RelatedObjectSet getMemberUsers() throws GuacamoleException { + return RelatedObjectSet.EMPTY_SET; + } + + /** + * {@inheritDoc} + * + *

This implementation simply an immutable, empty related object set. + * Implementations that wish to expose group membership should override + * this function. + */ + @Override + public RelatedObjectSet getMemberUserGroups() throws GuacamoleException { + return RelatedObjectSet.EMPTY_SET; + } + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/RelatedObjectSet.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/RelatedObjectSet.java index 58bee372e..05f4c249e 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/RelatedObjectSet.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/RelatedObjectSet.java @@ -19,8 +19,10 @@ package org.apache.guacamole.net.auth; +import java.util.Collections; import java.util.Set; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleSecurityException; /** * An arbitrary set of existing objects sharing some common relation. Unlike a @@ -75,4 +77,28 @@ public interface RelatedObjectSet { */ void removeObjects(Set identifiers) throws GuacamoleException; + /** + * An immutable instance of RelatedObjectSEt which contains no objects. + */ + static final RelatedObjectSet EMPTY_SET = new RelatedObjectSet() { + + @Override + public Set getObjects() throws GuacamoleException { + return Collections.emptySet(); + } + + @Override + public void addObjects(Set identifiers) + throws GuacamoleException { + throw new GuacamoleSecurityException("Permission denied."); + } + + @Override + public void removeObjects(Set identifiers) + throws GuacamoleException { + throw new GuacamoleSecurityException("Permission denied."); + } + + }; + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/permission/ObjectPermissionSet.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/permission/ObjectPermissionSet.java index 3c3e910b5..638f95246 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/permission/ObjectPermissionSet.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/permission/ObjectPermissionSet.java @@ -20,8 +20,10 @@ package org.apache.guacamole.net.auth.permission; import java.util.Collection; +import java.util.Collections; import java.util.Set; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleSecurityException; /** @@ -126,4 +128,54 @@ public interface ObjectPermissionSet extends PermissionSet { void removePermissions(Set permissions) throws GuacamoleException; + /** + * An immutable instance of ObjectPermissionSet which contains no + * permissions. + */ + static final ObjectPermissionSet EMPTY_SET = new ObjectPermissionSet() { + + @Override + public boolean hasPermission(ObjectPermission.Type permission, + String identifier) throws GuacamoleException { + return false; + } + + @Override + public void addPermission(ObjectPermission.Type permission, + String identifier) throws GuacamoleException { + throw new GuacamoleSecurityException("Permission denied."); + } + + @Override + public void removePermission(ObjectPermission.Type permission, + String identifier) throws GuacamoleException { + throw new GuacamoleSecurityException("Permission denied."); + } + + @Override + public Collection getAccessibleObjects(Collection permissions, + Collection identifiers) throws GuacamoleException { + return Collections.emptySet(); + } + + @Override + public Set getPermissions() + throws GuacamoleException { + return Collections.emptySet(); + } + + @Override + public void addPermissions(Set permissions) + throws GuacamoleException { + throw new GuacamoleSecurityException("Permission denied."); + } + + @Override + public void removePermissions(Set permissions) + throws GuacamoleException { + throw new GuacamoleSecurityException("Permission denied."); + } + + }; + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/permission/SystemPermissionSet.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/permission/SystemPermissionSet.java index 3f504db48..52b62cd6e 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/permission/SystemPermissionSet.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/permission/SystemPermissionSet.java @@ -19,8 +19,10 @@ package org.apache.guacamole.net.auth.permission; +import java.util.Collections; import java.util.Set; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleSecurityException; /** @@ -81,4 +83,48 @@ public interface SystemPermissionSet extends PermissionSet { void removePermissions(Set permissions) throws GuacamoleException; + /** + * An immutable instance of SystemPermissionSet which contains no + * permissions. + */ + static final SystemPermissionSet EMPTY_SET = new SystemPermissionSet() { + + @Override + public boolean hasPermission(SystemPermission.Type permission) + throws GuacamoleException { + return false; + } + + @Override + public void addPermission(SystemPermission.Type permission) + throws GuacamoleException { + throw new GuacamoleSecurityException("Permission denied."); + } + + @Override + public void removePermission(SystemPermission.Type permission) + throws GuacamoleException { + throw new GuacamoleSecurityException("Permission denied."); + } + + @Override + public Set getPermissions() + throws GuacamoleException { + return Collections.emptySet(); + } + + @Override + public void addPermissions(Set permissions) + throws GuacamoleException { + throw new GuacamoleSecurityException("Permission denied."); + } + + @Override + public void removePermissions(Set permissions) + throws GuacamoleException { + throw new GuacamoleSecurityException("Permission denied."); + } + + }; + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleObjectPermissionSet.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleObjectPermissionSet.java index 7cf54bd2d..cbb298e90 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleObjectPermissionSet.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleObjectPermissionSet.java @@ -22,6 +22,7 @@ package org.apache.guacamole.net.auth.simple; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Set; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; @@ -37,14 +38,77 @@ public class SimpleObjectPermissionSet implements ObjectPermissionSet { /** * The set of all permissions currently granted. */ - private Set permissions = Collections.emptySet(); + private Set permissions = Collections.emptySet(); /** - * Creates a new empty SimpleObjectPermissionSet. + * Creates a new empty SimpleObjectPermissionSet. If you are not extending + * SimpleObjectPermissionSet and only need an immutable, empty + * ObjectPermissionSet, consider using {@link ObjectPermissionSet#EMPTY_SET} + * instead. */ public SimpleObjectPermissionSet() { } + /** + * Creates a new set of ObjectPermissions for each possible combination of + * the given identifiers and permission types. + * + * @param identifiers + * The identifiers which should have one ObjectPermission for each of + * the given permission types. + * + * @param types + * The permissions which should be granted for each of the given + * identifiers. + * + * @return + * A new set of ObjectPermissions containing one ObjectPermission for + * each possible combination of the given identifiers and permission + * types. + */ + private static Set createPermissions(Collection identifiers, + Collection types) { + + // Add a permission of each type to the set for each identifier given + Set permissions = new HashSet<>(identifiers.size()); + types.forEach(type -> { + identifiers.forEach(identifier -> permissions.add(new ObjectPermission(type, identifier))); + }); + + return permissions; + + } + + /** + * Creates a new SimpleObjectPermissionSet which contains permissions for + * all possible unique combinations of the given identifiers and permission + * types. + * + * @param identifiers + * The identifiers which should be associated permissions having each + * of the given permission types. + * + * @param types + * The types of permissions which should be granted for each of the + * given identifiers. + */ + public SimpleObjectPermissionSet(Collection identifiers, + Collection types) { + this(createPermissions(identifiers, types)); + } + + /** + * Creates a new SimpleObjectPermissionSet which contains only READ + * permissions for each of the given identifiers. + * + * @param identifiers + * The identifiers which should each be associated with READ + * permission. + */ + public SimpleObjectPermissionSet(Collection identifiers) { + this(identifiers, Collections.singletonList(ObjectPermission.Type.READ)); + } + /** * Creates a new SimpleObjectPermissionSet which contains the permissions * within the given Set. diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleRelatedObjectSet.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleRelatedObjectSet.java index 72f2da9f1..3796c43fa 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleRelatedObjectSet.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleRelatedObjectSet.java @@ -34,10 +34,13 @@ public class SimpleRelatedObjectSet implements RelatedObjectSet { /** * A set containing the identifiers of all objects currently present. */ - private Set identifiers = Collections.emptySet(); + private Set identifiers = Collections.emptySet(); /** - * Creates a new empty SimpleObjectPermissionSet. + * Creates a new empty SimpleRelatedObjectSet. If you are not extending + * SimpleRelatedObjectSet and only need an immutable, empty + * RelatedObjectSet, consider using {@link RelatedObjectSet#EMPTY_SET} + * instead. */ public SimpleRelatedObjectSet() { } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleSystemPermissionSet.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleSystemPermissionSet.java index ac82c838b..24c6d2d71 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleSystemPermissionSet.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleSystemPermissionSet.java @@ -35,10 +35,13 @@ public class SimpleSystemPermissionSet implements SystemPermissionSet { /** * The set of all permissions currently granted. */ - private Set permissions = Collections.emptySet(); + private Set permissions = Collections.emptySet(); /** - * Creates a new empty SimpleSystemPermissionSet. + * Creates a new empty SimpleSystemPermissionSet. If you are not extending + * SimpleSystemPermissionSet and only need an immutable, empty + * SystemPermissionSet, consider using {@link SystemPermissionSet#EMPTY_SET} + * instead. */ public SimpleSystemPermissionSet() { } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUser.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUser.java index 61fce20f4..8e349e27c 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUser.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUser.java @@ -20,43 +20,34 @@ package org.apache.guacamole.net.auth.simple; import java.util.Collection; -import java.util.Collections; -import java.util.Date; import java.util.HashSet; -import java.util.List; -import java.util.Map; import java.util.Set; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.net.auth.AbstractUser; -import org.apache.guacamole.net.auth.ActivityRecord; -import org.apache.guacamole.net.auth.Permissions; -import org.apache.guacamole.net.auth.RelatedObjectSet; import org.apache.guacamole.net.auth.permission.ObjectPermission; import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; -import org.apache.guacamole.net.auth.permission.SystemPermissionSet; /** - * An extremely basic User implementation. + * A read-only User implementation which has no permissions. Implementations + * that need to define permissions should extend this class and override the + * associated getters. */ public class SimpleUser extends AbstractUser { /** - * All connection permissions granted to this user. + * All user permissions granted to this user. */ - private final Set userPermissions = - new HashSet(); + private final Set userPermissions = new HashSet<>(); /** * All connection permissions granted to this user. */ - private final Set connectionPermissions = - new HashSet(); + private final Set connectionPermissions = new HashSet<>(); /** * All connection group permissions granted to this user. */ - private final Set connectionGroupPermissions = - new HashSet(); + private final Set connectionGroupPermissions = new HashSet<>(); /** * Creates a completely uninitialized SimpleUser. @@ -65,16 +56,13 @@ public class SimpleUser extends AbstractUser { } /** - * Creates a new SimpleUser having the given username and no permissions. + * Creates a new SimpleUser having the given username. * * @param username * The username to assign to this SimpleUser. */ public SimpleUser(String username) { - - // Set username - setIdentifier(username); - + super.setIdentifier(username); } /** @@ -92,18 +80,17 @@ public class SimpleUser extends AbstractUser { Collection identifiers) { // Add a READ permission to the set for each identifier given - for (String identifier : identifiers) { - permissions.add(new ObjectPermission ( + identifiers.forEach(identifier -> + permissions.add(new ObjectPermission( ObjectPermission.Type.READ, - identifier + identifier) )); - } } - + /** * Creates a new SimpleUser having the given username and READ access to - * the connections and groups having the given identifiers. + * the connections and connection groups having the given identifiers. * * @param username * The username to assign to this SimpleUser. @@ -114,7 +101,15 @@ public class SimpleUser extends AbstractUser { * @param connectionGroupIdentifiers * The identifiers of all connection groups this user has READ access * to. + * + * @deprecated + * Extend and override the applicable permission set getters instead, + * relying on SimpleUser to expose no permissions by default for all + * permission sets that aren't overridden. See {@link SimpleObjectPermissionSet} + * for convenient methods of providing a read-only permission set with + * specific permissions. */ + @Deprecated public SimpleUser(String username, Collection connectionIdentifiers, Collection connectionGroupIdentifiers) { @@ -143,7 +138,15 @@ public class SimpleUser extends AbstractUser { * @param connectionGroupIdentifiers * The identifiers of all connection groups this user has READ access * to. + * + * @deprecated + * Extend and override the applicable permission set getters instead, + * relying on SimpleUser to expose no permissions by default for all + * permission sets that aren't overridden. See {@link SimpleObjectPermissionSet} + * for convenient methods of providing a read-only permission set with + * specific permissions. */ + @Deprecated public SimpleUser(String username, Collection userIdentifiers, Collection connectionIdentifiers, @@ -158,32 +161,6 @@ public class SimpleUser extends AbstractUser { } - @Override - public Map getAttributes() { - return Collections.emptyMap(); - } - - @Override - public void setAttributes(Map attributes) { - // Do nothing - there are no attributes - } - - @Override - public Date getLastActive() { - return null; - } - - @Override - public List getHistory() throws GuacamoleException { - return Collections.emptyList(); - } - - @Override - public SystemPermissionSet getSystemPermissions() - throws GuacamoleException { - return new SimpleSystemPermissionSet(); - } - @Override public ObjectPermissionSet getConnectionPermissions() throws GuacamoleException { @@ -202,31 +179,4 @@ public class SimpleUser extends AbstractUser { return new SimpleObjectPermissionSet(userPermissions); } - @Override - public ObjectPermissionSet getUserGroupPermissions() - throws GuacamoleException { - return new SimpleObjectPermissionSet(); - } - - @Override - public ObjectPermissionSet getActiveConnectionPermissions() - throws GuacamoleException { - return new SimpleObjectPermissionSet(); - } - - @Override - public ObjectPermissionSet getSharingProfilePermissions() { - return new SimpleObjectPermissionSet(); - } - - @Override - public RelatedObjectSet getUserGroups() throws GuacamoleException { - return new SimpleRelatedObjectSet(); - } - - @Override - public Permissions getEffectivePermissions() throws GuacamoleException { - return this; - } - } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUserContext.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUserContext.java index 26978e9fb..03e94fbac 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUserContext.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUserContext.java @@ -19,7 +19,6 @@ package org.apache.guacamole.net.auth.simple; -import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.guacamole.GuacamoleException; @@ -29,6 +28,7 @@ import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.User; +import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; import org.apache.guacamole.protocol.GuacamoleConfiguration; /** @@ -113,20 +113,19 @@ public class SimpleUserContext extends AbstractUserContext { @Override public User self() { + return new SimpleUser(username) { - try { - return new SimpleUser(username, - getConnectionDirectory().getIdentifiers(), - getConnectionGroupDirectory().getIdentifiers() - ); - } + @Override + public ObjectPermissionSet getConnectionGroupPermissions() throws GuacamoleException { + return new SimpleObjectPermissionSet(getConnectionDirectory().getIdentifiers()); + } - catch (GuacamoleException e) { - return new SimpleUser(username, - Collections.emptySet(), - Collections.emptySet()); - } + @Override + public ObjectPermissionSet getConnectionPermissions() throws GuacamoleException { + return new SimpleObjectPermissionSet(getConnectionGroupDirectory().getIdentifiers()); + } + }; } @Override diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUserGroup.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUserGroup.java new file mode 100644 index 000000000..be52d1642 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleUserGroup.java @@ -0,0 +1,47 @@ +/* + * 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.net.auth.simple; + +import org.apache.guacamole.net.auth.AbstractUserGroup; + +/** + * A read-only UserGroup implementation which has no members and no + * permissions. Implementations that need to define members or permissions + * should extend this class and override the associated getters. + */ +public class SimpleUserGroup extends AbstractUserGroup { + + /** + * Creates a completely uninitialized SimpleUserGroup. + */ + public SimpleUserGroup() { + } + + /** + * Creates a new SimpleUserGroup having the given identifier. + * + * @param identifier + * The identifier to assign to this SimpleUserGroup. + */ + public SimpleUserGroup(String identifier) { + super.setIdentifier(identifier); + } + +}