GUACAMOLE-1616: Merge support for tracking external connection history within database.

This commit is contained in:
Mike Jumper
2022-05-31 20:54:31 -07:00
committed by GitHub
15 changed files with 686 additions and 164 deletions

View File

@@ -110,4 +110,34 @@ public interface AuthenticationProviderService {
UserContext context, AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException;
/**
* Decorates a UserContext instance for the given already-authenticated user.
* If no decoration is required, the original UserContext will be returned.
*
* @param authenticationProvider
* The AuthenticationProvider on behalf of which the UserContext is
* being decorated.
*
* @param context
* The UserContext to decorate.
*
* @param authenticatedUser
* The AuthenticatedUser associated with the UserContext being decorated.
*
* @param credentials
* The credentials most recently submitted by the user. These
* credentials are not guaranteed to be the same as the credentials
* already associated with the AuthenticatedUser.
*
* @return
* A decorated UserContext instance for the user identified by the given
* credentials, or the original user context if no decoration is required.
*
* @throws GuacamoleException
* If the an error occurs during decoration of the UserContext.
*/
public UserContext decorateUserContext(AuthenticationProvider authenticationProvider,
UserContext context, AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException;
}

View File

@@ -0,0 +1,124 @@
/*
* 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;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel;
import org.apache.guacamole.auth.jdbc.connection.ModeledConnectionRecord;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.DelegatingConnection;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
/**
* Connection implementation that creates a history record when the connection
* is established, and returns a HistoryTrackingTunnel to automatically set the
* end date when the connection is closed.
*/
public class HistoryTrackingConnection extends DelegatingConnection {
/**
* The current Guacamole user.
*/
private final User currentUser;
/**
* The remote host that the user connected from.
*/
private final String remoteHost;
/**
* The connection record mapper to use when writing history entries for
* established connections.
*/
private final ConnectionRecordMapper connectionRecordMapper;
/**
* Creates a new HistoryConnection that wraps the given connection,
* automatically creating a history record when the connection is
* established, and returning a HistoryTrackingTunnel to set the end
* date on the history entry when the connection is closed.
*
* @param currentUser
* The current Guacamole user.
*
* @param remoteHost
* The remote host that the user connected from.
*
* @param connection
* The connection to wrap.
*
* @param connectionRecordMapper
* The connection record mapper that will be used to write the connection history records.
*/
public HistoryTrackingConnection(User currentUser, String remoteHost, Connection connection, ConnectionRecordMapper connectionRecordMapper) {
super(connection);
this.currentUser = currentUser;
this.remoteHost = remoteHost;
this.connectionRecordMapper = connectionRecordMapper;
}
@Override
public GuacamoleTunnel connect(GuacamoleClientInformation info,
Map<String, String> tokens) throws GuacamoleException {
// Create a connection record model, starting at the current date/time
ConnectionRecordModel connectionRecordModel = new ConnectionRecordModel();
connectionRecordModel.setStartDate(new Date());
// Set the user information
connectionRecordModel.setUsername(this.currentUser.getIdentifier());
connectionRecordModel.setRemoteHost(this.remoteHost);
// Set the connection information
connectionRecordModel.setConnectionName(this.getDelegateConnection().getName());
// Insert the connection history record to mark the start of this connection
connectionRecordMapper.insert(connectionRecordModel);
// Include history record UUID as token
ModeledConnectionRecord modeledRecord = new ModeledConnectionRecord(connectionRecordModel);
Map<String, String> updatedTokens = new HashMap<>(tokens);
updatedTokens.put("HISTORY_UUID", modeledRecord.getUUID().toString());
// Connect, and wrap the tunnel for return
GuacamoleTunnel tunnel = super.connect(info, tokens);
return new HistoryTrackingTunnel(
tunnel, this.connectionRecordMapper, connectionRecordModel);
}
/**
* Get the Connection wrapped by this HistoryTrackingConnection.
*
* @return
* The wrapped Connection.
*/
public Connection getWrappedConnection() {
return getDelegateConnection();
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.DecoratingDirectory;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.User;
/**
* A connection directory that returns HistoryTrackingConnection-wrapped connections
* when queried.
*/
public class HistoryTrackingConnectionDirectory extends DecoratingDirectory<Connection> {
/**
* The connection record mapper to use when writing history entries for
* established connections.
*/
private final ConnectionRecordMapper connectionRecordMapper;
/**
* The user that directory operations are being performed for.
*/
private final User user;
/**
* The remote host that the user connected from.
*/
private final String remoteHost;
/**
* Create a new history tracking connection directory. Any connection retrieved from this
* directory will be wrapped in a HistoryTrackingConnection, enabling connection history
* records to be written with the provided connection record mapper.
*
* @param directory
* The connection directory to wrap.
*
* @param user
* The user associated with the connection directory.
*
* @param remoteHost
* The remote host that the user connected from.
*
* @param connectionRecordMapper
* The connection record mapper that will be used to write the connection history records.
*/
public HistoryTrackingConnectionDirectory(Directory<Connection> directory, User user, String remoteHost, ConnectionRecordMapper connectionRecordMapper) {
super(directory);
this.user = user;
this.remoteHost = remoteHost;
this.connectionRecordMapper = connectionRecordMapper;
}
@Override
protected Connection decorate(Connection connection) throws GuacamoleException {
// Wrap the connection in a history-tracking layer
return new HistoryTrackingConnection(
this.user, this.remoteHost, connection, this.connectionRecordMapper);
}
@Override
protected Connection undecorate(Connection connection) throws GuacamoleException {
// If the connection was wrapped, unwrap it
if (connection instanceof HistoryTrackingConnection) {
return ((HistoryTrackingConnection) connection).getWrappedConnection();
}
// Otherwise, return the unwrapped connection directly
return connection;
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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;
import java.util.Date;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel;
import org.apache.guacamole.net.DelegatingGuacamoleTunnel;
import org.apache.guacamole.net.GuacamoleTunnel;
/**
* Tunnel implementation which automatically writes an end date for the
* provided connection history record model using the provided connection
* history mapper, when the tunnel is closed.
*/
public class HistoryTrackingTunnel extends DelegatingGuacamoleTunnel {
/**
* The connection for which this tunnel was established.
*/
private final ConnectionRecordMapper connectionRecordMapper;
/**
* The user for which this tunnel was established.
*/
private final ConnectionRecordModel connectionRecordModel;
/**
* Creates a new HistoryTrackingTunnel that wraps the given tunnel,
* automatically setting the end date for the provided connection history records,
* using the provided connection history record mapper.
*
* @param tunnel
* The tunnel to wrap.
*
* @param connectionRecordMapper
* The mapper to use when writing connection history records.
*
* @param connectionRecordModel
* The connection history record model representing the in-progress connection.
*/
public HistoryTrackingTunnel(GuacamoleTunnel tunnel,
ConnectionRecordMapper connectionRecordMapper, ConnectionRecordModel connectionRecordModel) {
super(tunnel);
// Store the connection record mapper and model for history tracking
this.connectionRecordMapper = connectionRecordMapper;
this.connectionRecordModel = connectionRecordModel;
}
@Override
public void close() throws GuacamoleException {
// Set the end date to complete the connection history record
this.connectionRecordModel.setEndDate(new Date());
this.connectionRecordMapper.updateEndDate(this.connectionRecordModel);
super.close();
}
}

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;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.DelegatingUserContext;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.UserContext;
/**
* DelegatingUserContext implementation which writes connection history records
* when connections are established and closed.
*/
public class HistoryTrackingUserContext extends DelegatingUserContext {
/**
* The remote host that the user associated with the user context
* connected from.
*/
private final String remoteHost;
/**
* The connection record mapper to use when writing history entries for
* established connections.
*/
private final ConnectionRecordMapper connectionRecordMapper;
/**
* Creates a new HistoryTrackingUserContext which wraps the given
* UserContext, allowing for tracking of connection history external to
* this authentication provider.
*
* @param userContext
* The UserContext to wrap.
*
* @param remoteHost
* The host that the user associated with the given user context connected from.
*
* @param connectionRecordMapper
* The mapper to use when writing connection history entries to the DB.
*/
public HistoryTrackingUserContext(UserContext userContext, String remoteHost, ConnectionRecordMapper connectionRecordMapper) {
super(userContext);
this.remoteHost = remoteHost;
this.connectionRecordMapper = connectionRecordMapper;
}
@Override
public Directory<Connection> getConnectionDirectory() throws GuacamoleException {
return new HistoryTrackingConnectionDirectory(
super.getConnectionDirectory(), self(),
this.remoteHost, this.connectionRecordMapper);
}
}

View File

@@ -90,4 +90,12 @@ public abstract class InjectedAuthenticationProvider extends AbstractAuthenticat
authenticatedUser, credentials);
}
@Override
public UserContext decorate(UserContext context,
AuthenticatedUser authenticatedUser, Credentials credentials)
throws GuacamoleException {
return authProviderService.decorateUserContext(this, context,
authenticatedUser, credentials);
}
}

View File

@@ -22,6 +22,7 @@ package org.apache.guacamole.auth.jdbc;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper;
import org.apache.guacamole.auth.jdbc.security.PasswordPolicyService;
import org.apache.guacamole.auth.jdbc.sharing.user.SharedAuthenticatedUser;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
@@ -68,6 +69,12 @@ public class JDBCAuthenticationProviderService implements AuthenticationProvider
@Inject
private Provider<ModeledUserContext> userContextProvider;
/**
* Mapper for writing connection history.
*/
@Inject
private ConnectionRecordMapper connectionRecordMapper;
@Override
public AuthenticatedUser authenticateUser(AuthenticationProvider authenticationProvider,
Credentials credentials) throws GuacamoleException {
@@ -162,4 +169,18 @@ public class JDBCAuthenticationProviderService implements AuthenticationProvider
}
@Override
public UserContext decorateUserContext(AuthenticationProvider authenticationProvider,
UserContext context, AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException {
// Track connection history only for external connections, and only if enabled in the config
if (environment.trackExternalConnectionHistory() && context.getAuthenticationProvider() != authenticationProvider) {
return new HistoryTrackingUserContext(context, credentials.getRemoteHostname(), connectionRecordMapper);
}
return context;
}
}

View File

@@ -212,4 +212,19 @@ public abstract class JDBCEnvironment extends DelegatingEnvironment {
}
}
/**
* Returns a boolean value representing whether or not the JDBC module
* should automatically track connection history for external connections,
* i.e. connections not originated from within the JDBC auth provider
* itself.
*
* @return
* true if connection history should be tracked for connections that
* do not originate from within this JDBC auth provider, false otherwise.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
public abstract boolean trackExternalConnectionHistory() throws GuacamoleException;
}

View File

@@ -109,4 +109,13 @@ public class SharedAuthenticationProviderService implements AuthenticationProvid
}
@Override
public UserContext decorateUserContext(AuthenticationProvider authenticationProvider,
UserContext context, AuthenticatedUser authenticatedUser,
Credentials credentials) {
// There's no need to decorate the user context here
return context;
}
}

View File

@@ -404,4 +404,12 @@ public class MySQLEnvironment extends JDBCEnvironment {
return getProperty(MySQLGuacamoleProperties.SERVER_TIMEZONE);
}
@Override
public boolean trackExternalConnectionHistory() throws GuacamoleException {
// Track external connection history unless explicitly disabled
return getProperty(MySQLGuacamoleProperties.MYSQL_TRACK_EXTERNAL_CONNECTION_HISTORY,
true);
}
}

View File

@@ -265,4 +265,17 @@ public class MySQLGuacamoleProperties {
};
/**
* Whether or not to track connection history for connections that do not originate
* from within the MySQL database. By default, external connection history will be
* tracked.
*/
public static final BooleanGuacamoleProperty MYSQL_TRACK_EXTERNAL_CONNECTION_HISTORY =
new BooleanGuacamoleProperty() {
@Override
public String getName() { return "mysql-track-external-connection-history"; }
};
}

View File

@@ -363,4 +363,12 @@ public class PostgreSQLEnvironment extends JDBCEnvironment {
false);
}
@Override
public boolean trackExternalConnectionHistory() throws GuacamoleException {
// Track external connection history unless explicitly disabled
return getProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_TRACK_EXTERNAL_CONNECTION_HISTORY,
true);
}
}

View File

@@ -277,4 +277,17 @@ public class PostgreSQLGuacamoleProperties {
};
/**
* Whether or not to track connection history for connections that do not originate
* from within the Postgres database. By default, external connection history will be
* tracked.
*/
public static final BooleanGuacamoleProperty POSTGRESQL_TRACK_EXTERNAL_CONNECTION_HISTORY =
new BooleanGuacamoleProperty() {
@Override
public String getName() { return "postgresql-track-external-connection-history"; }
};
}

View File

@@ -260,4 +260,12 @@ public class SQLServerEnvironment extends JDBCEnvironment {
false);
}
@Override
public boolean trackExternalConnectionHistory() throws GuacamoleException {
// Track external connection history unless explicitly disabled
return getProperty(SQLServerGuacamoleProperties.SQLSERVER_TRACK_EXTERNAL_CONNECTION_HISTORY,
true);
}
}

View File

@@ -207,4 +207,17 @@ public class SQLServerGuacamoleProperties {
};
/**
* Whether or not to track connection history for connections that do not originate
* from within the SQL Server database. By default, external connection history will be
* tracked.
*/
public static final BooleanGuacamoleProperty SQLSERVER_TRACK_EXTERNAL_CONNECTION_HISTORY =
new BooleanGuacamoleProperty() {
@Override
public String getName() { return "sqlserver-track-external-connection-history"; }
};
}