GUACAMOLE-5: Merge database connection sharing implementation.

This commit is contained in:
James Muehlner
2016-07-21 19:50:00 -07:00
18 changed files with 1530 additions and 179 deletions

View File

@@ -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);

View File

@@ -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();
@@ -122,14 +138,41 @@ public class TrackedActiveConnection extends RestrictedObject implements ActiveC
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

View File

@@ -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.<Field>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);
}
}

View File

@@ -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<String, SharedConnectionDefinition> connectionMap =
new ConcurrentHashMap<String, SharedConnectionDefinition>();
@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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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<String, String> getAttributes() {
return Collections.<String, String>emptyMap();
}
@Override
public void setAttributes(Map<String, String> attributes) {
// Do nothing - no attributes supported
}
@Override
public List<? extends ConnectionRecord> getHistory()
throws GuacamoleException {
return Collections.<ConnectionRecord>emptyList();
}
@Override
public Set<String> getSharingProfileIdentifiers()
throws GuacamoleException {
return Collections.<String>emptySet();
}
@Override
public int getActiveConnections() {
return 0;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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<SharedConnection> 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<Connection> connectionDirectory;
/**
* A directory of all connection groups visible to the user for whom this
* user context was created.
*/
private Directory<ConnectionGroup> connectionGroupDirectory;
/**
* A directory of all users visible to the user for whom this user context
* was created.
*/
private Directory<User> 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<String> connectionIdentifiers =
Collections.singletonList(connection.getIdentifier());
// The connection directory should contain only the shared connection
this.connectionDirectory = new SimpleConnectionDirectory(
Collections.<Connection>singletonList(connection));
// The user should have access only to the shared connection and himself
this.self = new SimpleUser(user.getIdentifier(),
Collections.<String>singletonList(user.getIdentifier()),
connectionIdentifiers,
Collections.<String>emptyList());
// The root group contains only the shared connection
String rootIdentifier = connection.getParentIdentifier();
this.rootGroup = new SimpleConnectionGroup(rootIdentifier, rootIdentifier,
connectionIdentifiers, Collections.<String>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<User> getUserDirectory() {
return userDirectory;
}
@Override
public Directory<Connection> getConnectionDirectory()
throws GuacamoleException {
return connectionDirectory;
}
@Override
public Directory<ConnectionGroup> getConnectionGroupDirectory() {
return connectionGroupDirectory;
}
@Override
public Directory<ActiveConnection> getActiveConnectionDirectory()
throws GuacamoleException {
return new SimpleDirectory<ActiveConnection>();
}
@Override
public Directory<SharingProfile> getSharingProfileDirectory()
throws GuacamoleException {
return new SimpleDirectory<SharingProfile>();
}
@Override
public ConnectionRecordSet getConnectionHistory() {
return new SimpleConnectionRecordSet();
}
@Override
public ConnectionGroup getRootConnectionGroup() {
return rootGroup;
}
@Override
public Collection<Form> getUserAttributes() {
return Collections.<Form>emptyList();
}
@Override
public Collection<Form> getConnectionAttributes() {
return Collections.<Form>emptyList();
}
@Override
public Collection<Form> getConnectionGroupAttributes() {
return Collections.<Form>emptyList();
}
@Override
public Collection<Form> getSharingProfileAttributes() {
return Collections.<Form>emptyList();
}
}

View File

@@ -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<ModeledConnection> 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<ConnectionParameterModel> parameters = parameterMapper.select(connection.getIdentifier());
Collection<ConnectionParameterModel> 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<SharingProfileParameterModel> 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,20 +385,27 @@ 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();
// Remove underlying tunnel from list of active tunnels
activeTunnels.remove(activeConnection.getUUID().toString());
// Get associated identifiers
// 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
activeTunnels.remove(activeConnection.getUUID().toString());
activeConnections.remove(identifier, activeConnection);
activeConnectionGroups.remove(parentIdentifier, activeConnection);
release(user, connection);
}
// Release any associated group
if (activeConnection.hasBalancingGroup())
release(user, activeConnection.getBalancingGroup());
@@ -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 {
ModeledConnection connection = activeConnection.getConnection();
GuacamoleClientInformation info) throws GuacamoleException {
// 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);
}
}

View File

@@ -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.
*/
@@ -67,12 +76,53 @@ public class ActiveConnectionRecord implements ConnectionRecord {
*/
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;
@@ -269,4 +388,19 @@ public class ActiveConnectionRecord implements ConnectionRecord {
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;
}
}

View File

@@ -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<ActiveConnectionRecord> 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;
}

View File

@@ -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<ModeledConnection> 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());
}

View File

@@ -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<String> preferredConnections =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
/**
* 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();

View File

@@ -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<UserContext> userContextProvider;
/**
* Provider for retrieving SharedConnectionUserContext instances.
*/
@Inject
private Provider<SharedConnectionUserContext> 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);

View File

@@ -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;
}
}