diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderModule.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderModule.java index 71d784a52..708ec3e87 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderModule.java @@ -19,6 +19,7 @@ package org.apache.guacamole.auth.jdbc; +import com.google.inject.Scopes; import org.apache.guacamole.auth.jdbc.user.UserContext; import org.apache.guacamole.auth.jdbc.connectiongroup.RootConnectionGroup; import org.apache.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; @@ -62,6 +63,11 @@ import org.apache.guacamole.auth.jdbc.connection.ConnectionParameterMapper; import org.apache.guacamole.auth.jdbc.permission.SharingProfilePermissionMapper; import org.apache.guacamole.auth.jdbc.permission.SharingProfilePermissionService; import org.apache.guacamole.auth.jdbc.permission.SharingProfilePermissionSet; +import org.apache.guacamole.auth.jdbc.sharing.ConnectionSharingService; +import org.apache.guacamole.auth.jdbc.sharing.HashSharedConnectionMap; +import org.apache.guacamole.auth.jdbc.sharing.SecureRandomShareKeyGenerator; +import org.apache.guacamole.auth.jdbc.sharing.ShareKeyGenerator; +import org.apache.guacamole.auth.jdbc.sharing.SharedConnectionMap; import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile; import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileDirectory; import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileMapper; @@ -163,10 +169,13 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule { bind(ConnectionGroupPermissionService.class); bind(ConnectionGroupService.class); bind(ConnectionPermissionService.class); + bind(ConnectionSharingService.class); bind(ConnectionService.class); bind(GuacamoleTunnelService.class).to(RestrictedGuacamoleTunnelService.class); bind(PasswordEncryptionService.class).to(SHA256PasswordEncryptionService.class); bind(SaltService.class).to(SecureRandomSaltService.class); + bind(SharedConnectionMap.class).to(HashSharedConnectionMap.class).in(Scopes.SINGLETON); + bind(ShareKeyGenerator.class).to(SecureRandomShareKeyGenerator.class).in(Scopes.SINGLETON); bind(SharingProfilePermissionService.class); bind(SharingProfileService.class); bind(SystemPermissionService.class); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/activeconnection/TrackedActiveConnection.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/activeconnection/TrackedActiveConnection.java index d4a23ffbc..6c2e4d5a8 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/activeconnection/TrackedActiveConnection.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/activeconnection/TrackedActiveConnection.java @@ -19,10 +19,12 @@ package org.apache.guacamole.auth.jdbc.activeconnection; +import com.google.inject.Inject; import java.util.Date; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.auth.jdbc.base.RestrictedObject; +import org.apache.guacamole.auth.jdbc.connection.ModeledConnection; +import org.apache.guacamole.auth.jdbc.sharing.ConnectionSharingService; import org.apache.guacamole.auth.jdbc.tunnel.ActiveConnectionRecord; import org.apache.guacamole.auth.jdbc.user.AuthenticatedUser; import org.apache.guacamole.net.GuacamoleTunnel; @@ -37,15 +39,21 @@ import org.apache.guacamole.net.auth.credentials.UserCredentials; */ public class TrackedActiveConnection extends RestrictedObject implements ActiveConnection { + /** + * Service for managing shared connections. + */ + @Inject + private ConnectionSharingService sharingService; + /** * The identifier of this active connection. */ private String identifier; /** - * The identifier of the associated connection. + * The connection being actively used or shared. */ - private String connectionIdentifier; + private ModeledConnection connection; /** * The identifier of the associated sharing profile. @@ -67,6 +75,13 @@ public class TrackedActiveConnection extends RestrictedObject implements ActiveC */ private String username; + /** + * The connection ID of the connection as determined by guacd, not to be + * confused with the connection identifier determined by the database. This + * is the ID that must be supplied to guacd if joining this connection. + */ + private String connectionID; + /** * The underlying GuacamoleTunnel. */ @@ -98,7 +113,8 @@ public class TrackedActiveConnection extends RestrictedObject implements ActiveC super.init(currentUser); // Copy all non-sensitive data from given record - this.connectionIdentifier = activeConnectionRecord.getConnectionIdentifier(); + this.connection = activeConnectionRecord.getConnection(); + this.connectionID = activeConnectionRecord.getConnectionID(); this.sharingProfileIdentifier = activeConnectionRecord.getSharingProfileIdentifier(); this.identifier = activeConnectionRecord.getUUID().toString(); this.startDate = activeConnectionRecord.getStartDate(); @@ -121,15 +137,42 @@ public class TrackedActiveConnection extends RestrictedObject implements ActiveC public void setIdentifier(String identifier) { this.identifier = identifier; } - + + /** + * Returns the connection being actively used. If this active connection is + * not the primary connection, this will be the connection being actively + * shared. + * + * @return + * The connection being actively used. + */ + public ModeledConnection getConnection() { + return connection; + } + + /** + * Returns the connection ID of the in-progress connection as determined by + * guacd, not to be confused with the connection identifier determined by + * the database. This is the ID that must be supplied to guacd if joining + * this connection. + * + * @return + * The ID of the in-progress connection, as determined by guacd. + */ + public String getConnectionID() { + return connectionID; + } + @Override public String getConnectionIdentifier() { - return connectionIdentifier; + return connection.getIdentifier(); } @Override public void setConnectionIdentifier(String connnectionIdentifier) { - this.connectionIdentifier = connnectionIdentifier; + throw new UnsupportedOperationException("The connection identifier of " + + "TrackedActiveConnection is inherited from the underlying " + + "connection."); } @Override @@ -145,7 +188,8 @@ public class TrackedActiveConnection extends RestrictedObject implements ActiveC @Override public UserCredentials getSharingCredentials(String identifier) throws GuacamoleException { - throw new GuacamoleSecurityException("Permission denied"); + return sharingService.generateTemporaryCredentials(getCurrentUser(), + this, identifier); } @Override diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/ConnectionSharingService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/ConnectionSharingService.java new file mode 100644 index 000000000..7b97f5701 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/ConnectionSharingService.java @@ -0,0 +1,169 @@ +/* + * 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.jdbc.sharing; + +import com.google.inject.Inject; +import java.util.Collections; +import javax.servlet.http.HttpServletRequest; +import org.apache.guacamole.auth.jdbc.user.AuthenticatedUser; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleSecurityException; +import org.apache.guacamole.auth.jdbc.activeconnection.TrackedActiveConnection; +import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile; +import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileService; +import org.apache.guacamole.form.Field; +import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.auth.Credentials; +import org.apache.guacamole.net.auth.credentials.CredentialsInfo; +import org.apache.guacamole.net.auth.credentials.UserCredentials; + +/** + * Service which provides convenience methods for sharing active connections. + * + * @author Michael Jumper + */ +public class ConnectionSharingService { + + /** + * The name of the query parameter that is used when authenticating obtain + * temporary access to a connection. + */ + public static final String SHARE_KEY_NAME = "key"; + + /** + * Generator for sharing keys. + */ + @Inject + private ShareKeyGenerator keyGenerator; + + /** + * Map of all currently-shared connections. + */ + @Inject + private SharedConnectionMap connectionMap; + + /** + * Service for retrieving and manipulating sharing profile objects. + */ + @Inject + private SharingProfileService sharingProfileService; + + /** + * The credentials expected when a user is authenticating using temporary + * credentials in order to obtain access to a single connection. + */ + public static final CredentialsInfo SHARE_KEY = + new CredentialsInfo(Collections.singletonList( + new Field(SHARE_KEY_NAME, Field.Type.QUERY_PARAMETER) + )); + + /** + * Generates a set of temporary credentials which can be used to connect to + * the given connection using the given sharing profile. If the user does + * not have permission to share the connection via the given sharing + * profile, permission will be denied. + * + * @param user + * The user sharing the connection. + * + * @param activeConnection + * The active connection being shared. + * + * @param sharingProfileIdentifier + * The identifier of the sharing profile dictating the semantics or + * restrictions applying to the shared session. + * + * @return + * A newly-generated set of temporary credentials which can be used to + * connect to the given connection. + * + * @throws GuacamoleException + * If permission to share the given connection is denied. + */ + public UserCredentials generateTemporaryCredentials(AuthenticatedUser user, + TrackedActiveConnection activeConnection, + String sharingProfileIdentifier) throws GuacamoleException { + + // Pull sharing profile (verifying access) + ModeledSharingProfile sharingProfile = + sharingProfileService.retrieveObject(user, + sharingProfileIdentifier); + + // Verify that this profile is indeed a sharing profile for the + // requested connection + String connectionIdentifier = activeConnection.getConnectionIdentifier(); + if (sharingProfile == null || !sharingProfile.getPrimaryConnectionIdentifier().equals(connectionIdentifier)) + throw new GuacamoleSecurityException("Permission denied."); + + // Generate a share key for the requested connection + String key = keyGenerator.getShareKey(); + connectionMap.put(key, new SharedConnectionDefinition(activeConnection, + sharingProfile)); + + // Return credentials defining a single expected parameter + return new UserCredentials(SHARE_KEY, + Collections.singletonMap(SHARE_KEY_NAME, key)); + + } + + /** + * Returns a SharedConnectionUser (an implementation of AuthenticatedUser) + * if the given credentials contain a valid share key. The returned user + * will be associated with the single shared connection to which they have + * been granted temporary access. If the share key is invalid, or no share + * key is contained within the given credentials, null is returned. + * + * @param authProvider + * The AuthenticationProvider on behalf of which the user is being + * retrieved. + * + * @param credentials + * The credentials which are expected to contain the share key. + * + * @return + * A SharedConnectionUser with access to a single shared connection, if + * the share key within the given credentials is valid, or null if the + * share key is invalid or absent. + */ + public SharedConnectionUser retrieveSharedConnectionUser( + AuthenticationProvider authProvider, Credentials credentials) { + + // Pull associated HTTP request + HttpServletRequest request = credentials.getRequest(); + if (request == null) + return null; + + // Retrieve the share key from the request + String shareKey = request.getParameter(ConnectionSharingService.SHARE_KEY_NAME); + if (shareKey == null) + return null; + + // Pull the connection definition describing the connection these + // credentials provide access to (if any) + SharedConnectionDefinition definition = connectionMap.get(shareKey); + if (definition == null) + return null; + + // Return temporary in-memory user with access only to the shared connection + return new SharedConnectionUser(authProvider, definition, credentials); + + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/HashSharedConnectionMap.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/HashSharedConnectionMap.java new file mode 100644 index 000000000..e3dff02a7 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/HashSharedConnectionMap.java @@ -0,0 +1,67 @@ +/* + * 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.jdbc.sharing; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A HashMap-based implementation of the SharedConnectionMap. + * + * @author Michael Jumper + */ +public class HashSharedConnectionMap implements SharedConnectionMap { + + /** + * Keeps track of the share key to SharedConnectionDefinition mapping. + */ + private final ConcurrentMap connectionMap = + new ConcurrentHashMap(); + + @Override + public SharedConnectionDefinition get(String key) { + + // There are no null share keys + if (key == null) + return null; + + // Update the last access time and return the SharedConnectionDefinition + return connectionMap.get(key); + + } + + @Override + public void put(String key, SharedConnectionDefinition definition) { + connectionMap.put(key, definition); + } + + @Override + public SharedConnectionDefinition remove(String key) { + + // There are no null share keys + if (key == null) + return null; + + // Attempt to retrieve only if non-null + return connectionMap.remove(key); + + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SecureRandomShareKeyGenerator.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SecureRandomShareKeyGenerator.java new file mode 100644 index 000000000..7cc1823c5 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SecureRandomShareKeyGenerator.java @@ -0,0 +1,45 @@ +/* + * 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.jdbc.sharing; + +import java.security.SecureRandom; +import javax.xml.bind.DatatypeConverter; + +/** + * An implementation of the ShareKeyGenerator which uses SecureRandom to + * generate cryptographically-secure random sharing keys. + * + * @author Michael Jumper + */ +public class SecureRandomShareKeyGenerator implements ShareKeyGenerator { + + /** + * Instance of SecureRandom for generating sharing keys. + */ + private final SecureRandom secureRandom = new SecureRandom(); + + @Override + public String getShareKey() { + byte[] bytes = new byte[33]; + secureRandom.nextBytes(bytes); + return DatatypeConverter.printBase64Binary(bytes); + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/ShareKeyGenerator.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/ShareKeyGenerator.java new file mode 100644 index 000000000..1cf97e175 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/ShareKeyGenerator.java @@ -0,0 +1,39 @@ +/* + * 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.jdbc.sharing; + +/** + * Produces unique keys that can be safely used for the automatically-generated + * "sharing credentials" associated with a shared connection. + * + * @author Michael Jumper + */ +public interface ShareKeyGenerator { + + /** + * Returns a new share key, guaranteed to be unique from all previously- + * returned share keys. + * + * @return + * The new share key. + */ + public String getShareKey(); + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnection.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnection.java new file mode 100644 index 000000000..70b894429 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnection.java @@ -0,0 +1,171 @@ +/* + * 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.jdbc.sharing; + +import com.google.inject.Inject; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.jdbc.activeconnection.TrackedActiveConnection; +import org.apache.guacamole.auth.jdbc.connectiongroup.RootConnectionGroup; +import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile; +import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService; +import org.apache.guacamole.net.GuacamoleTunnel; +import org.apache.guacamole.net.auth.Connection; +import org.apache.guacamole.net.auth.ConnectionRecord; +import org.apache.guacamole.protocol.GuacamoleClientInformation; +import org.apache.guacamole.protocol.GuacamoleConfiguration; + +/** + * A Connection which joins an active connection, limited by restrictions + * defined by a sharing profile. + * + * @author Michael Jumper + */ +public class SharedConnection implements Connection { + + /** + * Service for establishing tunnels to Guacamole connections. + */ + @Inject + private GuacamoleTunnelService tunnelService; + + /** + * Randomly-generated unique identifier, guaranteeing this shared connection + * does not duplicate the identifying information of the underlying + * connection being shared. + */ + private final String identifier = UUID.randomUUID().toString(); + + /** + * The user that successfully authenticated to obtain access to this + * SharedConnection. + */ + private SharedConnectionUser user; + + /** + * The active connection being shared. + */ + private TrackedActiveConnection activeConnection; + + /** + * The sharing profile which dictates the level of access provided to a user + * of the shared connection. + */ + private ModeledSharingProfile sharingProfile; + + /** + * Creates a new SharedConnection which can be used to join the connection + * described by the given SharedConnectionDefinition. + * + * @param user + * The user that successfully authenticated to obtain access to this + * SharedConnection. + * + * @param definition + * The SharedConnectionDefinition dictating the connection being shared + * and any associated restrictions. + */ + public void init(SharedConnectionUser user, SharedConnectionDefinition definition) { + this.user = user; + this.activeConnection = definition.getActiveConnection(); + this.sharingProfile = definition.getSharingProfile(); + } + + @Override + public String getIdentifier() { + return identifier; + } + + @Override + public void setIdentifier(String identifier) { + throw new UnsupportedOperationException("Shared connections are immutable."); + } + + @Override + public String getName() { + return sharingProfile.getName(); + } + + @Override + public void setName(String name) { + throw new UnsupportedOperationException("Shared connections are immutable."); + } + + @Override + public String getParentIdentifier() { + return RootConnectionGroup.IDENTIFIER; + } + + @Override + public void setParentIdentifier(String parentIdentifier) { + throw new UnsupportedOperationException("Shared connections are immutable."); + } + + @Override + public GuacamoleConfiguration getConfiguration() { + GuacamoleConfiguration config = new GuacamoleConfiguration(); + config.setProtocol(activeConnection.getConnection().getConfiguration().getProtocol()); + return config; + } + + @Override + public void setConfiguration(GuacamoleConfiguration config) { + throw new UnsupportedOperationException("Shared connections are immutable."); + } + + @Override + public GuacamoleTunnel connect(GuacamoleClientInformation info) + throws GuacamoleException { + return tunnelService.getGuacamoleTunnel(user, activeConnection, + sharingProfile, info); + } + + @Override + public Map getAttributes() { + return Collections.emptyMap(); + } + + @Override + public void setAttributes(Map attributes) { + // Do nothing - no attributes supported + } + + @Override + public List getHistory() + throws GuacamoleException { + return Collections.emptyList(); + } + + @Override + public Set getSharingProfileIdentifiers() + throws GuacamoleException { + return Collections.emptySet(); + } + + @Override + public int getActiveConnections() { + return 0; + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnectionDefinition.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnectionDefinition.java new file mode 100644 index 000000000..78ed62cdd --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnectionDefinition.java @@ -0,0 +1,87 @@ +/* + * 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.jdbc.sharing; + +import org.apache.guacamole.auth.jdbc.activeconnection.TrackedActiveConnection; +import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile; + +/** + * Defines the semantics/restrictions of a shared connection by associating an + * active connection with a sharing profile. The sharing profile defines the + * access provided to users of the shared active connection through its + * connection parameters. + * + * @author Michael Jumper + */ +public class SharedConnectionDefinition { + + /** + * The active connection being shared. + */ + private final TrackedActiveConnection activeConnection; + + /** + * The sharing profile which dictates the level of access provided to a user + * of the shared connection. + */ + private final ModeledSharingProfile sharingProfile; + + /** + * Creates a new SharedConnectionDefinition which describes an active + * connection that can be joined, including the restrictions dictated by a + * given sharing profile. + * + * @param activeConnection + * The active connection being shared. + * + * @param sharingProfile + * A sharing profile whose associated parameters dictate the level of + * access provided to the shared connection. + */ + public SharedConnectionDefinition(TrackedActiveConnection activeConnection, + ModeledSharingProfile sharingProfile) { + this.activeConnection = activeConnection; + this.sharingProfile = sharingProfile; + } + + /** + * Returns the TrackedActiveConnection of the actual in-progress connection + * being shared. + * + * @return + * The TrackedActiveConnection being shared. + */ + public TrackedActiveConnection getActiveConnection() { + return activeConnection; + } + + /** + * Returns the ModeledSharingProfile whose associated parameters dictate the + * level of access granted to users of the shared connection. + * + * @return + * A ModeledSharingProfile whose associated parameters dictate the + * level of access granted to users of the shared connection. + */ + public ModeledSharingProfile getSharingProfile() { + return sharingProfile; + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnectionMap.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnectionMap.java new file mode 100644 index 000000000..2df4b2d76 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnectionMap.java @@ -0,0 +1,75 @@ +/* + * 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.jdbc.sharing; + +/** + * Represents a mapping between share keys and the Guacamole connection being + * shared. + * + * @author Michael Jumper + */ +public interface SharedConnectionMap { + + /** + * Associates the given share key with a SharedConnectionDefinition, + * allowing the connection it describes to be accessed by users having the + * share key. + * + * @param key + * The share key to use to share the connection described by the given + * SharedConnectionDefinition. + * + * @param definition + * The SharedConnectionDefinition describing the connection being + * shared via the given share key. + */ + public void put(String key, SharedConnectionDefinition definition); + + /** + * Retrieves the connection definition associated with the given share key. + * If no such share key exists, null is returned. + * + * @param key + * The share key associated with the connection definition to be + * returned. + * + * @return + * The connection definition associated with the given share key, or + * null if no such share key exists. + */ + public SharedConnectionDefinition get(String key); + + /** + * Invalidates given share key, if it exists, returning the connection + * definition previously associated with that key. If no such share key + * exists, this function has no effect, and null is returned. + * + * @param key + * The share key associated with the connection definition to be + * removed. + * + * @return + * The connection definition previously associated with the given + * share key, or null if no such share key exists and no connection was + * removed. + */ + public SharedConnectionDefinition remove(String key); + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnectionUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnectionUser.java new file mode 100644 index 000000000..125628a4c --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnectionUser.java @@ -0,0 +1,93 @@ +/* + * 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.jdbc.sharing; + +import java.util.UUID; +import org.apache.guacamole.auth.jdbc.user.RemoteAuthenticatedUser; +import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.auth.Credentials; + +/** + * A temporary user who has authenticated using a share key and thus has + * restricted access to a single shared connection. + * + * @author Michael Jumper + */ +public class SharedConnectionUser extends RemoteAuthenticatedUser { + + /** + * The single shared connection to which this user has access. + */ + private final SharedConnectionDefinition definition; + + /** + * An arbitrary identifier guaranteed to be unique across users. Note that + * because Guacamole users the AuthenticatedUser's identifier as the means + * of determining overall user identity and aggregating data across + * multiple extensions, this identifier MUST NOT match the identifier of + * any possibly existing user (or else the user may unexpectedly gain + * access to another identically-named user's data). + */ + private final String identifier = UUID.randomUUID().toString(); + + /** + * Creates a new SharedConnectionUser with access solely to connection + * described by the given SharedConnectionDefinition. + * + * @param authenticationProvider + * The AuthenticationProvider that has authenticated the given user. + * + * @param definition + * The SharedConnectionDefinition describing the connection that this + * user should have access to, along with any associated restrictions. + * + * @param credentials + * The credentials given by the user when they authenticated. + */ + public SharedConnectionUser(AuthenticationProvider authenticationProvider, + SharedConnectionDefinition definition, Credentials credentials) { + super(authenticationProvider, credentials); + this.definition = definition; + } + + @Override + public String getIdentifier() { + return identifier; + } + + @Override + public void setIdentifier(String identifier) { + throw new UnsupportedOperationException("Shared connection users are immutable"); + } + + /** + * Returns the SharedConnectionDefinition which describes the connection + * that this user should have access to, along with any associated + * restrictions. + * + * @return + * The SharedConnectionDefinition describing the connection that this + * user should have access to, along with any associated restrictions. + */ + public SharedConnectionDefinition getSharedConnectionDefinition() { + return definition; + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnectionUserContext.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnectionUserContext.java new file mode 100644 index 000000000..37b344ab3 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/SharedConnectionUserContext.java @@ -0,0 +1,213 @@ +/* + * 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.jdbc.sharing; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import java.util.Collection; +import java.util.Collections; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.form.Form; +import org.apache.guacamole.net.auth.ActiveConnection; +import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.auth.Connection; +import org.apache.guacamole.net.auth.ConnectionGroup; +import org.apache.guacamole.net.auth.ConnectionRecordSet; +import org.apache.guacamole.net.auth.Directory; +import org.apache.guacamole.net.auth.SharingProfile; +import org.apache.guacamole.net.auth.User; +import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.net.auth.simple.SimpleConnectionDirectory; +import org.apache.guacamole.net.auth.simple.SimpleConnectionGroup; +import org.apache.guacamole.net.auth.simple.SimpleConnectionGroupDirectory; +import org.apache.guacamole.net.auth.simple.SimpleConnectionRecordSet; +import org.apache.guacamole.net.auth.simple.SimpleDirectory; +import org.apache.guacamole.net.auth.simple.SimpleUser; +import org.apache.guacamole.net.auth.simple.SimpleUserDirectory; + +/** + * The user context of a SharedConnectionUser, providing access ONLY to the + * user themselves, the single SharedConnection associated with that user, and + * an internal root connection group containing only that single + * SharedConnection. + * + * @author Michael Jumper + */ +public class SharedConnectionUserContext implements UserContext { + + /** + * Provider for retrieving SharedConnection instances. + */ + @Inject + private Provider connectionProvider; + + /** + * The AuthenticationProvider that created this SharedConnectionUserContext. + */ + @Inject + private AuthenticationProvider authProvider; + + /** + * The user whose level of access is represented by this user context. + */ + private User self; + + /** + * A directory of all connections visible to the user for whom this user + * context was created. + */ + private Directory connectionDirectory; + + /** + * A directory of all connection groups visible to the user for whom this + * user context was created. + */ + private Directory connectionGroupDirectory; + + /** + * A directory of all users visible to the user for whom this user context + * was created. + */ + private Directory userDirectory; + + /** + * The root connection group of the hierarchy containing all connections + * and connection groups visible to the user for whom this user context was + * created. + */ + private ConnectionGroup rootGroup; + + /** + * Creates a new SharedConnectionUserContext which provides access ONLY to + * the given user, the single SharedConnection associated with that user, + * and an internal root connection group containing only that single + * SharedConnection. + * + * @param user + * The SharedConnectionUser for whom this SharedConnectionUserContext + * is being created. + */ + public void init(SharedConnectionUser user) { + + // Get the definition of the shared connection + SharedConnectionDefinition definition = + user.getSharedConnectionDefinition(); + + // Create a single shared connection accessible by the user + SharedConnection connection = connectionProvider.get(); + connection.init(user, definition); + + // Build list of all accessible connection identifiers + Collection connectionIdentifiers = + Collections.singletonList(connection.getIdentifier()); + + // The connection directory should contain only the shared connection + this.connectionDirectory = new SimpleConnectionDirectory( + Collections.singletonList(connection)); + + // The user should have access only to the shared connection and himself + this.self = new SimpleUser(user.getIdentifier(), + Collections.singletonList(user.getIdentifier()), + connectionIdentifiers, + Collections.emptyList()); + + // The root group contains only the shared connection + String rootIdentifier = connection.getParentIdentifier(); + this.rootGroup = new SimpleConnectionGroup(rootIdentifier, rootIdentifier, + connectionIdentifiers, Collections.emptyList()); + + // The connection group directory contains only the root group + this.connectionGroupDirectory = new SimpleConnectionGroupDirectory( + Collections.singletonList(this.rootGroup)); + + // The user directory contains only this user + this.userDirectory = new SimpleUserDirectory(this.self); + + } + + @Override + public User self() { + return self; + } + + @Override + public AuthenticationProvider getAuthenticationProvider() { + return authProvider; + } + + @Override + public Directory getUserDirectory() { + return userDirectory; + } + + @Override + public Directory getConnectionDirectory() + throws GuacamoleException { + return connectionDirectory; + } + + @Override + public Directory getConnectionGroupDirectory() { + return connectionGroupDirectory; + } + + @Override + public Directory getActiveConnectionDirectory() + throws GuacamoleException { + return new SimpleDirectory(); + } + + @Override + public Directory getSharingProfileDirectory() + throws GuacamoleException { + return new SimpleDirectory(); + } + + @Override + public ConnectionRecordSet getConnectionHistory() { + return new SimpleConnectionRecordSet(); + } + + @Override + public ConnectionGroup getRootConnectionGroup() { + return rootGroup; + } + + @Override + public Collection
getUserAttributes() { + return Collections.emptyList(); + } + + @Override + public Collection getConnectionAttributes() { + return Collections.emptyList(); + } + + @Override + public Collection getConnectionGroupAttributes() { + return Collections.emptyList(); + } + + @Override + public Collection getSharingProfileAttributes() { + return Collections.emptyList(); + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java index 82af02db9..d82563195 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java @@ -38,10 +38,11 @@ import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper; import org.apache.guacamole.auth.jdbc.connection.ConnectionModel; import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel; import org.apache.guacamole.auth.jdbc.connection.ConnectionParameterModel; -import org.apache.guacamole.auth.jdbc.user.UserModel; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleResourceNotFoundException; import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.auth.jdbc.JDBCEnvironment; +import org.apache.guacamole.auth.jdbc.activeconnection.TrackedActiveConnection; import org.apache.guacamole.auth.jdbc.connection.ConnectionMapper; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.net.GuacamoleSocket; @@ -55,6 +56,11 @@ import org.apache.guacamole.token.StandardTokens; import org.apache.guacamole.token.TokenFilter; import org.mybatis.guice.transactional.Transactional; import org.apache.guacamole.auth.jdbc.connection.ConnectionParameterMapper; +import org.apache.guacamole.auth.jdbc.sharing.SharedConnectionUser; +import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile; +import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileParameterMapper; +import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileParameterModel; +import org.apache.guacamole.auth.jdbc.user.RemoteAuthenticatedUser; /** @@ -88,7 +94,13 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS * Mapper for accessing connection parameters. */ @Inject - private ConnectionParameterMapper parameterMapper; + private ConnectionParameterMapper connectionParameterMapper; + + /** + * Mapper for accessing sharing profile parameters. + */ + @Inject + private SharingProfileParameterMapper sharingProfileParameterMapper; /** * Mapper for accessing connection history. @@ -141,7 +153,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS * @throws GuacamoleException * If access is denied to the given user for any reason. */ - protected abstract ModeledConnection acquire(AuthenticatedUser user, + protected abstract ModeledConnection acquire(RemoteAuthenticatedUser user, List connections) throws GuacamoleException; /** @@ -155,7 +167,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS * @param connection * The connection being released. */ - protected abstract void release(AuthenticatedUser user, + protected abstract void release(RemoteAuthenticatedUser user, ModeledConnection connection); /** @@ -172,7 +184,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS * @throws GuacamoleException * If access is denied to the given user for any reason. */ - protected abstract void acquire(AuthenticatedUser user, + protected abstract void acquire(RemoteAuthenticatedUser user, ModeledConnectionGroup connectionGroup) throws GuacamoleException; /** @@ -186,7 +198,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS * @param connectionGroup * The connection group being released. */ - protected abstract void release(AuthenticatedUser user, + protected abstract void release(RemoteAuthenticatedUser user, ModeledConnectionGroup connectionGroup); /** @@ -206,7 +218,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS * A GuacamoleConfiguration containing the protocol and parameters from * the given connection. */ - private GuacamoleConfiguration getGuacamoleConfiguration(AuthenticatedUser user, + private GuacamoleConfiguration getGuacamoleConfiguration(RemoteAuthenticatedUser user, ModeledConnection connection) { // Generate configuration from available data @@ -217,7 +229,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS config.setProtocol(model.getProtocol()); // Set parameters from associated data - Collection parameters = parameterMapper.select(connection.getIdentifier()); + Collection parameters = connectionParameterMapper.select(connection.getIdentifier()); for (ConnectionParameterModel parameter : parameters) config.setParameter(parameter.getName(), parameter.getValue()); @@ -232,6 +244,52 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS } + /** + * Returns a guacamole configuration which joins the active connection + * having the given ID, using the provided sharing profile to restrict the + * access provided to the user accessing the shared connection. If tokens + * are used in the connection parameter values of the sharing profile, + * credentials from the given user will be substituted appropriately. + * + * @param user + * The user whose credentials should be used if necessary. + * + * @param sharingProfile + * The sharing profile whose associated parameters dictate the level + * of access granted to the user joining the connection. + * + * @param connectionID + * The ID of the connection being joined, as provided by guacd when the + * original connection was established, or null if a new connection + * should be created instead. + * + * @return + * A GuacamoleConfiguration containing the protocol and parameters from + * the given connection. + */ + private GuacamoleConfiguration getGuacamoleConfiguration(RemoteAuthenticatedUser user, + ModeledSharingProfile sharingProfile, String connectionID) { + + // Generate configuration from available data + GuacamoleConfiguration config = new GuacamoleConfiguration(); + config.setConnectionID(connectionID); + + // Set parameters from associated data + Collection parameters = sharingProfileParameterMapper.select(sharingProfile.getIdentifier()); + for (SharingProfileParameterModel parameter : parameters) + config.setParameter(parameter.getName(), parameter.getValue()); + + // Build token filter containing credential tokens + TokenFilter tokenFilter = new TokenFilter(); + StandardTokens.addStandardTokens(tokenFilter, user.getCredentials()); + + // Filter the configuration + tokenFilter.filterValues(config.getParameters()); + + return config; + + } + /** * Saves the given ActiveConnectionRecord to the database. The end date of * the saved record will be populated with the current time. @@ -241,17 +299,15 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS */ private void saveConnectionRecord(ActiveConnectionRecord record) { - // Get associated connection - ModeledConnection connection = record.getConnection(); - // Get associated models - AuthenticatedUser user = record.getUser(); ConnectionRecordModel recordModel = new ConnectionRecordModel(); // Copy user information and timestamps into new record - recordModel.setUsername(user.getIdentifier()); - recordModel.setConnectionIdentifier(connection.getIdentifier()); - recordModel.setConnectionName(connection.getName()); + recordModel.setUsername(record.getUsername()); + recordModel.setConnectionIdentifier(record.getConnectionIdentifier()); + recordModel.setConnectionName(record.getConnectionName()); + recordModel.setSharingProfileIdentifier(record.getSharingProfileIdentifier()); + recordModel.setSharingProfileName(record.getSharingProfileName()); recordModel.setStartDate(record.getStartDate()); recordModel.setEndDate(new Date()); @@ -329,19 +385,26 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS if (!hasRun.compareAndSet(false, true)) return; - // Get original user and connection - AuthenticatedUser user = activeConnection.getUser(); - ModeledConnection connection = activeConnection.getConnection(); - - // Get associated identifiers - String identifier = connection.getIdentifier(); - String parentIdentifier = connection.getParentIdentifier(); - - // Release connection + // Remove underlying tunnel from list of active tunnels activeTunnels.remove(activeConnection.getUUID().toString()); - activeConnections.remove(identifier, activeConnection); - activeConnectionGroups.remove(parentIdentifier, activeConnection); - release(user, connection); + + // Get original user + RemoteAuthenticatedUser user = activeConnection.getUser(); + + // Release the associated connection if this is the primary connection + if (activeConnection.isPrimaryConnection()) { + + // Get connection and associated identifiers + ModeledConnection connection = activeConnection.getConnection(); + String identifier = connection.getIdentifier(); + String parentIdentifier = connection.getParentIdentifier(); + + // Release connection + activeConnections.remove(identifier, activeConnection); + activeConnectionGroups.remove(parentIdentifier, activeConnection); + release(user, connection); + + } // Release any associated group if (activeConnection.hasBalancingGroup()) @@ -379,25 +442,44 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS * while connection configuration information is being retrieved. */ private GuacamoleTunnel assignGuacamoleTunnel(ActiveConnectionRecord activeConnection, - GuacamoleClientInformation info) - throws GuacamoleException { + GuacamoleClientInformation info) throws GuacamoleException { - ModeledConnection connection = activeConnection.getConnection(); - // Record new active connection Runnable cleanupTask = new ConnectionCleanupTask(activeConnection); activeTunnels.put(activeConnection.getUUID().toString(), activeConnection); - activeConnections.put(connection.getIdentifier(), activeConnection); - activeConnectionGroups.put(connection.getParentIdentifier(), activeConnection); try { + GuacamoleConfiguration config; + + // Pull configuration directly from the connection if we are not + // joining an active connection + if (activeConnection.isPrimaryConnection()) { + ModeledConnection connection = activeConnection.getConnection(); + activeConnections.put(connection.getIdentifier(), activeConnection); + activeConnectionGroups.put(connection.getParentIdentifier(), activeConnection); + config = getGuacamoleConfiguration(activeConnection.getUser(), connection); + } + + // If we ARE joining an active connection, generate a configuration + // which does so + else { + + // Verify that the connection ID is known + String connectionID = activeConnection.getConnectionID(); + if (!activeConnection.isActive() || connectionID == null) + throw new GuacamoleResourceNotFoundException("No existing connection to be joined."); + + // Build configuration from the sharing profile and the ID of + // the connection being joined + config = getGuacamoleConfiguration(activeConnection.getUser(), + activeConnection.getSharingProfile(), connectionID); + + } + // Obtain socket which will automatically run the cleanup task - GuacamoleSocket socket = new ConfiguredGuacamoleSocket( - getUnconfiguredGuacamoleSocket(cleanupTask), - getGuacamoleConfiguration(activeConnection.getUser(), connection), - info - ); + ConfiguredGuacamoleSocket socket = new ConfiguredGuacamoleSocket( + getUnconfiguredGuacamoleSocket(cleanupTask), config, info); // Assign and return new tunnel return activeConnection.assignGuacamoleTunnel(socket); @@ -596,4 +678,17 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS } + @Override + @Transactional + public GuacamoleTunnel getGuacamoleTunnel(SharedConnectionUser user, + TrackedActiveConnection activeConnection, + ModeledSharingProfile sharingProfile, + GuacamoleClientInformation info) + throws GuacamoleException { + + // Connect to shared connection + return assignGuacamoleTunnel(new ActiveConnectionRecord(user, activeConnection, sharingProfile), info); + + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/ActiveConnectionRecord.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/ActiveConnectionRecord.java index a34e339c3..16da6899b 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/ActiveConnectionRecord.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/ActiveConnectionRecord.java @@ -21,20 +21,22 @@ package org.apache.guacamole.auth.jdbc.tunnel; import java.util.Date; import java.util.UUID; +import org.apache.guacamole.auth.jdbc.activeconnection.TrackedActiveConnection; import org.apache.guacamole.auth.jdbc.connection.ModeledConnection; import org.apache.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; -import org.apache.guacamole.auth.jdbc.user.AuthenticatedUser; +import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile; +import org.apache.guacamole.auth.jdbc.user.RemoteAuthenticatedUser; import org.apache.guacamole.net.AbstractGuacamoleTunnel; import org.apache.guacamole.net.GuacamoleSocket; import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.auth.ConnectionRecord; +import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket; /** * A connection record implementation that describes an active connection. As * the associated connection has not yet ended, getEndDate() will always return - * null, and isActive() will always return true. The associated start date will - * be the time of this objects creation. + * null. The associated start date will be the time of this objects creation. * * @author Michael Jumper */ @@ -44,7 +46,7 @@ public class ActiveConnectionRecord implements ConnectionRecord { * The user that connected to the connection associated with this connection * record. */ - private final AuthenticatedUser user; + private final RemoteAuthenticatedUser user; /** * The balancing group from which the associated connection was chosen, if @@ -57,6 +59,13 @@ public class ActiveConnectionRecord implements ConnectionRecord { */ private final ModeledConnection connection; + /** + * The sharing profile that was used to access the connection associated + * with this connection record. If the connection was accessed directly + * (without involving a sharing profile), this will be null. + */ + private final ModeledSharingProfile sharingProfile; + /** * The time this connection record was created. */ @@ -66,13 +75,54 @@ public class ActiveConnectionRecord implements ConnectionRecord { * The UUID that will be assigned to the underlying tunnel. */ private final UUID uuid = UUID.randomUUID(); + + /** + * The connection ID of the connection as determined by guacd, not to be + * confused with the connection identifier determined by the database. This + * is the ID that must be supplied to guacd if joining this connection. + */ + private String connectionID; /** * The GuacamoleTunnel used by the connection associated with this * connection record. */ private GuacamoleTunnel tunnel; - + + /** + * Creates a new connection record associated with the given user, + * connection, balancing connection group, and sharing profile. The given + * balancing connection group MUST be the connection group from which the + * given connection was chosen, and the given sharing profile MUST be the + * sharing profile that was used to share access to the given connection. + * The start date of this connection record will be the time of its + * creation. + * + * @param user + * The user that connected to the connection associated with this + * connection record. + * + * @param balancingGroup + * The balancing group from which the given connection was chosen, or + * null if no balancing group is being used. + * + * @param connection + * The connection to associate with this connection record. + * + * @param sharingProfile + * The sharing profile that was used to share access to the given + * connection, or null if no sharing profile was used. + */ + private ActiveConnectionRecord(RemoteAuthenticatedUser user, + ModeledConnectionGroup balancingGroup, + ModeledConnection connection, + ModeledSharingProfile sharingProfile) { + this.user = user; + this.balancingGroup = balancingGroup; + this.connection = connection; + this.sharingProfile = sharingProfile; + } + /** * Creates a new connection record associated with the given user, * connection, and balancing connection group. The given balancing @@ -90,12 +140,10 @@ public class ActiveConnectionRecord implements ConnectionRecord { * @param connection * The connection to associate with this connection record. */ - public ActiveConnectionRecord(AuthenticatedUser user, + public ActiveConnectionRecord(RemoteAuthenticatedUser user, ModeledConnectionGroup balancingGroup, ModeledConnection connection) { - this.user = user; - this.balancingGroup = balancingGroup; - this.connection = connection; + this(user, balancingGroup, connection, null); } /** @@ -110,11 +158,38 @@ public class ActiveConnectionRecord implements ConnectionRecord { * @param connection * The connection to associate with this connection record. */ - public ActiveConnectionRecord(AuthenticatedUser user, + public ActiveConnectionRecord(RemoteAuthenticatedUser user, ModeledConnection connection) { this(user, null, connection); } + /** + * Creates a new connection record associated with the given user, active + * connection, and sharing profile. The given sharing profile MUST be the + * sharing profile that was used to share access to the given connection. + * The start date of this connection record will be the time of its + * creation. + * + * @param user + * The user that connected to the connection associated with this + * connection record. + * + * @param activeConnection + * The active connection which is being shared to the given user via + * the given sharing profile. + * + * @param sharingProfile + * The sharing profile that was used to share access to the given + * connection. As a record created in this way always refers to a + * shared connection, this value may NOT be null. + */ + public ActiveConnectionRecord(RemoteAuthenticatedUser user, + TrackedActiveConnection activeConnection, + ModeledSharingProfile sharingProfile) { + this(user, null, activeConnection.getConnection(), sharingProfile); + this.connectionID = activeConnection.getConnectionID(); + } + /** * Returns the user that connected to the connection associated with this * connection record. @@ -123,7 +198,7 @@ public class ActiveConnectionRecord implements ConnectionRecord { * The user that connected to the connection associated with this * connection record. */ - public AuthenticatedUser getUser() { + public RemoteAuthenticatedUser getUser() { return user; } @@ -149,6 +224,20 @@ public class ActiveConnectionRecord implements ConnectionRecord { return connection; } + /** + * Returns the sharing profile that was used to access the connection + * associated with this connection record. If the connection was accessed + * directly (without involving a sharing profile), this will be null. + * + * @return + * The sharing profile that was used to access the connection + * associated with this connection record, or null if the connection + * was accessed directly. + */ + public ModeledSharingProfile getSharingProfile() { + return sharingProfile; + } + /** * Returns whether the connection associated with this connection record * was chosen from a balancing group. @@ -161,6 +250,21 @@ public class ActiveConnectionRecord implements ConnectionRecord { return balancingGroup != null; } + /** + * Returns whether this connection record is associated with a connection + * being used directly, in the absence of a sharing profile. If a connection + * is shared, this will continue to return false for the connection being + * shared, but will return true for the connections which join that + * connection. + * + * @return + * true if the connection associated with this connection record is + * being used directly, false otherwise. + */ + public boolean isPrimaryConnection() { + return sharingProfile == null; + } + @Override public String getConnectionIdentifier() { return connection.getIdentifier(); @@ -173,12 +277,26 @@ public class ActiveConnectionRecord implements ConnectionRecord { @Override public String getSharingProfileIdentifier() { + + // Return sharing profile identifier if known + if (sharingProfile != null) + return sharingProfile.getIdentifier(); + + // No associated sharing profile return null; + } @Override public String getSharingProfileName() { + + // Return sharing profile name if known + if (sharingProfile != null) + return sharingProfile.getName(); + + // No associated sharing profile return null; + } @Override @@ -201,15 +319,12 @@ public class ActiveConnectionRecord implements ConnectionRecord { @Override public String getUsername() { - return user.getUser().getIdentifier(); + return user.getIdentifier(); } @Override public boolean isActive() { - - // Active connections are active by definition - return true; - + return tunnel != null && tunnel.isOpen(); } /** @@ -229,13 +344,13 @@ public class ActiveConnectionRecord implements ConnectionRecord { * given socket. * * @param socket - * The GuacamoleSocket to use to create the tunnel associated with this - * connection record. + * The ConfiguredGuacamoleSocket to use to create the tunnel associated + * with this connection record. * * @return * The newly-created tunnel associated with this connection record. */ - public GuacamoleTunnel assignGuacamoleTunnel(final GuacamoleSocket socket) { + public GuacamoleTunnel assignGuacamoleTunnel(final ConfiguredGuacamoleSocket socket) { // Create tunnel with given socket this.tunnel = new AbstractGuacamoleTunnel() { @@ -252,6 +367,10 @@ public class ActiveConnectionRecord implements ConnectionRecord { }; + // Store connection ID of the primary connection only + if (isPrimaryConnection()) + this.connectionID = socket.getConnectionID(); + // Return newly-created tunnel return this.tunnel; @@ -268,5 +387,20 @@ public class ActiveConnectionRecord implements ConnectionRecord { public UUID getUUID() { return uuid; } - + + /** + * Returns the connection ID of the in-progress connection as determined by + * guacd, not to be confused with the connection identifier determined by + * the database. This is the ID that must be supplied to guacd if joining + * this connection. If the in-progress connection is joining another + * connection, this will be the ID of the connection being joined, NOT the + * ID of the connection directly represented by this record. + * + * @return + * The ID of the in-progress connection, as determined by guacd. + */ + public String getConnectionID() { + return connectionID; + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/GuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/GuacamoleTunnelService.java index 554967b3a..6a00b2e2c 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/GuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/GuacamoleTunnelService.java @@ -24,6 +24,9 @@ import org.apache.guacamole.auth.jdbc.user.AuthenticatedUser; import org.apache.guacamole.auth.jdbc.connection.ModeledConnection; import org.apache.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.jdbc.activeconnection.TrackedActiveConnection; +import org.apache.guacamole.auth.jdbc.sharing.SharedConnectionUser; +import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile; import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; @@ -145,4 +148,39 @@ public interface GuacamoleTunnelService { */ public Collection getActiveConnections(ConnectionGroup connectionGroup); + /** + * Creates a socket for the given user which joins the given active + * connection. The given client information will be passed to guacd when + * the connection is established. This function will apply any concurrent + * usage rules in effect, but will NOT test object- or system-level + * permissions. + * + * @param user + * The user for whom the connection is being established. + * + * @param activeConnection + * The active connection the user is joining. + * + * @param sharingProfile + * The sharing profile whose associated parameters dictate the level + * of access granted to the user joining the connection. + * + * @param info + * Information describing the Guacamole client connecting to the given + * connection. + * + * @return + * A new GuacamoleTunnel which is configured and connected to the given + * active connection. + * + * @throws GuacamoleException + * If the connection cannot be established due to concurrent usage + * rules. + */ + GuacamoleTunnel getGuacamoleTunnel(SharedConnectionUser user, + TrackedActiveConnection activeConnection, + ModeledSharingProfile sharingProfile, + GuacamoleClientInformation info) + throws GuacamoleException; + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/RestrictedGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/RestrictedGuacamoleTunnelService.java index 4163adc69..ca9234111 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/RestrictedGuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/RestrictedGuacamoleTunnelService.java @@ -27,12 +27,12 @@ import java.util.Comparator; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.apache.guacamole.GuacamoleClientTooManyException; -import org.apache.guacamole.auth.jdbc.user.AuthenticatedUser; import org.apache.guacamole.auth.jdbc.connection.ModeledConnection; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleResourceConflictException; import org.apache.guacamole.auth.jdbc.JDBCEnvironment; import org.apache.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; +import org.apache.guacamole.auth.jdbc.user.RemoteAuthenticatedUser; /** @@ -166,7 +166,7 @@ public class RestrictedGuacamoleTunnelService } @Override - protected ModeledConnection acquire(AuthenticatedUser user, + protected ModeledConnection acquire(RemoteAuthenticatedUser user, List connections) throws GuacamoleException { // Do not acquire connection unless within overall limits @@ -174,7 +174,7 @@ public class RestrictedGuacamoleTunnelService throw new GuacamoleResourceConflictException("Cannot connect. Overall maximum connections reached."); // Get username - String username = user.getUser().getIdentifier(); + String username = user.getIdentifier(); // Sort connections in ascending order of usage ModeledConnection[] sortedConnections = connections.toArray(new ModeledConnection[connections.size()]); @@ -230,18 +230,18 @@ public class RestrictedGuacamoleTunnelService } @Override - protected void release(AuthenticatedUser user, ModeledConnection connection) { - activeSeats.remove(new Seat(user.getUser().getIdentifier(), connection.getIdentifier())); + protected void release(RemoteAuthenticatedUser user, ModeledConnection connection) { + activeSeats.remove(new Seat(user.getIdentifier(), connection.getIdentifier())); activeConnections.remove(connection.getIdentifier()); totalActiveConnections.decrementAndGet(); } @Override - protected void acquire(AuthenticatedUser user, + protected void acquire(RemoteAuthenticatedUser user, ModeledConnectionGroup connectionGroup) throws GuacamoleException { // Get username - String username = user.getUser().getIdentifier(); + String username = user.getIdentifier(); // Attempt to aquire connection group according to per-user limits Seat seat = new Seat(username, connectionGroup.getIdentifier()); @@ -267,9 +267,9 @@ public class RestrictedGuacamoleTunnelService } @Override - protected void release(AuthenticatedUser user, + protected void release(RemoteAuthenticatedUser user, ModeledConnectionGroup connectionGroup) { - activeGroupSeats.remove(new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier())); + activeGroupSeats.remove(new Seat(user.getIdentifier(), connectionGroup.getIdentifier())); activeGroups.remove(connectionGroup.getIdentifier()); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/AuthenticatedUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/AuthenticatedUser.java index 0696c8801..d58c2e316 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/AuthenticatedUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/AuthenticatedUser.java @@ -22,9 +22,6 @@ package org.apache.guacamole.auth.jdbc.user; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; @@ -33,49 +30,13 @@ import org.apache.guacamole.net.auth.Credentials; * * @author Michael Jumper */ -public class AuthenticatedUser implements org.apache.guacamole.net.auth.AuthenticatedUser { +public class AuthenticatedUser extends RemoteAuthenticatedUser { /** * The user that authenticated. */ private final ModeledUser user; - /** - * The credentials given when this user authenticated. - */ - private final Credentials credentials; - - /** - * The AuthenticationProvider that authenticated this user. - */ - private final AuthenticationProvider authenticationProvider; - - /** - * The host from which this user authenticated. - */ - private final String remoteHost; - - /** - * Regular expression which matches any IPv4 address. - */ - private static final String IPV4_ADDRESS_REGEX = "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})"; - - /** - * Regular expression which matches any IPv6 address. - */ - private static final String IPV6_ADDRESS_REGEX = "([0-9a-fA-F]*(:[0-9a-fA-F]*){0,7})"; - - /** - * Regular expression which matches any IP address, regardless of version. - */ - private static final String IP_ADDRESS_REGEX = "(" + IPV4_ADDRESS_REGEX + "|" + IPV6_ADDRESS_REGEX + ")"; - - /** - * Pattern which matches valid values of the de-facto standard - * "X-Forwarded-For" header. - */ - private static final Pattern X_FORWARDED_FOR = Pattern.compile("^" + IP_ADDRESS_REGEX + "(, " + IP_ADDRESS_REGEX + ")*$"); - /** * The connections which have been committed for use by this user in the * context of a balancing connection group. Balancing connection groups @@ -87,38 +48,6 @@ public class AuthenticatedUser implements org.apache.guacamole.net.auth.Authenti private final Set preferredConnections = Collections.newSetFromMap(new ConcurrentHashMap()); - /** - * Derives the remote host of the authenticating user from the given - * credentials object. The remote host is derived from X-Forwarded-For - * in addition to the actual source IP of the request, and thus is not - * trusted. The derived remote host is really only useful for logging, - * unless the server is configured such that X-Forwarded-For is guaranteed - * to be trustworthy. - * - * @param credentials - * The credentials to derive the remote host from. - * - * @return - * The remote host from which the user with the given credentials is - * authenticating. - */ - private static String getRemoteHost(Credentials credentials) { - - HttpServletRequest request = credentials.getRequest(); - - // Use X-Forwarded-For, if present and valid - String header = request.getHeader("X-Forwarded-For"); - if (header != null) { - Matcher matcher = X_FORWARDED_FOR.matcher(header); - if (matcher.matches()) - return matcher.group(1); - } - - // If header absent or invalid, just use source IP - return request.getRemoteAddr(); - - } - /** * Creates a new AuthenticatedUser associating the given user with their * corresponding credentials. @@ -134,10 +63,8 @@ public class AuthenticatedUser implements org.apache.guacamole.net.auth.Authenti */ public AuthenticatedUser(AuthenticationProvider authenticationProvider, ModeledUser user, Credentials credentials) { - this.authenticationProvider = authenticationProvider; + super(authenticationProvider, credentials); this.user = user; - this.credentials = credentials; - this.remoteHost = getRemoteHost(credentials); } /** @@ -150,27 +77,6 @@ public class AuthenticatedUser implements org.apache.guacamole.net.auth.Authenti return user; } - /** - * Returns the credentials given during authentication by this user. - * - * @return - * The credentials given during authentication by this user. - */ - @Override - public Credentials getCredentials() { - return credentials; - } - - /** - * Returns the host from which this user authenticated. - * - * @return - * The host from which this user authenticated. - */ - public String getRemoteHost() { - return remoteHost; - } - /** * Returns whether the connection having the given identifier has been * marked as preferred for this user's current Guacamole session. A @@ -201,11 +107,6 @@ public class AuthenticatedUser implements org.apache.guacamole.net.auth.Authenti preferredConnections.add(identifier); } - @Override - public AuthenticationProvider getAuthenticationProvider() { - return authenticationProvider; - } - @Override public String getIdentifier() { return user.getIdentifier(); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/AuthenticationProviderService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/AuthenticationProviderService.java index 2c4484c9e..3e61cf536 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/AuthenticationProviderService.java @@ -22,6 +22,10 @@ package org.apache.guacamole.auth.jdbc.user; import com.google.inject.Inject; import com.google.inject.Provider; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.jdbc.sharing.ConnectionSharingService; +import org.apache.guacamole.auth.jdbc.sharing.SharedConnectionUser; +import org.apache.guacamole.auth.jdbc.sharing.SharedConnectionUserContext; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.credentials.CredentialsInfo; @@ -48,6 +52,18 @@ public class AuthenticationProviderService { @Inject private Provider userContextProvider; + /** + * Provider for retrieving SharedConnectionUserContext instances. + */ + @Inject + private Provider sharedUserContextProvider; + + /** + * Service for sharing active connections. + */ + @Inject + private ConnectionSharingService sharingService; + /** * Authenticates the user having the given credentials, returning a new * AuthenticatedUser instance only if the credentials are valid. If the @@ -72,8 +88,15 @@ public class AuthenticationProviderService { public AuthenticatedUser authenticateUser(AuthenticationProvider authenticationProvider, Credentials credentials) throws GuacamoleException { + AuthenticatedUser user; + + // Check whether user is authenticating with a valid sharing key + user = sharingService.retrieveSharedConnectionUser(authenticationProvider, credentials); + if (user != null) + return user; + // Authenticate user - AuthenticatedUser user = userService.retrieveAuthenticatedUser(authenticationProvider, credentials); + user = userService.retrieveAuthenticatedUser(authenticationProvider, credentials); if (user != null) return user; @@ -98,8 +121,15 @@ public class AuthenticationProviderService { * If an error occurs during authentication, or if the given * credentials are invalid or expired. */ - public UserContext getUserContext(org.apache.guacamole.net.auth.AuthenticatedUser authenticatedUser) - throws GuacamoleException { + public org.apache.guacamole.net.auth.UserContext getUserContext( + AuthenticatedUser authenticatedUser) throws GuacamoleException { + + // Produce sharing-specific user context if this is the user of a shared connection + if (authenticatedUser instanceof SharedConnectionUser) { + SharedConnectionUserContext context = sharedUserContextProvider.get(); + context.init((SharedConnectionUser) authenticatedUser); + return context; + } // Retrieve user account for already-authenticated user ModeledUser user = userService.retrieveUser(authenticatedUser); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java new file mode 100644 index 000000000..cb89b50b8 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java @@ -0,0 +1,141 @@ +/* + * 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.jdbc.user; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; +import org.apache.guacamole.net.auth.AuthenticatedUser; +import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.auth.Credentials; + +/** + * An AuthenticatedUser that has an associated remote host. + * + * @author Michael Jumper + */ +public abstract class RemoteAuthenticatedUser implements AuthenticatedUser { + + /** + * The credentials given when this user authenticated. + */ + private final Credentials credentials; + + /** + * The AuthenticationProvider that authenticated this user. + */ + private final AuthenticationProvider authenticationProvider; + + /** + * The host from which this user authenticated. + */ + private final String remoteHost; + + /** + * Regular expression which matches any IPv4 address. + */ + private static final String IPV4_ADDRESS_REGEX = "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})"; + + /** + * Regular expression which matches any IPv6 address. + */ + private static final String IPV6_ADDRESS_REGEX = "([0-9a-fA-F]*(:[0-9a-fA-F]*){0,7})"; + + /** + * Regular expression which matches any IP address, regardless of version. + */ + private static final String IP_ADDRESS_REGEX = "(" + IPV4_ADDRESS_REGEX + "|" + IPV6_ADDRESS_REGEX + ")"; + + /** + * Pattern which matches valid values of the de-facto standard + * "X-Forwarded-For" header. + */ + private static final Pattern X_FORWARDED_FOR = Pattern.compile("^" + IP_ADDRESS_REGEX + "(, " + IP_ADDRESS_REGEX + ")*$"); + + /** + * Derives the remote host of the authenticating user from the given + * credentials object. The remote host is derived from X-Forwarded-For + * in addition to the actual source IP of the request, and thus is not + * trusted. The derived remote host is really only useful for logging, + * unless the server is configured such that X-Forwarded-For is guaranteed + * to be trustworthy. + * + * @param credentials + * The credentials to derive the remote host from. + * + * @return + * The remote host from which the user with the given credentials is + * authenticating. + */ + private static String getRemoteHost(Credentials credentials) { + + HttpServletRequest request = credentials.getRequest(); + + // Use X-Forwarded-For, if present and valid + String header = request.getHeader("X-Forwarded-For"); + if (header != null) { + Matcher matcher = X_FORWARDED_FOR.matcher(header); + if (matcher.matches()) + return matcher.group(1); + } + + // If header absent or invalid, just use source IP + return request.getRemoteAddr(); + + } + + /** + * Creates a new RemoteAuthenticatedUser, deriving the associated remote + * host from the given credentials. + * + * @param authenticationProvider + * The AuthenticationProvider that has authenticated the given user. + * + * @param credentials + * The credentials given by the user when they authenticated. + */ + public RemoteAuthenticatedUser(AuthenticationProvider authenticationProvider, + Credentials credentials) { + this.authenticationProvider = authenticationProvider; + this.credentials = credentials; + this.remoteHost = getRemoteHost(credentials); + } + + @Override + public Credentials getCredentials() { + return credentials; + } + + /** + * Returns the host from which this user authenticated. + * + * @return + * The host from which this user authenticated. + */ + public String getRemoteHost() { + return remoteHost; + } + + @Override + public AuthenticationProvider getAuthenticationProvider() { + return authenticationProvider; + } + +}