Add .gitignore and .ratignore files for various directories
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
gyurix
2025-04-29 21:43:12 +02:00
parent 983ecbfc53
commit be9f66dee9
2167 changed files with 254128 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
target/
*~

View File

@@ -0,0 +1 @@
src/main/resources/html/*.html

View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-auth-jdbc-base</artifactId>
<packaging>jar</packaging>
<name>guacamole-auth-jdbc-base</name>
<url>http://guacamole.apache.org/</url>
<parent>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-auth-jdbc</artifactId>
<version>1.6.0</version>
<relativePath>../../</relativePath>
</parent>
<dependencies>
<!-- Java servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- Guacamole Extension API -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-ext</artifactId>
<scope>provided</scope>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.19</version>
</dependency>
<!-- MyBatis Guice -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-guice</artifactId>
<version>3.18</version>
<exclusions>
<!-- This dependency appears to be necessary only to provide an
SLF4J bridge for the commons-logging (JCL) system used by DBCP2
an optional dependency that is not being used here. See: https://github.com/mybatis/guice/commit/fbf655ed5bd7ecb00ec070ce20d4fe8aeb6fa9c1 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</exclusion>
<!-- This dependency is LGPL-licensed and is listed in the
mybatis-guice pom.xml as optional. Its only current use within
mybatis-guice is to provide the "Nullable" annotation for a
single member variable, and that annotation does not have
runtime retention. -->
<exclusion>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Guice -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<!-- Guava - Utility Library -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,143 @@
/*
* 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.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.UserContext;
/**
* Service which authenticates users based on credentials and provides for
* the creation of corresponding, new UserContext objects for authenticated
* users.
*/
public interface AuthenticationProviderService {
/**
* Authenticates the user having the given credentials, returning a new
* AuthenticatedUser instance only if the credentials are valid. If the
* credentials are invalid or expired, an appropriate GuacamoleException
* will be thrown.
*
* @param authenticationProvider
* The AuthenticationProvider on behalf of which the user is being
* authenticated.
*
* @param credentials
* The credentials to use to produce the AuthenticatedUser.
*
* @return
* A new AuthenticatedUser instance for the user identified by the
* given credentials.
*
* @throws GuacamoleException
* If an error occurs during authentication, or if the given
* credentials are invalid or expired.
*/
public AuthenticatedUser authenticateUser(AuthenticationProvider authenticationProvider,
Credentials credentials) throws GuacamoleException;
/**
* Returning a new UserContext instance for the given already-authenticated
* user.
*
* @param authenticationProvider
* The AuthenticationProvider on behalf of which the UserContext is
* being produced.
*
* @param authenticatedUser
* The credentials to use to produce the UserContext.
*
* @return
* A new UserContext instance for the user identified by the given
* credentials, or null if no such user exists within the database.
*
* @throws GuacamoleException
* If an error occurs during authentication, or if the given
* credentials are invalid or expired.
*/
public UserContext getUserContext(AuthenticationProvider authenticationProvider,
AuthenticatedUser authenticatedUser) throws GuacamoleException;
/**
* Returns an updated UserContext instance for the given
* already-authenticated user. If no changes need be made to the
* UserContext, the original UserContext will be returned.
*
* @param authenticationProvider
* The AuthenticationProvider on behalf of which the UserContext is
* being updated.
*
* @param context
* The UserContext to update.
*
* @param authenticatedUser
* The AuthenticatedUser associated with the UserContext being updated.
*
* @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 new UserContext instance for the user identified by the given
* credentials.
*
* @throws GuacamoleException
* If an error occurs during authentication, or if the given
* credentials are invalid or expired.
*/
public UserContext updateUserContext(AuthenticationProvider authenticationProvider,
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,156 @@
/*
* 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 com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.guacamole.GuacamoleException;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Pooled DataSource implementation which dynamically retrieves the database
* username and password from the Guacamole server environment each time a
* new database connection is created.
*/
@Singleton
public class DynamicallyAuthenticatedDataSource extends PooledDataSource {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(DynamicallyAuthenticatedDataSource.class);
/**
* Creates a new DynamicallyAuthenticatedDataSource which dynamically
* retrieves database credentials from the given JDBCEnvironment each time
* a new database connection is needed.
*
* @param environment
* The JDBCEnvironment that should be used to retrieve database
* credentials.
*
* @param driverClassLoader
* @param driver
* @param url
*/
@Inject
public DynamicallyAuthenticatedDataSource(JDBCEnvironment environment,
@Named(value="JDBC.driverClassLoader") ClassLoader driverClassLoader,
@Named(value="JDBC.driver") String driver,
@Named(value="JDBC.url") String url) {
// Wrap unpooled DataSource, overriding the connection process such
// that credentials are dynamically retrieved from the JDBCEnvironment
super(new UnpooledDataSource(driverClassLoader, driver, url, null, null) {
@Override
public Connection getConnection() throws SQLException {
try {
logger.debug("Creating new database connection for pool.");
return super.getConnection(environment.getUsername(), environment.getPassword());
}
catch (GuacamoleException e) {
throw new SQLException("Retrieval of database credentials failed.", e);
}
}
});
// Force recalculation of expectedConnectionTypeCode. The
// PooledDataSource constructor accepting a single UnpooledDataSource
// will otherwise leave this value uninitialized, resulting in all
// connections failing to pass sanity checks and never being returned
// to the pool.
super.forceCloseAll();
}
@Override
@Inject(optional=true)
public void setPoolPingConnectionsNotUsedFor(
@Named("mybatis.pooled.pingConnectionsNotUsedFor") int milliseconds) {
super.setPoolPingConnectionsNotUsedFor(milliseconds);
}
@Override
@Inject(optional=true)
public void setPoolPingEnabled(@Named("mybatis.pooled.pingEnabled") boolean poolPingEnabled) {
super.setPoolPingEnabled(poolPingEnabled);
}
@Override
@Inject(optional=true)
public void setPoolPingQuery(@Named("mybatis.pooled.pingQuery") String poolPingQuery) {
super.setPoolPingQuery(poolPingQuery);
}
@Override
@Inject(optional=true)
public void setPoolTimeToWait(@Named("mybatis.pooled.timeToWait") int poolTimeToWait) {
super.setPoolTimeToWait(poolTimeToWait);
}
@Override
@Inject(optional=true)
public void setPoolMaximumCheckoutTime(
@Named("mybatis.pooled.maximumCheckoutTime") int poolMaximumCheckoutTime) {
super.setPoolMaximumCheckoutTime(poolMaximumCheckoutTime);
}
@Override
@Inject(optional=true)
public void setPoolMaximumIdleConnections(
@Named("mybatis.pooled.maximumIdleConnections") int poolMaximumIdleConnections) {
super.setPoolMaximumIdleConnections(poolMaximumIdleConnections);
}
@Override
@Inject(optional=true)
public void setPoolMaximumActiveConnections(
@Named("mybatis.pooled.maximumActiveConnections") int poolMaximumActiveConnections) {
super.setPoolMaximumActiveConnections(poolMaximumActiveConnections);
}
@Override
@Inject(optional=true)
public void setDriverProperties(@Named("JDBC.driverProperties") Properties driverProps) {
super.setDriverProperties(driverProps);
}
@Override
@Inject(optional=true)
public void setDefaultAutoCommit(@Named("JDBC.autoCommit") boolean defaultAutoCommit) {
super.setDefaultAutoCommit(defaultAutoCommit);
}
@Override
@Inject(optional=true)
public void setLoginTimeout(@Named("JDBC.loginTimeout") int loginTimeout) {
super.setLoginTimeout(loginTimeout);
}
}

View File

@@ -0,0 +1,132 @@
/*
* 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 com.google.inject.Inject;
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.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
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;
/**
* The environment in which Guacamole is running.
*/
private final Environment environment = LocalEnvironment.getInstance();
/**
* 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,
environment.getCaseSensitivity());
// 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, updatedTokens);
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

@@ -0,0 +1,101 @@
/*
* 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 com.google.inject.Injector;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.net.auth.AuthenticatedUser;
/**
* Provides a base implementation of an AuthenticationProvider which delegates
* the various function calls to an underlying AuthenticationProviderService
* implementation. As such a service is injectable by Guice, this provides a
* means for Guice to (effectively) apply dependency injection to an
* AuthenticationProvider, even though it is the AuthenticationProvider that
* serves as the entry point.
*/
public abstract class InjectedAuthenticationProvider extends AbstractAuthenticationProvider {
/**
* The AuthenticationProviderService to which all AuthenticationProvider
* calls will be delegated.
*/
private final AuthenticationProviderService authProviderService;
/**
* Creates a new AuthenticationProvider that delegates all calls to an
* underlying AuthenticationProviderService. The behavior of the
* AuthenticationProvider is defined by the given
* AuthenticationProviderService implementation, which will be injected by
* the Guice Injector provided by the given JDBCInjectorProvider.
*
* @param injectorProvider
* A JDBCInjectorProvider instance which provides singleton instances
* of a Guice Injector, pre-configured to set up all injections and
* access to the underlying database via MyBatis.
*
* @param authProviderServiceClass
* The AuthenticationProviderService implementation which defines the
* behavior of this AuthenticationProvider.
*
* @throws GuacamoleException
* If the Injector cannot be created due to an error.
*/
public InjectedAuthenticationProvider(JDBCInjectorProvider injectorProvider,
Class<? extends AuthenticationProviderService> authProviderServiceClass)
throws GuacamoleException {
Injector injector = injectorProvider.get();
authProviderService = injector.getInstance(authProviderServiceClass);
}
@Override
public AuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
return authProviderService.authenticateUser(this, credentials);
}
@Override
public UserContext getUserContext(AuthenticatedUser authenticatedUser)
throws GuacamoleException {
return authProviderService.getUserContext(this, authenticatedUser);
}
@Override
public UserContext updateUserContext(UserContext context,
AuthenticatedUser authenticatedUser, Credentials credentials)
throws GuacamoleException {
return authProviderService.updateUserContext(this, context,
authenticatedUser, credentials);
}
@Override
public UserContext decorate(UserContext context,
AuthenticatedUser authenticatedUser, Credentials credentials)
throws GuacamoleException {
return authProviderService.decorateUserContext(this, context,
authenticatedUser, credentials);
}
}

View File

@@ -0,0 +1,209 @@
/*
* 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 com.google.inject.Scopes;
import javax.sql.DataSource;
import org.apache.guacamole.auth.jdbc.user.ModeledUserContext;
import org.apache.guacamole.auth.jdbc.connectiongroup.RootConnectionGroup;
import org.apache.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
import org.apache.guacamole.auth.jdbc.connectiongroup.ConnectionGroupDirectory;
import org.apache.guacamole.auth.jdbc.connection.ConnectionDirectory;
import org.apache.guacamole.auth.jdbc.connection.ModeledGuacamoleConfiguration;
import org.apache.guacamole.auth.jdbc.connection.ModeledConnection;
import org.apache.guacamole.auth.jdbc.permission.SystemPermissionSet;
import org.apache.guacamole.auth.jdbc.user.ModeledUser;
import org.apache.guacamole.auth.jdbc.user.UserDirectory;
import org.apache.guacamole.auth.jdbc.connectiongroup.ConnectionGroupMapper;
import org.apache.guacamole.auth.jdbc.connection.ConnectionMapper;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper;
import org.apache.guacamole.auth.jdbc.permission.SystemPermissionMapper;
import org.apache.guacamole.auth.jdbc.user.UserMapper;
import org.apache.guacamole.auth.jdbc.connectiongroup.ConnectionGroupService;
import org.apache.guacamole.auth.jdbc.connection.ConnectionService;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.auth.jdbc.security.PasswordEncryptionService;
import org.apache.guacamole.auth.jdbc.security.SHA256PasswordEncryptionService;
import org.apache.guacamole.auth.jdbc.security.SaltService;
import org.apache.guacamole.auth.jdbc.security.SecureRandomSaltService;
import org.apache.guacamole.auth.jdbc.permission.SystemPermissionService;
import org.apache.guacamole.auth.jdbc.user.UserService;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.apache.guacamole.auth.jdbc.permission.ConnectionGroupPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.ConnectionGroupPermissionService;
import org.apache.guacamole.auth.jdbc.permission.ConnectionGroupPermissionSet;
import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionService;
import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionSet;
import org.apache.guacamole.auth.jdbc.permission.UserPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.UserPermissionService;
import org.apache.guacamole.auth.jdbc.permission.UserPermissionSet;
import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionDirectory;
import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionPermissionService;
import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionPermissionSet;
import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionService;
import org.apache.guacamole.auth.jdbc.activeconnection.TrackedActiveConnection;
import org.apache.guacamole.auth.jdbc.base.EntityMapper;
import org.apache.guacamole.auth.jdbc.base.EntityService;
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.permission.UserGroupPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.UserGroupPermissionService;
import org.apache.guacamole.auth.jdbc.permission.UserGroupPermissionSet;
import org.apache.guacamole.auth.jdbc.security.PasswordPolicyService;
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;
import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileParameterMapper;
import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileService;
import org.apache.guacamole.auth.jdbc.tunnel.RestrictedGuacamoleTunnelService;
import org.apache.guacamole.auth.jdbc.user.PasswordRecordMapper;
import org.apache.guacamole.auth.jdbc.user.UserRecordMapper;
import org.apache.guacamole.auth.jdbc.usergroup.ModeledUserGroup;
import org.apache.guacamole.auth.jdbc.usergroup.UserGroupDirectory;
import org.apache.guacamole.auth.jdbc.usergroup.UserGroupMapper;
import org.apache.guacamole.auth.jdbc.usergroup.UserGroupMemberUserGroupMapper;
import org.apache.guacamole.auth.jdbc.usergroup.UserGroupMemberUserMapper;
import org.apache.guacamole.auth.jdbc.usergroup.UserGroupParentUserGroupMapper;
import org.apache.guacamole.auth.jdbc.usergroup.UserGroupService;
import org.mybatis.guice.MyBatisModule;
import org.apache.guacamole.auth.jdbc.user.UserParentUserGroupMapper;
/**
* Guice module which configures the injections used by the JDBC authentication
* provider base. This module MUST be included in the Guice injector, or
* authentication providers based on JDBC will not function.
*/
public class JDBCAuthenticationProviderModule extends MyBatisModule {
/**
* The environment of the Guacamole server.
*/
private final JDBCEnvironment environment;
/**
* Creates a new JDBC authentication provider module that configures the
* various injected base classes using the given environment, and provides
* connections using the given socket service.
*
* @param environment
* The environment to use to configure injected classes.
*/
public JDBCAuthenticationProviderModule(JDBCEnvironment environment) {
this.environment = environment;
}
@Override
protected void initialize() {
// Datasource
bind(DataSource.class).to(DynamicallyAuthenticatedDataSource.class);
// Transaction factory
bindTransactionFactoryType(JdbcTransactionFactory.class);
// Set the JDBC Auth provider to use batch execution if enabled
if (environment.shouldUseBatchExecutor())
bindConfigurationSetting(configuration -> {
configuration.setDefaultExecutorType(ExecutorType.BATCH);
});
// Add MyBatis mappers
addMapperClass(ConnectionMapper.class);
addMapperClass(ConnectionGroupMapper.class);
addMapperClass(ConnectionGroupPermissionMapper.class);
addMapperClass(ConnectionPermissionMapper.class);
addMapperClass(ConnectionRecordMapper.class);
addMapperClass(ConnectionParameterMapper.class);
addMapperClass(EntityMapper.class);
addMapperClass(PasswordRecordMapper.class);
addMapperClass(SystemPermissionMapper.class);
addMapperClass(SharingProfileMapper.class);
addMapperClass(SharingProfileParameterMapper.class);
addMapperClass(SharingProfilePermissionMapper.class);
addMapperClass(UserGroupMapper.class);
addMapperClass(UserGroupMemberUserGroupMapper.class);
addMapperClass(UserGroupMemberUserMapper.class);
addMapperClass(UserGroupParentUserGroupMapper.class);
addMapperClass(UserGroupPermissionMapper.class);
addMapperClass(UserMapper.class);
addMapperClass(UserParentUserGroupMapper.class);
addMapperClass(UserPermissionMapper.class);
addMapperClass(UserRecordMapper.class);
// Bind core implementations of guacamole-ext classes
bind(ActiveConnectionDirectory.class);
bind(ActiveConnectionPermissionSet.class);
bind(JDBCEnvironment.class).toInstance(environment);
bind(ConnectionDirectory.class);
bind(ConnectionGroupDirectory.class);
bind(ConnectionGroupPermissionSet.class);
bind(ConnectionPermissionSet.class);
bind(ModeledConnection.class);
bind(ModeledConnectionGroup.class);
bind(ModeledGuacamoleConfiguration.class);
bind(ModeledSharingProfile.class);
bind(ModeledUser.class);
bind(ModeledUserContext.class);
bind(ModeledUserGroup.class);
bind(RootConnectionGroup.class);
bind(SharingProfileDirectory.class);
bind(SharingProfilePermissionSet.class);
bind(SystemPermissionSet.class);
bind(TrackedActiveConnection.class);
bind(UserDirectory.class);
bind(UserGroupDirectory.class);
bind(UserGroupPermissionSet.class);
bind(UserPermissionSet.class);
// Bind services
bind(ActiveConnectionService.class);
bind(ActiveConnectionPermissionService.class);
bind(ConnectionGroupPermissionService.class);
bind(ConnectionGroupService.class);
bind(ConnectionPermissionService.class);
bind(ConnectionSharingService.class);
bind(ConnectionService.class);
bind(EntityService.class);
bind(GuacamoleTunnelService.class).to(RestrictedGuacamoleTunnelService.class);
bind(PasswordEncryptionService.class).to(SHA256PasswordEncryptionService.class);
bind(PasswordPolicyService.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);
bind(UserGroupService.class);
bind(UserGroupPermissionService.class);
bind(UserPermissionService.class);
bind(UserService.class);
}
}

View File

@@ -0,0 +1,186 @@
/*
* 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 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;
import org.apache.guacamole.auth.jdbc.user.ModeledUser;
import org.apache.guacamole.auth.jdbc.user.ModeledUserContext;
import org.apache.guacamole.auth.jdbc.user.PrivilegedModeledAuthenticatedUser;
import org.apache.guacamole.auth.jdbc.user.UserService;
import org.apache.guacamole.language.TranslatableGuacamoleClientException;
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.UserContext;
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
/**
* AuthenticationProviderService implementation which authenticates users with
* a username/password pair, producing new UserContext objects which are backed
* by an underlying, arbitrary database.
*/
public class JDBCAuthenticationProviderService implements AuthenticationProviderService {
/**
* The environment of the Guacamole server.
*/
@Inject
private JDBCEnvironment environment;
/**
* Service for accessing users.
*/
@Inject
private UserService userService;
/**
* Service for enforcing password complexity policies.
*/
@Inject
private PasswordPolicyService passwordPolicyService;
/**
* Provider for retrieving UserContext instances.
*/
@Inject
private Provider<ModeledUserContext> userContextProvider;
/**
* Mapper for writing connection history.
*/
@Inject
private ConnectionRecordMapper connectionRecordMapper;
@Override
public AuthenticatedUser authenticateUser(AuthenticationProvider authenticationProvider,
Credentials credentials) throws GuacamoleException {
// Authenticate user
AuthenticatedUser user = userService.retrieveAuthenticatedUser(authenticationProvider, credentials);
if (user != null)
return user;
// Otherwise, unauthorized
throw new GuacamoleInvalidCredentialsException("Invalid login", CredentialsInfo.USERNAME_PASSWORD);
}
@Override
public ModeledUserContext getUserContext(AuthenticationProvider authenticationProvider,
AuthenticatedUser authenticatedUser) throws GuacamoleException {
// Always allow but provide no data for users authenticated via our own
// connection sharing links
if (authenticatedUser instanceof SharedAuthenticatedUser)
return null;
// Set semantic flags based on context
boolean databaseCredentialsUsed = (authenticatedUser instanceof ModeledAuthenticatedUser);
boolean databaseRestrictionsApplicable = (databaseCredentialsUsed || environment.isUserRequired());
// Retrieve user account for already-authenticated user
ModeledUser user = userService.retrieveUser(authenticationProvider, authenticatedUser);
ModeledUserContext context = userContextProvider.get();
if (user != null && !user.isDisabled()) {
// Enforce applicable account restrictions
if (databaseRestrictionsApplicable) {
// Verify user account is still valid as of today
if (!user.isAccountValid())
throw new TranslatableGuacamoleClientException("User "
+ "account is no longer valid.",
"LOGIN.ERROR_NOT_VALID");
// Verify user account is allowed to be used at the current time
if (!user.isAccountAccessible())
throw new TranslatableGuacamoleClientException("User "
+ "account may not be used at this time.",
"LOGIN.ERROR_NOT_ACCESSIBLE");
}
// Update password if password is expired AND the password was
// actually involved in the authentication process
if (databaseCredentialsUsed) {
if (user.isExpired() || passwordPolicyService.isPasswordExpired(user))
userService.resetExpiredPassword(user, authenticatedUser.getCredentials());
}
}
// If no user account is found, and database-specific account
// restrictions do not apply, get a skeleton user.
else if (!databaseRestrictionsApplicable) {
user = userService.retrieveSkeletonUser(authenticationProvider, authenticatedUser);
// If auto account creation is enabled, add user to DB.
if (environment.autoCreateAbsentAccounts()) {
ModeledUser createdUser = userService.createObject(new PrivilegedModeledAuthenticatedUser(user.getCurrentUser()), user);
user.setModel(createdUser.getModel());
}
}
// Veto authentication result only if database-specific account
// restrictions apply in this situation
else
throw new GuacamoleInvalidCredentialsException("Invalid login",
CredentialsInfo.USERNAME_PASSWORD);
// Initialize the UserContext with the user account and return it.
context.init(user.getCurrentUser());
context.recordUserLogin();
return context;
}
@Override
public UserContext updateUserContext(AuthenticationProvider authenticationProvider,
UserContext context, AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException {
// Refresh the user context
return getUserContext(authenticationProvider, authenticatedUser);
}
@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

@@ -0,0 +1,275 @@
/*
* 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.security.PasswordPolicy;
import org.apache.guacamole.environment.DelegatingEnvironment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.ibatis.session.SqlSession;
/**
* A JDBC-specific implementation of Environment that defines generic properties
* intended for use within JDBC based authentication providers.
*/
public abstract class JDBCEnvironment extends DelegatingEnvironment {
/**
* Constructs a new JDBCEnvironment using an underlying LocalEnviroment to
* read properties from the file system.
*/
public JDBCEnvironment() {
super(LocalEnvironment.getInstance());
}
/**
* Returns whether a database user account is required for authentication to
* succeed, even if another authentication provider has already
* authenticated the user.
*
* @return
* true if database user accounts are required for absolutely all
* authentication attempts, false otherwise.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract boolean isUserRequired() throws GuacamoleException;
/**
* Returns the maximum number of concurrent connections to allow overall.
* As this limit applies globally (independent of which connection is in
* use or which user is using it), this setting cannot be overridden at the
* connection level. Zero denotes unlimited.
*
* @return
* The maximum allowable number of concurrent connections.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract int getAbsoluteMaxConnections() throws GuacamoleException;
/**
* Returns the maximum number of identifiers/parameters to be
* included in a single batch when executing SQL statements.
*
* @return
* The maximum number of identifiers/parameters to be included
* in a single batch.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract int getBatchSize() throws GuacamoleException;
/**
* Returns the default maximum number of concurrent connections to allow to
* any one connection, unless specified differently on an individual
* connection. Zero denotes unlimited.
*
* @return
* The default maximum allowable number of concurrent connections
* to any connection.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract int getDefaultMaxConnections() throws GuacamoleException;
/**
* Returns the default maximum number of concurrent connections to allow to
* any one connection group, unless specified differently on an individual
* connection group. Zero denotes unlimited.
*
* @return
* The default maximum allowable number of concurrent connections
* to any connection group.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract int getDefaultMaxGroupConnections()
throws GuacamoleException;
/**
* Returns the default maximum number of concurrent connections to allow to
* any one connection by an individual user, unless specified differently on
* an individual connection. Zero denotes unlimited.
*
* @return
* The default maximum allowable number of concurrent connections to
* any connection by an individual user.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract int getDefaultMaxConnectionsPerUser()
throws GuacamoleException;
/**
* Returns the default maximum number of concurrent connections to allow to
* any one connection group by an individual user, unless specified
* differently on an individual connection group. Zero denotes unlimited.
*
* @return
* The default maximum allowable number of concurrent connections to
* any connection group by an individual user.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract int getDefaultMaxGroupConnectionsPerUser()
throws GuacamoleException;
/**
* Returns the policy which applies to newly-set passwords. Passwords which
* apply to Guacamole user accounts will be required to conform to this
* policy.
*
* @return
* The password policy which applies to Guacamole user accounts.
*/
public abstract PasswordPolicy getPasswordPolicy();
/**
* Returns whether the database supports recursive queries. Many database
* engines support recursive queries through CTEs. If recursive queries are
* not supported, queries that are intended to be recursive may need to be
* invoked multiple times to retrieve the same data.
*
* @param session
* The SqlSession provided by MyBatis for the current transaction.
*
* @return
* true if the database supports recursive queries, false otherwise.
*/
public abstract boolean isRecursiveQuerySupported(SqlSession session);
/**
* Returns a boolean value representing whether or not the JDBC module
* should automatically create accounts within the database for users that
* are successfully authenticated via other extensions. Returns true if
* accounts should be auto-created, otherwise returns false.
*
* @return
* true if user accounts should be automatically created within the
* database when authentication succeeds from another extension;
* otherwise false.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
public abstract boolean autoCreateAbsentAccounts() throws GuacamoleException;
/**
* Returns the username that should be used when authenticating with the
* database containing the Guacamole authentication tables.
*
* @return
* The username for the database.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property value, or if the
* value was not set, as this property is required.
*/
public abstract String getUsername() throws GuacamoleException;
/**
* Returns the password that should be used authenticating with the
* database containing the Guacamole authentication tables.
*
* @return
* The password for the database.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property value, or if the
* value was not set, as this property is required.
*/
public abstract String getPassword() throws GuacamoleException;
/**
* Returns whether the given Java class is defined within the classpath.
*
* @param classname
* The name of the Java class to check.
*
* @return
* true if the given Java class is present within the classpath, false
* otherwise.
*/
public static boolean isClassDefined(String classname) {
try {
Class.forName(classname, false, JDBCEnvironment.class.getClassLoader());
return true;
}
catch (ClassNotFoundException e) {
return false;
}
}
/**
* 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;
/**
* Returns a boolean value representing whether access time windows should
* be enforced for active connections - i.e. whether a currently-connected
* user should be disconnected upon the closure of an access window.
*
* @return
* true if a connected user should be disconnected upon an access time
* window closing, false otherwise.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
public abstract boolean enforceAccessWindowsForActiveSessions() throws GuacamoleException;
/**
* Returns true if the JDBC batch executor should be used by default, false
* otherwise. The batch executor allows repeated updates to be batched
* together for improved performance.
* See https://mybatis.org/mybatis-3/java-api.html#sqlSessions
*
* @return
* true if the batch executor should be used by default, false otherwise.
*/
public boolean shouldUseBatchExecutor() {
// Unless otherwise overwritten due to implementation-specific problems,
// all JDBC extensions should use the batch executor if possible to
// ensure the best performance for repetitive queries
return true;
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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 com.google.inject.Injector;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.guacamole.GuacamoleException;
/**
* A caching provider of singleton Guice Injector instances. The first call to
* get() will return a new instance of the Guice Injector, while all subsequent
* calls will return that same instance. It is up to implementations of this
* class to define how the Guice Injector will be created through defining the
* create() function.
*
* IMPORTANT: Because the Injector returned by get() is cached statically, only
* ONE implementation of this class may be used within any individual
* classloader. Within the context of the JDBC extension, as long as each built
* extension only provides one subclass of this class, things should work
* properly, as each extension is given its own classloader by Guacamole.
*/
public abstract class JDBCInjectorProvider {
/**
* An AtomicReference wrapping the cached Guice Injector. If the Injector
* has not yet been created, null will be wrapped instead.
*/
private static final AtomicReference<Injector> injector = new AtomicReference<Injector>(null);
/**
* Creates a new instance of the Guice Injector which should be used
* across the entire JDBC authentication extension. This function will
* generally only be called once, but multiple invocations are possible if
* get() is invoked several times concurrently prior to the Injector being
* cached.
*
* @return
* @throws GuacamoleException
*/
protected abstract Injector create() throws GuacamoleException;
/**
* Returns a common, singleton instance of a Guice Injector, configured for
* the injections required by the JDBC authentication extension. The result
* of the first call to this function will be cached statically within this
* class, and will be returned for all subsequent calls.
*
* @return
* A singleton instance of the Guice Injector used across the entire
* JDBC authentication extension.
*
* @throws GuacamoleException
* If the Injector cannot be created due to an error.
*/
public Injector get() throws GuacamoleException {
// Return existing Injector if already created
Injector value = injector.get();
if (value != null)
return value;
// Explicitly create and store new Injector only if necessary
injector.compareAndSet(null, create());
// Consistently return the same Injector, even if two create operations
// happen concurrently
return injector.get();
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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.activeconnection;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.JDBCDirectory;
import org.apache.guacamole.net.auth.ActiveConnection;
/**
* Implementation of a Directory which contains all currently-active
* connections.
*/
public class ActiveConnectionDirectory extends JDBCDirectory<ActiveConnection> {
/**
* Service for retrieving and manipulating active connections.
*/
@Inject
private ActiveConnectionService activeConnectionService;
@Override
public ActiveConnection get(String identifier) throws GuacamoleException {
return activeConnectionService.retrieveObject(getCurrentUser(), identifier);
}
@Override
public Collection<ActiveConnection> getAll(Collection<String> identifiers)
throws GuacamoleException {
Collection<TrackedActiveConnection> objects = activeConnectionService.retrieveObjects(getCurrentUser(), identifiers);
return Collections.<ActiveConnection>unmodifiableCollection(objects);
}
@Override
public Set<String> getIdentifiers() throws GuacamoleException {
return activeConnectionService.getIdentifiers(getCurrentUser());
}
@Override
public void add(ActiveConnection object) throws GuacamoleException {
activeConnectionService.createObject(getCurrentUser(), object);
}
@Override
public void update(ActiveConnection object) throws GuacamoleException {
TrackedActiveConnection connection = (TrackedActiveConnection) object;
activeConnectionService.updateObject(getCurrentUser(), connection);
}
@Override
public void remove(String identifier) throws GuacamoleException {
activeConnectionService.deleteObject(getCurrentUser(), identifier);
}
}

View File

@@ -0,0 +1,178 @@
/*
* 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.activeconnection;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
import org.apache.guacamole.auth.jdbc.permission.AbstractPermissionService;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionService;
import org.apache.guacamole.auth.jdbc.tunnel.ActiveConnectionRecord;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating active connections.
*/
public class ActiveConnectionPermissionService
extends AbstractPermissionService<ObjectPermissionSet, ObjectPermission>
implements ObjectPermissionService {
/**
* Service for creating and tracking tunnels.
*/
@Inject
private GuacamoleTunnelService tunnelService;
/**
* Provider for active connection permission sets.
*/
@Inject
private Provider<ActiveConnectionPermissionSet> activeConnectionPermissionSetProvider;
@Override
public boolean hasPermission(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
ObjectPermission.Type type, String identifier,
Set<String> effectiveGroups) throws GuacamoleException {
// Retrieve permissions
Set<ObjectPermission> permissions = retrievePermissions(user,
targetEntity, effectiveGroups);
// Permission is granted if retrieved permissions contains the
// requested permission
ObjectPermission permission = new ObjectPermission(type, identifier);
return permissions.contains(permission);
}
@Override
public Set<ObjectPermission> retrievePermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException {
// Retrieve permissions only if allowed
if (canReadPermissions(user, targetEntity)) {
// Privileged accounts (such as administrators or UserContexts
// returned by getPrivileged()) may always access active connections
boolean isPrivileged = targetEntity.isPrivileged();
// Get all active connections
Collection<ActiveConnectionRecord> records = tunnelService.getActiveConnections(user);
// We have READ, and possibly DELETE, on all active connections
Set<ObjectPermission> permissions = new HashSet<>();
for (ActiveConnectionRecord record : records) {
// Add implicit READ
String identifier = record.getUUID().toString();
permissions.add(new ObjectPermission(ObjectPermission.Type.READ, identifier));
// If the target user is privileged, or the connection belongs
// to the target user, then they can DELETE
if (isPrivileged || targetEntity.isUser(record.getUsername()))
permissions.add(new ObjectPermission(ObjectPermission.Type.DELETE, identifier));
}
return permissions;
}
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public Collection<String> retrieveAccessibleIdentifiers(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<ObjectPermission.Type> permissionTypes,
Collection<String> identifiers, Set<String> effectiveGroups)
throws GuacamoleException {
Set<ObjectPermission> permissions = retrievePermissions(user, targetEntity, effectiveGroups);
Collection<String> accessibleObjects = new ArrayList<String>(permissions.size());
// For each identifier/permission combination
for (String identifier : identifiers) {
for (ObjectPermission.Type permissionType : permissionTypes) {
// Add identifier if at least one requested permission is granted
ObjectPermission permission = new ObjectPermission(permissionType, identifier);
if (permissions.contains(permission)) {
accessibleObjects.add(identifier);
break;
}
}
}
return accessibleObjects;
}
@Override
public ObjectPermissionSet getPermissionSet(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException {
// Create permission set for requested entity
ActiveConnectionPermissionSet permissionSet = activeConnectionPermissionSetProvider.get();
permissionSet.init(user, targetEntity, effectiveGroups);
return permissionSet;
}
@Override
public void createPermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<ObjectPermission> permissions)
throws GuacamoleException {
// Creating active connection permissions is not implemented
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public void deletePermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<ObjectPermission> permissions)
throws GuacamoleException {
// Deleting active connection permissions is not implemented
throw new GuacamoleSecurityException("Permission denied.");
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.activeconnection;
import com.google.inject.Inject;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionService;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionSet;
/**
* An implementation of ObjectPermissionSet which uses an injected service to
* query and manipulate the permissions associated with active connections.
*/
public class ActiveConnectionPermissionSet extends ObjectPermissionSet {
/**
* Service for querying and manipulating active connection permissions.
*/
@Inject
private ActiveConnectionPermissionService activeConnectionPermissionService;
@Override
protected ObjectPermissionService getObjectPermissionService() {
return activeConnectionPermissionService;
}
}

View File

@@ -0,0 +1,219 @@
/*
* 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.activeconnection;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.base.DirectoryObjectService;
import org.apache.guacamole.auth.jdbc.tunnel.ActiveConnectionRecord;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.ActiveConnection;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating active connections.
*/
public class ActiveConnectionService
implements DirectoryObjectService<TrackedActiveConnection, ActiveConnection> {
/**
* Service for creating and tracking tunnels.
*/
@Inject
private GuacamoleTunnelService tunnelService;
/**
* Provider for active connections.
*/
@Inject
private Provider<TrackedActiveConnection> trackedActiveConnectionProvider;
@Override
public TrackedActiveConnection retrieveObject(ModeledAuthenticatedUser user,
String identifier) throws GuacamoleException {
// Pull objects having given identifier
Collection<TrackedActiveConnection> objects = retrieveObjects(user, Collections.singleton(identifier));
// If no such object, return null
if (objects.isEmpty())
return null;
// The object collection will have exactly one element unless the
// database has seriously lost integrity
assert(objects.size() == 1);
// Return first and only object
return objects.iterator().next();
}
@Override
public Collection<TrackedActiveConnection> retrieveObjects(ModeledAuthenticatedUser user,
Collection<String> identifiers) throws GuacamoleException {
String username = user.getIdentifier();
boolean isPrivileged = user.isPrivileged();
Set<String> identifierSet = new HashSet<String>(identifiers);
// Retrieve all visible connections (permissions enforced by tunnel service)
Collection<ActiveConnectionRecord> records = tunnelService.getActiveConnections(user);
// Restrict to subset of records which match given identifiers
Collection<TrackedActiveConnection> activeConnections = new ArrayList<TrackedActiveConnection>(identifiers.size());
for (ActiveConnectionRecord record : records) {
// The current user should have access to sensitive information and
// be able to connect to (join) the active connection if they are
// the user that started the connection OR the user is an admin
boolean hasPrivilegedAccess =
isPrivileged || username.equals(record.getUsername());
// Add connection if within requested identifiers
if (identifierSet.contains(record.getUUID().toString())) {
TrackedActiveConnection activeConnection = trackedActiveConnectionProvider.get();
activeConnection.init(user, record, hasPrivilegedAccess, hasPrivilegedAccess);
activeConnections.add(activeConnection);
}
}
return activeConnections;
}
@Override
public void deleteObject(ModeledAuthenticatedUser user, String identifier)
throws GuacamoleException {
// Close connection, if it exists and we have permission
ActiveConnection activeConnection = retrieveObject(user, identifier);
if (activeConnection == null)
return;
if (hasObjectPermissions(user, identifier, ObjectPermission.Type.DELETE)) {
// Close connection if not already closed
GuacamoleTunnel tunnel = activeConnection.getTunnel();
if (tunnel != null && tunnel.isOpen())
tunnel.close();
}
else
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public Set<String> getIdentifiers(ModeledAuthenticatedUser user)
throws GuacamoleException {
// Retrieve all visible connections (permissions enforced by tunnel service)
Collection<ActiveConnectionRecord> records = tunnelService.getActiveConnections(user);
// Build list of identifiers
Set<String> identifiers = new HashSet<String>(records.size());
for (ActiveConnectionRecord record : records)
identifiers.add(record.getUUID().toString());
return identifiers;
}
@Override
public TrackedActiveConnection createObject(ModeledAuthenticatedUser user,
ActiveConnection object) throws GuacamoleException {
// Updating active connections is not implemented
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public void updateObject(ModeledAuthenticatedUser user, TrackedActiveConnection object)
throws GuacamoleException {
// Updating active connections is not implemented
throw new GuacamoleSecurityException("Permission denied.");
}
/**
* Retrieve the permission set for the specified user that relates
* to access to active connections.
*
* @param user
* The user for which to retrieve the permission set.
*
* @return
* A permission set associated with the given user that specifies
* the permissions available for active connection objects.
*
* @throws GuacamoleException
* If permission to read permissions for the user is denied.
*/
private ObjectPermissionSet getPermissionSet(ModeledAuthenticatedUser user)
throws GuacamoleException {
return user.getUser().getActiveConnectionPermissions();
}
/**
* Return a boolean value representing whether or not a user has the given
* permission available to them on the active connection with the given
* identifier.
*
* @param user
* The user for which the permissions are being queried.
*
* @param identifier
* The identifier of the active connection we are wondering about.
*
* @param type
* The type of permission being requested.
*
* @return
* True if the user has the necessary permission; otherwise false.
*
* @throws GuacamoleException
* If the user does not have access to read permissions.
*/
private boolean hasObjectPermissions(ModeledAuthenticatedUser user,
String identifier, ObjectPermission.Type type)
throws GuacamoleException {
ObjectPermissionSet permissionSet = getPermissionSet(user);
return user.isPrivileged()
|| permissionSet.hasPermission(type, identifier);
}
}

View File

@@ -0,0 +1,284 @@
/*
* 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.activeconnection;
import com.google.inject.Inject;
import java.util.Date;
import java.util.Map;
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.sharing.connection.SharedConnectionDefinition;
import org.apache.guacamole.auth.jdbc.tunnel.ActiveConnectionRecord;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.ActiveConnection;
import org.apache.guacamole.net.auth.credentials.UserCredentials;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
/**
* An implementation of the ActiveConnection object which has an associated
* ActiveConnectionRecord.
*/
public class TrackedActiveConnection extends RestrictedObject implements ActiveConnection {
/**
* Service for managing shared connections.
*/
@Inject
private ConnectionSharingService sharingService;
/**
* Service for creating and tracking tunnels.
*/
@Inject
private GuacamoleTunnelService tunnelService;
/**
* The identifier of this active connection.
*/
private String identifier;
/**
* The actual connection record from which this ActiveConnection derives its
* data.
*/
private ActiveConnectionRecord connectionRecord;
/**
* The connection being actively used or shared.
*/
private ModeledConnection connection;
/**
* The identifier of the associated sharing profile.
*/
private String sharingProfileIdentifier;
/**
* The date and time this active connection began.
*/
private Date startDate;
/**
* The remote host that initiated this connection.
*/
private String remoteHost;
/**
* The username of the user that initiated this connection.
*/
private String username;
/**
* The underlying GuacamoleTunnel.
*/
private GuacamoleTunnel tunnel;
/**
* Whether connections to this TrackedActiveConnection are allowed.
*/
private boolean connectable;
/**
* Initializes this TrackedActiveConnection, copying the data associated
* with the given active connection record. At a minimum, the identifier
* of this active connection will be set, the start date, and the
* identifier of the associated connection will be copied. If requested,
* sensitive information like the associated username will be copied, as
* well.
*
* @param currentUser
* The user that created or retrieved this object.
*
* @param activeConnectionRecord
* The active connection record to copy.
*
* @param includeSensitiveInformation
* Whether sensitive data should be copied from the connection record
* as well. This includes the remote host, associated tunnel, and
* username.
*
* @param connectable
* Whether the user that retrieved this object should be allowed to
* join the active connection.
*/
public void init(ModeledAuthenticatedUser currentUser,
ActiveConnectionRecord activeConnectionRecord,
boolean includeSensitiveInformation,
boolean connectable) {
super.init(currentUser);
this.connectionRecord = activeConnectionRecord;
this.connectable = connectable;
// Copy all non-sensitive data from given record
this.connection = activeConnectionRecord.getConnection();
this.sharingProfileIdentifier = activeConnectionRecord.getSharingProfileIdentifier();
this.identifier = activeConnectionRecord.getUUID().toString();
this.startDate = activeConnectionRecord.getStartDate();
// Include sensitive data, too, if requested
if (includeSensitiveInformation) {
this.remoteHost = activeConnectionRecord.getRemoteHost();
this.tunnel = activeConnectionRecord.getTunnel();
this.username = activeConnectionRecord.getUsername();
}
}
@Override
public String getIdentifier() {
return identifier;
}
@Override
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
/**
* Returns the connection being actively used. If this active connection is
* not the primary connection, this will be the connection being actively
* shared.
*
* @return
* The connection being actively used.
*/
public ModeledConnection getConnection() {
return connection;
}
@Override
public String getConnectionIdentifier() {
return connection.getIdentifier();
}
@Override
public void setConnectionIdentifier(String connnectionIdentifier) {
throw new UnsupportedOperationException("The connection identifier of "
+ "TrackedActiveConnection is inherited from the underlying "
+ "connection.");
}
@Override
public String getSharingProfileIdentifier() {
return sharingProfileIdentifier;
}
@Override
public void setSharingProfileIdentifier(String sharingProfileIdentifier) {
this.sharingProfileIdentifier = sharingProfileIdentifier;
}
/**
* Shares this active connection with the user that retrieved it, returning
* a SharedConnectionDefinition that can be used to establish a tunnel to
* the shared connection. If provided, access within the shared connection
* will be restricted by the sharing profile with the given identifier.
*
* @param identifier
* The identifier of the sharing profile that defines the restrictions
* applying to the shared connection, or null if no such restrictions
* apply.
*
* @return
* A new SharedConnectionDefinition which can be used to establish a
* tunnel to the shared connection.
*
* @throws GuacamoleException
* If permission to share this active connection is denied.
*/
private SharedConnectionDefinition share(String identifier) throws GuacamoleException {
return sharingService.shareConnection(getCurrentUser(), connectionRecord, identifier);
}
@Override
public UserCredentials getSharingCredentials(String identifier)
throws GuacamoleException {
return sharingService.getSharingCredentials(share(identifier));
}
@Override
public Date getStartDate() {
return startDate;
}
@Override
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
@Override
public String getRemoteHost() {
return remoteHost;
}
@Override
public void setRemoteHost(String remoteHost) {
this.remoteHost = remoteHost;
}
@Override
public String getUsername() {
return username;
}
@Override
public void setUsername(String username) {
this.username = username;
}
@Override
public GuacamoleTunnel getTunnel() {
return tunnel;
}
@Override
public void setTunnel(GuacamoleTunnel tunnel) {
this.tunnel = tunnel;
}
@Override
public boolean isConnectable() {
return connectable;
}
@Override
public GuacamoleTunnel connect(GuacamoleClientInformation info,
Map<String, String> tokens) throws GuacamoleException {
// Establish connection only if connecting is allowed
if (isConnectable())
return tunnelService.getGuacamoleTunnel(getCurrentUser(), share(null), info, tokens);
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public int getActiveConnections() {
return 0;
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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.
*/
/**
* Classes related to currently-active connections.
*/
package org.apache.guacamole.auth.jdbc.activeconnection;

View File

@@ -0,0 +1,163 @@
/*
* 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.base;
import java.util.Collection;
import java.util.List;
import org.apache.guacamole.auth.jdbc.user.UserModel;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Common interface for mapping activity records.
*
* @param <ModelType>
* The type of model object representing the activity records mapped by
* this mapper.
*/
public interface ActivityRecordMapper<ModelType> {
/**
* Inserts the given activity record.
*
* @param record
* The activity record to insert.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* The number of rows inserted.
*/
int insert(@Param("record") ModelType record,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Updates the given activity record in the database, assigning an end
* date. No column of the existing activity record is updated except for
* the end date. If the record does not actually exist, this operation has
* no effect.
*
* @param record
* The activity record to update.
*
* @return
* The number of rows updated.
*/
int updateEndDate(@Param("record") ModelType record);
/**
* Searches for up to <code>limit</code> activity records that contain
* the given terms, sorted by the given predicates, regardless of whether
* the data they are associated with is readable by any particular user.
* This should only be called on behalf of a system administrator. If
* records are needed by a non-administrative user who must have explicit
* read rights, use {@link searchReadable()} instead.
*
* @param identifier
* The optional identifier of the object whose history is being
* retrieved, or null if records related to any such object should be
* retrieved.
*
* @param recordIdentifier
* The identifier of the specific history record to retrieve, if not
* all matching records. Search terms, etc. will still be applied to
* the single record.
*
* @param terms
* The search terms that must match the returned records.
*
* @param sortPredicates
* A list of predicates to sort the returned records by, in order of
* priority.
*
* @param limit
* The maximum number of records that should be returned.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* The results of the search performed with the given parameters.
*/
List<ModelType> search(@Param("identifier") String identifier,
@Param("recordIdentifier") String recordIdentifier,
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
@Param("limit") int limit,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Searches for up to <code>limit</code> activity records that contain
* the given terms, sorted by the given predicates. Only records that are
* associated with data explicitly readable by the given user will be
* returned. If records are needed by a system administrator (who, by
* definition, does not need explicit read rights), use {@link search()}
* instead.
*
* @param identifier
* The optional identifier of the object whose history is being
* retrieved, or null if records related to any such object should be
* retrieved.
*
* @param user
* The user whose permissions should determine whether a record is
* returned.
*
* @param recordIdentifier
* The identifier of the specific history record to retrieve, if not
* all matching records. Search terms, etc. will still be applied to
* the single record.
*
* @param terms
* The search terms that must match the returned records.
*
* @param sortPredicates
* A list of predicates to sort the returned records by, in order of
* priority.
*
* @param limit
* The maximum number of records that should be returned.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the user. If
* no groups are given, only permissions directly granted to the user
* will be used.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* The results of the search performed with the given parameters.
*/
List<ModelType> searchReadable(@Param("identifier") String identifier,
@Param("user") UserModel user,
@Param("recordIdentifier") String recordIdentifier,
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
@Param("limit") int limit,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
}

View File

@@ -0,0 +1,193 @@
/*
* 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.base;
import java.util.Date;
/**
* A single activity record representing an arbitrary activity performed by a
* user.
*/
public class ActivityRecordModel {
/**
* The ID of this object in the database, if any.
*/
private Integer recordID;
/**
* The database ID of the user associated with this activity record.
*/
private Integer userID;
/**
* The username of the user that performed the activity.
*/
private String username;
/**
* The remote host associated with the user that performed the activity.
*/
private String remoteHost;
/**
* The time the activity was initiated by the associated user.
*/
private Date startDate;
/**
* The time the activity ended, or null if the end time is not known or
* the activity is still in progress.
*/
private Date endDate;
/**
* Returns the ID of this record in the database, if it exists.
*
* @return
* The ID of this record in the database, or null if this record was
* not retrieved from the database.
*/
public Integer getRecordID() {
return recordID;
}
/**
* Sets the database ID of this record to the given value.
*
* @param recordID
* The ID to assign to this object.
*/
public void setRecordID(Integer recordID) {
this.recordID = recordID;
}
/**
* Returns the database ID of the user associated with this activity
* record.
*
* @return
* The database ID of the user associated with this activity record.
*/
public Integer getUserID() {
return userID;
}
/**
* Sets the database ID of the user associated with this activity record.
*
* @param userID
* The database ID of the user to associate with this activity
* record.
*/
public void setUserID(Integer userID) {
this.userID = userID;
}
/**
* Returns the username of the user that performed the activity associated
* with this record.
*
* @return
* The username of the user that performed the activity associated with
* this record.
*/
public String getUsername() {
return username;
}
/**
* Sets the username of the user that performed the activity associated
* with this record.
*
* @param username
* The username of the user that performed the activity associated with
* this record.
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Returns the remote host associated with the user that performed the
* activity.
*
* @return
* The remote host associated with the user that performed the activity.
*/
public String getRemoteHost() {
return remoteHost;
}
/**
* Sets the remote host associated with the user that performed the
* activity.
*
* @param remoteHost
* The remote host associated with the user that performed the activity.
*/
public void setRemoteHost(String remoteHost) {
this.remoteHost = remoteHost;
}
/**
* Returns the time the activity was initiated by the associated user.
*
* @return
* The time the activity was initiated by the associated user.
*/
public Date getStartDate() {
return startDate;
}
/**
* Sets the time the activity was initiated by the associated user.
*
* @param startDate
* The time the activity was initiated by the associated user.
*/
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
/**
* Returns the time the activity ended, or null if the end time is not
* known or the activity is still in progress.
*
* @return
* The time the activity ended, or null if the end time is not known or
* the activity is still in progress.
*/
public Date getEndDate() {
return endDate;
}
/**
* Sets the time the activity ended, if known.
*
* @param endDate
* The time the activity ended, or null if the end time is not known or
* the activity is still in progress.
*/
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
}

View File

@@ -0,0 +1,291 @@
/*
* 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.base;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A search term for querying historical records of arbitrary activities. This
* will contain a the search term in string form and, if that string appears to
* be a date. a corresponding date range.
*/
public class ActivityRecordSearchTerm {
/**
* A pattern that can match a year, year and month, or year and month and
* day.
*/
private static final Pattern DATE_PATTERN =
Pattern.compile("(\\d+)(?:-(\\d+)?(?:-(\\d+)?)?)?");
/**
* The index of the group within <code>DATE_PATTERN</code> containing the
* year number.
*/
private static final int YEAR_GROUP = 1;
/**
* The index of the group within <code>DATE_PATTERN</code> containing the
* month number, if any.
*/
private static final int MONTH_GROUP = 2;
/**
* The index of the group within <code>DATE_PATTERN</code> containing the
* day number, if any.
*/
private static final int DAY_GROUP = 3;
/**
* The start of the date range for records that should be retrieved, if the
* provided search term appears to be a date.
*/
private final Date startDate;
/**
* The end of the date range for records that should be retrieved, if the
* provided search term appears to be a date.
*/
private final Date endDate;
/**
* The string that should be searched for.
*/
private final String term;
/**
* Parse the given string as an integer, returning the provided default
* value if the string is null.
*
* @param str
* The string to parse as an integer.
*
* @param defaultValue
* The value to return if <code>str</code> is null.
*
* @return
* The parsed value, or the provided default value if <code>str</code>
* is null.
*/
private static int parseInt(String str, int defaultValue) {
if (str == null)
return defaultValue;
return Integer.parseInt(str);
}
/**
* Returns a new calendar representing the last millisecond of the same
* year as <code>calendar</code>.
*
* @param calendar
* The calendar defining the year whose end (last millisecond) is to be
* returned.
*
* @return
* A new calendar representing the last millisecond of the same year as
* <code>calendar</code>.
*/
private static Calendar getEndOfYear(Calendar calendar) {
// Get first day of next year
Calendar endOfYear = Calendar.getInstance();
endOfYear.clear();
endOfYear.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + 1);
// Transform into the last millisecond of the given year
endOfYear.add(Calendar.MILLISECOND, -1);
return endOfYear;
}
/**
* Returns a new calendar representing the last millisecond of the same
* month and year as <code>calendar</code>.
*
* @param calendar
* The calendar defining the month and year whose end (last millisecond)
* is to be returned.
*
* @return
* A new calendar representing the last millisecond of the same month
* and year as <code>calendar</code>.
*/
private static Calendar getEndOfMonth(Calendar calendar) {
// Copy given calender only up to given month
Calendar endOfMonth = Calendar.getInstance();
endOfMonth.clear();
endOfMonth.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
endOfMonth.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
// Advance to the last millisecond of the given month
endOfMonth.add(Calendar.MONTH, 1);
endOfMonth.add(Calendar.MILLISECOND, -1);
return endOfMonth;
}
/**
* Returns a new calendar representing the last millisecond of the same
* year, month, and day as <code>calendar</code>.
*
* @param calendar
* The calendar defining the year, month, and day whose end
* (last millisecond) is to be returned.
*
* @return
* A new calendar representing the last millisecond of the same year,
* month, and day as <code>calendar</code>.
*/
private static Calendar getEndOfDay(Calendar calendar) {
// Copy given calender only up to given month
Calendar endOfMonth = Calendar.getInstance();
endOfMonth.clear();
endOfMonth.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
endOfMonth.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
endOfMonth.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH));
// Advance to the last millisecond of the given day
endOfMonth.add(Calendar.DAY_OF_MONTH, 1);
endOfMonth.add(Calendar.MILLISECOND, -1);
return endOfMonth;
}
/**
* Creates a new ActivityRecordSearchTerm representing the given string.
* If the given string appears to be a date, the start and end dates of the
* implied date range will be automatically determined and made available
* via getStartDate() and getEndDate() respectively.
*
* @param term
* The string that should be searched for.
*/
public ActivityRecordSearchTerm(String term) {
// Search terms absolutely must not be null
if (term == null)
throw new NullPointerException("Search terms may not be null");
this.term = term;
// Parse start/end of date range if term appears to be a date
Matcher matcher = DATE_PATTERN.matcher(term);
if (matcher.matches()) {
// Retrieve date components from term
String year = matcher.group(YEAR_GROUP);
String month = matcher.group(MONTH_GROUP);
String day = matcher.group(DAY_GROUP);
// Parse start date from term
Calendar startCalendar = Calendar.getInstance();
startCalendar.clear();
startCalendar.set(
Integer.parseInt(year),
parseInt(month, 1) - 1,
parseInt(day, 1)
);
Calendar endCalendar;
// Derive end date from start date
if (month == null) {
endCalendar = getEndOfYear(startCalendar);
}
else if (day == null) {
endCalendar = getEndOfMonth(startCalendar);
}
else {
endCalendar = getEndOfDay(startCalendar);
}
// Convert results back into dates
this.startDate = startCalendar.getTime();
this.endDate = endCalendar.getTime();
}
// The search term doesn't look like a date
else {
this.startDate = null;
this.endDate = null;
}
}
/**
* Returns the start of the date range for records that should be retrieved,
* if the provided search term appears to be a date.
*
* @return
* The start of the date range.
*/
public Date getStartDate() {
return startDate;
}
/**
* Returns the end of the date range for records that should be retrieved,
* if the provided search term appears to be a date.
*
* @return
* The end of the date range.
*/
public Date getEndDate() {
return endDate;
}
/**
* Returns the string that should be searched for.
*
* @return
* The search term.
*/
public String getTerm() {
return term;
}
@Override
public int hashCode() {
return term.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof ActivityRecordSearchTerm))
return false;
return ((ActivityRecordSearchTerm) obj).getTerm().equals(getTerm());
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.base;
import org.apache.guacamole.net.auth.ActivityRecordSet;
/**
* A sort predicate which species the property to use when sorting activity
* records, along with the sort order.
*/
public class ActivityRecordSortPredicate {
/**
* The property to use when sorting ActivityRecords.
*/
private final ActivityRecordSet.SortableProperty property;
/**
* Whether the sort order is descending (true) or ascending (false).
*/
private final boolean descending;
/**
* Creates a new ActivityRecordSortPredicate with the given sort property
* and sort order.
*
* @param property
* The property to use when sorting ActivityRecords.
*
* @param descending
* Whether the sort order is descending (true) or ascending (false).
*/
public ActivityRecordSortPredicate(ActivityRecordSet.SortableProperty property,
boolean descending) {
this.property = property;
this.descending = descending;
}
/**
* Returns the property that should be used when sorting ActivityRecords.
*
* @return
* The property that should be used when sorting ActivityRecords.
*/
public ActivityRecordSet.SortableProperty getProperty() {
return property;
}
/**
* Returns whether the sort order is descending.
*
* @return
* true if the sort order is descending, false if the sort order is
* ascending.
*/
public boolean isDescending() {
return descending;
}
}

View File

@@ -0,0 +1,180 @@
/*
* 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.base;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Map of arbitrary attribute name/value pairs which can alternatively be
* exposed as a collection of model objects.
*/
public class ArbitraryAttributeMap extends HashMap<String, String> {
/**
* Creates a new ArbitraryAttributeMap containing the name/value pairs
* within the given collection of model objects.
*
* @param models
* The model objects of all attributes which should be stored in the
* new map as name/value pairs.
*
* @return
* A new ArbitraryAttributeMap containing the name/value pairs within
* the given collection of model objects.
*/
public static ArbitraryAttributeMap fromModelCollection(Collection<ArbitraryAttributeModel> models) {
// Add all name/value pairs from the given collection to the map
ArbitraryAttributeMap map = new ArbitraryAttributeMap();
for (ArbitraryAttributeModel model : models)
map.put(model.getName(), model.getValue());
return map;
}
/**
* Returns a collection of model objects which mirrors the contents of this
* ArbitraryAttributeMap. Each name/value pair within the map is reflected
* by a corresponding model object within the returned collection. Removing
* a model object from the collection removes the corresponding name/value
* pair from the map. Adding a new model object to the collection adds a
* corresponding name/value pair to the map. Changes to a model object
* within the collection are NOT reflected on the map, however.
*
* @return
* A collection of model objects which mirrors the contents of this
* ArbitraryAttributeMap.
*/
public Collection<ArbitraryAttributeModel> toModelCollection() {
return new AbstractCollection<ArbitraryAttributeModel>() {
@Override
public void clear() {
ArbitraryAttributeMap.this.clear();
}
@Override
public boolean remove(Object o) {
// The Collection view of an ArbitraryAttributeMap can contain
// only ArbitraryAttributeModel objects
if (!(o instanceof ArbitraryAttributeModel))
return false;
// Remove only if key is actually present
ArbitraryAttributeModel model = (ArbitraryAttributeModel) o;
if (!ArbitraryAttributeMap.this.containsKey(model.getName()))
return false;
// The attribute should be removed only if the value matches
String currentValue = ArbitraryAttributeMap.this.get(model.getName());
if (currentValue == null) {
if (model.getValue() != null)
return false;
}
else if (!currentValue.equals(model.getValue()))
return false;
ArbitraryAttributeMap.this.remove(model.getName());
return true;
}
@Override
public boolean add(ArbitraryAttributeModel e) {
String newValue = e.getValue();
String oldValue = put(e.getName(), newValue);
// If null value is being added, collection changed only if
// old value was non-null
if (newValue == null)
return oldValue != null;
// Collection changed if value changed
return !newValue.equals(oldValue);
}
@Override
public boolean contains(Object o) {
// The Collection view of an ArbitraryAttributeMap can contain
// only ArbitraryAttributeModel objects
if (!(o instanceof ArbitraryAttributeModel))
return false;
// No need to check the value of the attribute if the attribute
// is not even present
ArbitraryAttributeModel model = (ArbitraryAttributeModel) o;
String value = get(model.getName());
if (value == null)
return false;
// The name/value pair is present only if the value matches
return value.equals(model.getValue());
}
@Override
public Iterator<ArbitraryAttributeModel> iterator() {
// Get iterator over all string name/value entries
final Iterator<Map.Entry<String, String>> iterator = entrySet().iterator();
// Dynamically translate each string name/value entry into a
// corresponding attribute model object as iteration continues
return new Iterator<ArbitraryAttributeModel>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public ArbitraryAttributeModel next() {
Map.Entry<String, String> entry = iterator.next();
return new ArbitraryAttributeModel(entry.getKey(),
entry.getValue());
}
@Override
public void remove() {
iterator.remove();
}
};
}
@Override
public int size() {
return ArbitraryAttributeMap.this.size();
}
};
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.base;
/**
* A single attribute name/value pair belonging to a object which implements
* the Attributes interface, such as a Connection or User. Attributes stored
* as raw name/value pairs are the attributes which are given to the database
* authentication extension for storage by other extensions. Attributes which
* are directly supported by the database authentication extension have defined
* columns and properties with proper types, constraints, etc.
*/
public class ArbitraryAttributeModel {
/**
* The name of the attribute.
*/
private String name;
/**
* The value the attribute is set to.
*/
private String value;
/**
* Creates a new ArbitraryAttributeModel with its name and value both set
* to null.
*/
public ArbitraryAttributeModel() {
}
/**
* Creates a new ArbitraryAttributeModel with its name and value
* initialized to the given values.
*
* @param name
* The name of the attribute.
*
* @param value
* The value the attribute is set to.
*/
public ArbitraryAttributeModel(String name, String value) {
this.name = name;
this.value = value;
}
/**
* Returns the name of this attribute.
*
* @return
* The name of this attribute.
*/
public String getName() {
return name;
}
/**
* Sets the name of this attribute.
*
* @param name
* The name of this attribute.
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns the value of this attribute.
*
* @return
* The value of this attribute.
*/
public String getValue() {
return value;
}
/**
* Sets the value of this attribute.
*
* @param value
* The value of this attribute.
*/
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.base;
/**
* Object representation of a Guacamole object which can be the child of another
* object, such as a connection or sharing profile, as represented in the
* database.
*/
public abstract class ChildObjectModel extends ObjectModel {
/**
* The unique identifier which identifies the parent of this object.
*/
private String parentIdentifier;
/**
* Creates a new, empty object.
*/
public ChildObjectModel() {
}
/**
* Returns the identifier of the parent connection group, or null if the
* parent connection group is the root connection group.
*
* @return
* The identifier of the parent connection group, or null if the parent
* connection group is the root connection group.
*/
public String getParentIdentifier() {
return parentIdentifier;
}
/**
* Sets the identifier of the parent connection group.
*
* @param parentIdentifier
* The identifier of the parent connection group, or null if the parent
* connection group is the root connection group.
*/
public void setParentIdentifier(String parentIdentifier) {
this.parentIdentifier = parentIdentifier;
}
}

View File

@@ -0,0 +1,151 @@
/*
* 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.base;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating objects that have unique identifiers, such as the objects
* within directories. This service will automatically enforce the permissions
* of the current user.
*
* @param <InternalType>
* The specific internal implementation of the type of object this service
* provides access to.
*
* @param <ExternalType>
* The external interface or implementation of the type of object this
* service provides access to, as defined by the guacamole-ext API.
*/
public interface DirectoryObjectService<InternalType, ExternalType> {
/**
* Retrieves the single object that has the given identifier, if it exists
* and the user has permission to read it.
*
* @param user
* The user retrieving the object.
*
* @param identifier
* The identifier of the object to retrieve.
*
* @return
* The object having the given identifier, or null if no such object
* exists.
*
* @throws GuacamoleException
* If an error occurs while retrieving the requested object.
*/
InternalType retrieveObject(ModeledAuthenticatedUser user, String identifier)
throws GuacamoleException;
/**
* Retrieves all objects that have the identifiers in the given collection.
* Only objects that the user has permission to read will be returned.
*
* @param user
* The user retrieving the objects.
*
* @param identifiers
* The identifiers of the objects to retrieve.
*
* @return
* The objects having the given identifiers.
*
* @throws GuacamoleException
* If an error occurs while retrieving the requested objects.
*/
Collection<InternalType> retrieveObjects(ModeledAuthenticatedUser user,
Collection<String> identifiers) throws GuacamoleException;
/**
* Creates the given object. If the object already exists, an error will be
* thrown.
*
* @param user
* The user creating the object.
*
* @param object
* The object to create.
*
* @return
* The newly-created object.
*
* @throws GuacamoleException
* If the user lacks permission to create the object, or an error
* occurs while creating the object.
*/
InternalType createObject(ModeledAuthenticatedUser user, ExternalType object)
throws GuacamoleException;
/**
* Deletes the object having the given identifier. If no such object
* exists, this function has no effect.
*
* @param user
* The user deleting the object.
*
* @param identifier
* The identifier of the object to delete.
*
* @throws GuacamoleException
* If the user lacks permission to delete the object, or an error
* occurs while deleting the object.
*/
void deleteObject(ModeledAuthenticatedUser user, String identifier)
throws GuacamoleException;
/**
* Updates the given object, applying any changes that have been made. If
* no such object exists, this function has no effect.
*
* @param user
* The user updating the object.
*
* @param object
* The object to update.
*
* @throws GuacamoleException
* If the user lacks permission to update the object, or an error
* occurs while updating the object.
*/
void updateObject(ModeledAuthenticatedUser user, InternalType object)
throws GuacamoleException;
/**
* Returns the set of all identifiers for all objects that the user has
* read access to.
*
* @param user
* The user retrieving the identifiers.
*
* @return
* The set of all identifiers for all objects.
*
* @throws GuacamoleException
* If an error occurs while reading identifiers.
*/
Set<String> getIdentifiers(ModeledAuthenticatedUser user) throws GuacamoleException;
}

View File

@@ -0,0 +1,86 @@
/*
* 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.base;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Mapper for entities. An entity is the base concept behind a user or user
* group, and serves as a common point for granting permissions and defining
* group membership.
*/
public interface EntityMapper {
/**
* Inserts the given entity into the database. If the entity already
* exists, this will result in an error.
*
* @param entity
* The entity to insert.
*
* @return
* The number of rows inserted.
*/
int insert(@Param("entity") EntityModel entity);
/**
* Returns the set of all group identifiers of which the given entity is a
* member, taking into account the given collection of known group
* memberships which are not necessarily defined within the database.
*
* NOTE: This query is expected to handle recursion through the membership
* graph on its own. If the database engine does not support recursive
* queries (isRecursiveQuerySupported() of JDBCEnvironment returns false),
* then this query will only return one level of depth past the effective
* groups given and will need to be invoked multiple times.
*
* @param entity
* The entity whose effective groups should be returned.
*
* @param effectiveGroups
* The identifiers of any known effective groups that should be taken
* into account, such as those defined externally to the database.
*
* @param recursive
* Whether the query should leverage database engine features to return
* absolutely all effective groups, including those inherited through
* group membership. If false, this query will return only one level of
* depth and may need to be executed multiple times. If it is known
* that the database engine in question will always support (or always
* not support) recursive queries, this parameter may be ignored.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* The set of identifiers of all groups that the given entity is a
* member of, including those where membership is inherited through
* membership in other groups.
*/
Set<String> selectEffectiveGroupIdentifiers(@Param("entity") EntityModel entity,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("recursive") boolean recursive,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
}

View File

@@ -0,0 +1,113 @@
/*
* 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.base;
/**
* Base representation of a Guacamole object that can be granted permissions
* (an "entity"), such as a user or user group, as represented in the database.
* Each entity has three base properties:
*
* 1. The "entityID", which points to the common entry in the
* guacamole_entity table and is common to any type of entity.
*
* 2. The "objectID", which points to the type-specific entry for the object
* in question (ie: an entry in guacamole_user or guacamole_user_group).
*
* 3. The "identifier", which contains the unique "name" value defined for
* the entity within the guacamole_entity table.
*/
public abstract class EntityModel extends ObjectModel {
/**
* The ID of the entity entry which corresponds to this object in the
* database, if any. Note that this is distinct from the objectID,
* inherited from ObjectModel, which is specific to the actual type of
* object represented by the entity.
*/
private Integer entityID;
/**
* The type of object represented by the entity (user or user group).
*/
private EntityType type;
/**
* Creates a new, empty entity.
*/
public EntityModel() {
}
/**
* Creates a new entity of the given type which is otherwise empty.
*
* @param type
* The type to assign to the new entity.
*/
public EntityModel(EntityType type) {
this.type = type;
}
/**
* Returns the ID of the entity entry which corresponds to this object in
* the database, if it exists. Note that this is distinct from the objectID,
* inherited from ObjectModel, which is specific to the actual type of
* object represented by the entity.
*
* @return
* The ID of this entity in the database, or null if this entity was
* not retrieved from the database.
*/
public Integer getEntityID() {
return entityID;
}
/**
* Sets the ID of this entity to the given value.
*
* @param entityID
* The ID to assign to this entity.
*/
public void setEntityID(Integer entityID) {
this.entityID = entityID;
}
/**
* Returns the type of object represented by the entity. Each entity may be
* either a user or a user group.
*
* @return
* The type of object represented by the entity.
*/
public EntityType getEntityType() {
return type;
}
/**
* Sets the type of object represented by the entity. Each entity may be
* either a user or a user group.
*
* @param type
* The type of object represented by the entity.
*/
public void setEntityType(EntityType type) {
this.type = type;
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.base;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.guice.transactional.Transactional;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating entities.
*/
public class EntityService {
/**
* The Guacamole server environment.
*/
@Inject
private JDBCEnvironment environment;
/**
* Mapper for Entity model objects.
*/
@Inject
private EntityMapper entityMapper;
/**
* The current SQL session used by MyBatis.
*/
@Inject
private SqlSession sqlSession;
/**
* Returns the set of all group identifiers of which the given entity is a
* member, taking into account the given collection of known group
* memberships which are not necessarily defined within the database.
*
* Note that group visibility with respect to the queried entity is NOT
* taken into account. If the entity is a member of a group, the identifier
* of that group will be included in the returned set even if the current
* user lacks "READ" permission for that group.
*
* @param entity
* The entity whose effective groups should be returned.
*
* @param effectiveGroups
* The identifiers of any known effective groups that should be taken
* into account, such as those defined externally to the database.
*
* @return
* The set of identifiers of all groups that the given entity is a
* member of, including those where membership is inherited through
* membership in other groups.
*/
@Transactional
public Set<String> retrieveEffectiveGroups(ModeledPermissions<? extends EntityModel> entity,
Collection<String> effectiveGroups) {
CaseSensitivity caseSensitivity = environment.getCaseSensitivity();
// Retrieve the effective user groups of the given entity, recursively if possible
boolean recursive = environment.isRecursiveQuerySupported(sqlSession);
Set<String> identifiers = entityMapper.selectEffectiveGroupIdentifiers(
entity.getModel(), effectiveGroups, recursive, caseSensitivity);
// If the set of user groups retrieved was not produced recursively,
// manually repeat the query to expand the set until all effective
// groups have been found
if (!recursive && !identifiers.isEmpty()) {
Set<String> previousIdentifiers;
do {
previousIdentifiers = identifiers;
identifiers = entityMapper.selectEffectiveGroupIdentifiers(
entity.getModel(), previousIdentifiers, false,
caseSensitivity);
} while (identifiers.size() > previousIdentifiers.size());
}
return identifiers;
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.base;
/**
* The type of object represented by an entity. Each entity may represent
* either a user or a user group.
*/
public enum EntityType {
/**
* An individual user.
*/
USER,
/**
* A group of users and/or other groups.
*/
USER_GROUP
}

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.base;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.AtomicDirectoryOperation;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.Identifiable;
import org.mybatis.guice.transactional.Transactional;
/**
* An implementation of Directory that uses database transactions to guarantee
* atomicity for any operations supplied to tryAtomically().
*/
public abstract class JDBCDirectory<ObjectType extends Identifiable>
extends RestrictedObject implements Directory<ObjectType> {
@Override
@Transactional
public void tryAtomically(AtomicDirectoryOperation<ObjectType> operation)
throws GuacamoleException {
// Execute the operation atomically - the @Transactional annotation
// specifies that the entire operation will be performed in a transaction
operation.executeOperation(true, this);
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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.base;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.UUID;
import org.apache.guacamole.net.auth.ActivityRecord;
/**
* An ActivityRecord which is backed by a database model.
*/
public class ModeledActivityRecord implements ActivityRecord {
/**
* The model object backing this activity record.
*/
private final ActivityRecordModel model;
/**
* The UUID namespace of the type 3 name UUID to generate for the record.
* This namespace should correspond to the source of IDs for the model such
* that the combination of this namespace with the numeric record ID will
* always be unique and deterministic across all activity records,
* regardless of record type.
*/
private final UUID namespace;
/**
* Creates a new ModeledActivityRecord backed by the given model object.
* Changes to this record will affect the backing model object, and changes
* to the backing model object will affect this record.
*
* @param namespace
* The UUID namespace of the type 3 name UUID to generate for the
* record. This namespace should correspond to the source of IDs for
* the model such that the combination of this namespace with the
* numeric record ID will always be unique and deterministic across all
* activity records, regardless of record type.
*
* @param model
* The model object to use to back this activity record.
*/
public ModeledActivityRecord(UUID namespace, ActivityRecordModel model) {
this.model = model;
this.namespace = namespace;
}
/**
* Returns the backing model object. Changes to this record will affect the
* backing model object, and changes to the backing model object will
* affect this record.
*
* @return
* The backing model object.
*/
public ActivityRecordModel getModel() {
return model;
}
@Override
public Date getStartDate() {
return model.getStartDate();
}
@Override
public Date getEndDate() {
return model.getEndDate();
}
@Override
public String getRemoteHost() {
return model.getRemoteHost();
}
@Override
public String getUsername() {
return model.getUsername();
}
@Override
public boolean isActive() {
return false;
}
@Override
public String getIdentifier() {
Integer id = model.getRecordID();
if (id == null)
return null;
return id.toString();
}
@Override
public UUID getUUID() {
Integer id = model.getRecordID();
if (id == null)
return null;
// Convert record ID to a name UUID in the given namespace
return UUID.nameUUIDFromBytes(ByteBuffer.allocate(24)
.putLong(namespace.getMostSignificantBits())
.putLong(namespace.getLeastSignificantBits())
.putLong(id)
.array());
}
}

View File

@@ -0,0 +1,163 @@
/*
* 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.base;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.ActivityRecord;
import org.apache.guacamole.net.auth.ActivityRecordSet;
import org.apache.guacamole.net.auth.ActivityRecordSet.SortableProperty;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A JDBC implementation of ActivityRecordSet. Calls to asCollection() will
* query history records using an implementation-specific mechanism. Which
* records are returned will be determined by the values passed in earlier.
*
* @param <RecordType>
* The type of ActivityRecord contained within this set.
*/
public abstract class ModeledActivityRecordSet<RecordType extends ActivityRecord>
extends RestrictedObject implements ActivityRecordSet<RecordType> {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(ModeledActivityRecordSet.class);
/**
* The set of strings that each must occur somewhere within the returned
* records, whether within the associated username, an associated date, or
* other related data. If non-empty, any record not matching each of the
* strings within the collection will be excluded from the results.
*/
private final Set<ActivityRecordSearchTerm> requiredContents =
new HashSet<>();
/**
* The maximum number of history records that should be returned by a call
* to asCollection().
*/
private int limit = Integer.MAX_VALUE;
/**
* A list of predicates to apply while sorting the resulting records,
* describing the properties involved and the sort order for those
* properties.
*/
private final List<ActivityRecordSortPredicate> sortPredicates =
new ArrayList<>();
/**
* Retrieves the history records matching the given criteria. Retrieves up
* to <code>limit</code> history records matching the given terms and sorted
* by the given predicates. Only history records associated with data that
* the given user can read are returned.
*
* @param user
* The user retrieving the history.
*
* @param recordIdentifier
* The identifier of the specific history record to retrieve, if not
* all matching records. Search terms, etc. will still be applied to
* the single record.
*
* @param requiredContents
* The search terms that must be contained somewhere within each of the
* returned records.
*
* @param sortPredicates
* A list of predicates to sort the returned records by, in order of
* priority.
*
* @param limit
* The maximum number of records that should be returned.
*
* @return
* A collection of all history records matching the given criteria.
*
* @throws GuacamoleException
* If permission to read the history records is denied.
*/
protected abstract List<RecordType> retrieveHistory(
AuthenticatedUser user, String recordIdentifier,
Set<ActivityRecordSearchTerm> requiredContents,
List<ActivityRecordSortPredicate> sortPredicates,
int limit) throws GuacamoleException;
@Override
public RecordType get(String identifier) throws GuacamoleException {
List<RecordType> records = retrieveHistory(getCurrentUser(),
identifier, requiredContents, sortPredicates, limit);
if (records.isEmpty())
return null;
if (records.size() == 1)
return records.get(0);
logger.warn("Multiple history records match ID \"{}\"! This should "
+ "not be possible and may indicate a bug or database "
+ "corruption.", identifier);
return null;
}
@Override
public Collection<RecordType> asCollection()
throws GuacamoleException {
return retrieveHistory(getCurrentUser(), null, requiredContents,
sortPredicates, limit);
}
@Override
public ModeledActivityRecordSet<RecordType> contains(String value)
throws GuacamoleException {
requiredContents.add(new ActivityRecordSearchTerm(value));
return this;
}
@Override
public ModeledActivityRecordSet<RecordType> limit(int limit) throws GuacamoleException {
this.limit = Math.min(this.limit, limit);
return this;
}
@Override
public ModeledActivityRecordSet<RecordType> sort(SortableProperty property, boolean desc)
throws GuacamoleException {
sortPredicates.add(new ActivityRecordSortPredicate(
property,
desc
));
return this;
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.base;
import org.apache.guacamole.auth.jdbc.connectiongroup.RootConnectionGroup;
/**
* Common base class for objects that will ultimately be made available through
* the Directory class. All such objects will need the same base set of queries
* to fulfill the needs of the Directory class.
*
* @param <ModelType>
* The type of model object that corresponds to this object.
*/
public abstract class ModeledChildDirectoryObject<ModelType extends ChildObjectModel>
extends ModeledDirectoryObject<ModelType> {
/**
* Returns the identifier of the parent connection group, which cannot be
* null. If the parent is the root connection group, this will be
* RootConnectionGroup.IDENTIFIER.
*
* @return
* The identifier of the parent connection group.
*/
public String getParentIdentifier() {
// Translate null parent to proper identifier
String parentIdentifier = getModel().getParentIdentifier();
if (parentIdentifier == null)
return RootConnectionGroup.IDENTIFIER;
return parentIdentifier;
}
/**
* Sets the identifier of the associated parent connection group. If the
* parent is the root connection group, this should be
* RootConnectionGroup.IDENTIFIER.
*
* @param parentIdentifier
* The identifier of the connection group to associate as this object's
* parent.
*/
public void setParentIdentifier(String parentIdentifier) {
// Translate root identifier back into null
if (parentIdentifier != null
&& parentIdentifier.equals(RootConnectionGroup.IDENTIFIER))
parentIdentifier = null;
getModel().setParentIdentifier(parentIdentifier);
}
}

View File

@@ -0,0 +1,209 @@
/*
* 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.base;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.net.auth.Identifiable;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating objects that can be children of other objects. This service will
* automatically enforce the permissions of the current user.
*
* @param <InternalType>
* The specific internal implementation of the type of object this service
* provides access to.
*
* @param <ExternalType>
* The external interface or implementation of the type of object this
* service provides access to, as defined by the guacamole-ext API.
*
* @param <ModelType>
* The underlying model object used to represent InternalType in the
* database.
*/
public abstract class ModeledChildDirectoryObjectService<InternalType extends ModeledChildDirectoryObject<ModelType>,
ExternalType extends Identifiable, ModelType extends ChildObjectModel>
extends ModeledDirectoryObjectService<InternalType, ExternalType, ModelType> {
/**
* Returns the permission set associated with the given user and related
* to the type of objects which can be parents of the child objects handled
* by this directory object service, taking into account permission
* inheritance via user groups.
*
* @param user
* The user whose permissions are being retrieved.
*
* @return
* A permission set which contains the permissions associated with the
* given user and related to the type of objects which can be parents
* of the child objects handled by this directory object service.
*
* @throws GuacamoleException
* If permission to read the user's permissions is denied.
*/
protected abstract ObjectPermissionSet getParentEffectivePermissionSet(
ModeledAuthenticatedUser user) throws GuacamoleException;
/**
* Returns the set of parent objects that are modified by the given model
* object (by virtue of the object changing parents). If the model is not
* changing parents, the resulting collection will be empty.
*
* @param user
* The user making the given changes to the model.
*
* @param identifier
* The identifier of the object that has been modified, if it exists.
* If the object is being created, this will be null.
*
* @param model
* The model that has been modified, if any. If the object is being
* deleted, this will be null.
*
* @return
* A collection of the identifiers of all parents that will be affected
* (updated) by the change.
*
* @throws GuacamoleException
* If an error occurs while determining which parents are affected.
*/
protected Collection<String> getModifiedParents(ModeledAuthenticatedUser user,
String identifier, ModelType model) throws GuacamoleException {
// Get old parent identifier
String oldParentIdentifier = null;
if (identifier != null) {
ModelType current = retrieveObject(user, identifier).getModel();
oldParentIdentifier = current.getParentIdentifier();
}
// Get new parent identifier
String parentIdentifier = null;
if (model != null) {
parentIdentifier = model.getParentIdentifier();
// If both parents have the same identifier, nothing has changed
if (parentIdentifier != null && parentIdentifier.equals(oldParentIdentifier))
return Collections.<String>emptyList();
}
// Return collection of all non-root parents involved
Collection<String> parents = new ArrayList<String>(2);
if (oldParentIdentifier != null) parents.add(oldParentIdentifier);
if (parentIdentifier != null) parents.add(parentIdentifier);
return parents;
}
/**
* Returns whether the given user has permission to modify the parents
* affected by the modifications made to the given model object.
*
* @param user
* The user who changed the model object.
*
* @param identifier
* The identifier of the object that has been modified, if it exists.
* If the object is being created, this will be null.
*
* @param model
* The model that has been modified, if any. If the object is being
* deleted, this will be null.
*
* @return
* true if the user has update permission for all modified parents,
* false otherwise.
*
* @throws GuacamoleException
* If an error occurs while determining which parents are affected.
*/
protected boolean canUpdateModifiedParents(ModeledAuthenticatedUser user,
String identifier, ModelType model) throws GuacamoleException {
// If user is privileged, no need to check
if (user.isPrivileged())
return true;
// Verify that we have permission to modify any modified parents
Collection<String> modifiedParents = getModifiedParents(user, identifier, model);
if (!modifiedParents.isEmpty()) {
ObjectPermissionSet permissionSet = getParentEffectivePermissionSet(user);
Collection<String> updateableParents = permissionSet.getAccessibleObjects(
Collections.singleton(ObjectPermission.Type.UPDATE),
modifiedParents
);
return updateableParents.size() == modifiedParents.size();
}
return true;
}
@Override
protected void beforeCreate(ModeledAuthenticatedUser user,
ExternalType object, ModelType model) throws GuacamoleException {
super.beforeCreate(user, object, model);
// Validate that we can update all applicable parents
if (!canUpdateModifiedParents(user, null, model))
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
protected void beforeUpdate(ModeledAuthenticatedUser user,
InternalType object, ModelType model) throws GuacamoleException {
super.beforeUpdate(user, object, model);
// Validate that we can update all applicable parents
if (!canUpdateModifiedParents(user, model.getIdentifier(), model))
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
protected void beforeDelete(ModeledAuthenticatedUser user,
String identifier) throws GuacamoleException {
super.beforeDelete(user, identifier);
// Validate that we can update all applicable parents
if (!canUpdateModifiedParents(user, identifier, null))
throw new GuacamoleSecurityException("Permission denied.");
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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.base;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.guacamole.net.auth.Attributes;
import org.apache.guacamole.net.auth.Identifiable;
/**
* Common base class for objects that will ultimately be made available through
* the Directory class and are persisted to an underlying database model. All
* such objects will need the same base set of queries to fulfill the needs of
* the Directory class.
*
* @param <ModelType>
* The type of model object that corresponds to this object.
*/
public abstract class ModeledDirectoryObject<ModelType extends ObjectModel>
extends ModeledObject<ModelType> implements Identifiable, Attributes {
@Override
public String getIdentifier() {
return getModel().getIdentifier();
}
@Override
public void setIdentifier(String identifier) {
getModel().setIdentifier(identifier);
}
/**
* Returns the names of all attributes explicitly supported by this object.
* Attributes named here have associated mappings within the backing model
* object, and thus should not be included in the arbitrary attribute
* storage. Any attributes set which do not match these names, such as those
* set via other extensions, will be added to arbitrary attribute storage.
*
* @return
* A read-only Set of the names of all attributes explicitly supported
* (mapped to a property of the backing model) by this object.
*/
public Set<String> getSupportedAttributeNames() {
return Collections.<String>emptySet();
}
@Override
public Map<String, String> getAttributes() {
return new HashMap<String, String>(getModel().getArbitraryAttributeMap());
}
@Override
public void setAttributes(Map<String, String> attributes) {
ArbitraryAttributeMap arbitraryAttributes = getModel().getArbitraryAttributeMap();
// Get set of all supported attribute names
Set<String> supportedAttributes = getSupportedAttributeNames();
// Store remaining attributes only if not directly mapped to model
for (Map.Entry<String, String> attribute : attributes.entrySet()) {
String name = attribute.getKey();
String value = attribute.getValue();
// Handle null attributes as explicit removal of that attribute,
// as the underlying model cannot store null attribute values
if (!supportedAttributes.contains(name)) {
if (value == null)
arbitraryAttributes.remove(name);
else
arbitraryAttributes.put(name, value);
}
}
}
}

View File

@@ -0,0 +1,190 @@
/*
* 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.base;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.UserModel;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Common interface for objects that will ultimately be made available through
* the Directory class. All such objects will need the same base set of queries
* to fulfill the needs of the Directory class.
*
* @param <ModelType>
* The type of object contained within the directory whose objects are
* mapped by this mapper.
*/
public interface ModeledDirectoryObjectMapper<ModelType> {
/**
* Selects the identifiers of all objects, regardless of whether they
* are readable by any particular user. This should only be called on
* behalf of a system administrator. If identifiers are needed by a non-
* administrative user who must have explicit read rights, use
* selectReadableIdentifiers() instead.
*
* @return
* A Set containing all identifiers of all objects.
*/
Set<String> selectIdentifiers();
/**
* Selects the identifiers of all objects that are explicitly readable by
* the given user. If identifiers are needed by a system administrator
* (who, by definition, does not need explicit read rights), use
* selectIdentifiers() instead.
*
* @param user
* The user whose permissions should determine whether an identifier
* is returned.
*
* @param effectiveGroups
* The identifiers of any known effective groups that should be taken
* into account, such as those defined externally to the database.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* A Set containing all identifiers of all readable objects.
*/
Set<String> selectReadableIdentifiers(@Param("user") UserModel user,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Selects all objects which have the given identifiers. If an identifier
* has no corresponding object, it will be ignored. This should only be
* called on behalf of a system administrator. If objects are needed by a
* non-administrative user who must have explicit read rights, use
* selectReadable() instead.
*
* @param identifiers
* The identifiers of the objects to return.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* A Collection of all objects having the given identifiers.
*/
Collection<ModelType> select(@Param("identifiers") Collection<String> identifiers,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Selects all objects which have the given identifiers and are explicitly
* readably by the given user. If an identifier has no corresponding
* object, or the corresponding object is unreadable, it will be ignored.
* If objects are needed by a system administrator (who, by definition,
* does not need explicit read rights), use select() instead.
*
* @param user
* The user whose permissions should determine whether an object
* is returned.
*
* @param identifiers
* The identifiers of the objects to return.
*
* @param effectiveGroups
* The identifiers of any known effective groups that should be taken
* into account, such as those defined externally to the database.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* A Collection of all objects having the given identifiers.
*/
Collection<ModelType> selectReadable(@Param("user") UserModel user,
@Param("identifiers") Collection<String> identifiers,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Inserts the given object into the database. If the object already
* exists, this will result in an error.
*
* @param object
* The object to insert.
*
* @return
* The number of rows inserted.
*/
int insert(@Param("object") ModelType object);
/**
* Deletes the given object into the database. If the object does not
* exist, this operation has no effect.
*
* @param identifier
* The identifier of the object to delete.
*
* @param caseSensitivity
* The case sensitivity configuration that contains information on
* whether usernames and/or group names will be treated as case-sensitive.
*
* @return
* The number of rows deleted.
*/
int delete(@Param("identifier") String identifier,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Updates the given existing object in the database. If the object does
* not actually exist, this operation has no effect.
*
* @param object
* The object to update.
*
* @return
* The number of rows updated.
*/
int update(@Param("object") ModelType object);
/**
* Deletes any arbitrary attributes currently associated with the given
* object in the database.
*
* @param object
* The object whose arbitrary attributes should be deleted.
*
* @return
* The number of rows deleted.
*/
int deleteAttributes(@Param("object") ModelType object);
/**
* Inserts all arbitrary attributes associated with the given object.
*
* @param object
* The object whose arbitrary attributes should be inserted.
*
* @return
* The number of rows inserted.
*/
int insertAttributes(@Param("object") ModelType object);
}

View File

@@ -0,0 +1,574 @@
/*
* 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.base;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionModel;
import org.apache.guacamole.auth.jdbc.user.UserModel;
import org.apache.guacamole.net.auth.Identifiable;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.properties.CaseSensitivity;
import org.mybatis.guice.transactional.Transactional;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating objects within directories. This service will automatically
* enforce the permissions of the current user.
*
* @param <InternalType>
* The specific internal implementation of the type of object this service
* provides access to.
*
* @param <ExternalType>
* The external interface or implementation of the type of object this
* service provides access to, as defined by the guacamole-ext API.
*
* @param <ModelType>
* The underlying model object used to represent InternalType in the
* database.
*/
public abstract class ModeledDirectoryObjectService<InternalType extends ModeledDirectoryObject<ModelType>,
ExternalType extends Identifiable, ModelType extends ObjectModel>
implements DirectoryObjectService<InternalType, ExternalType> {
/**
* All object permissions which are implicitly granted upon creation to the
* creator of the object.
*/
private static final ObjectPermission.Type[] IMPLICIT_OBJECT_PERMISSIONS = {
ObjectPermission.Type.READ,
ObjectPermission.Type.UPDATE,
ObjectPermission.Type.DELETE,
ObjectPermission.Type.ADMINISTER
};
/**
* The environment of the Guacamole server.
*/
@Inject
private JDBCEnvironment environment;
/**
* Returns an instance of a mapper for the type of object used by this
* service.
*
* @return
* A mapper which provides access to the model objects associated with
* the objects used by this service.
*/
protected abstract ModeledDirectoryObjectMapper<ModelType> getObjectMapper();
/**
* Returns an instance of a mapper for the type of permissions that affect
* the type of object used by this service.
*
* @return
* A mapper which provides access to the model objects associated with
* the permissions that affect the objects used by this service.
*/
protected abstract ObjectPermissionMapper getPermissionMapper();
/**
* Returns an instance of an object which is backed by the given model
* object.
*
* @param currentUser
* The user for whom this object is being created.
*
* @param model
* The model object to use to back the returned object.
*
* @return
* An object which is backed by the given model object.
*
* @throws GuacamoleException
* If the object instance cannot be created.
*/
protected abstract InternalType getObjectInstance(ModeledAuthenticatedUser currentUser,
ModelType model) throws GuacamoleException;
/**
* Returns the case sensitivity configuration for this service, which will
* be used to determine whether usernames and/or group names will be treated
* as case-sensitive.
*
* @return
* The case sensitivity configuration for this service.
*
* @throws GuacamoleException
* If an error occurs retrieving relevant configuration information.
*/
protected CaseSensitivity getCaseSensitivity() throws GuacamoleException {
// Retrieve the Guacamole setting.
return environment.getCaseSensitivity();
}
/**
* Returns an instance of a model object which is based on the given
* object.
*
* @param currentUser
* The user for whom this model object is being created.
*
* @param object
* The object to use to produce the returned model object.
*
* @return
* A model object which is based on the given object.
*
* @throws GuacamoleException
* If the model object instance cannot be created.
*/
protected abstract ModelType getModelInstance(ModeledAuthenticatedUser currentUser,
ExternalType object) throws GuacamoleException;
/**
* Returns whether the given user has permission to create the type of
* objects that this directory object service manages, taking into account
* permission inheritance through user groups.
*
* @param user
* The user being checked.
*
* @return
* true if the user has object creation permission relevant to this
* directory object service, false otherwise.
*
* @throws GuacamoleException
* If permission to read the user's permissions is denied.
*/
protected abstract boolean hasCreatePermission(ModeledAuthenticatedUser user)
throws GuacamoleException;
/**
* Returns whether the given user has permission to perform a certain
* action on a specific object managed by this directory object service,
* taking into account permission inheritance through user groups.
*
* @param user
* The user being checked.
*
* @param identifier
* The identifier of the object to check.
*
* @param type
* The type of action that will be performed.
*
* @return
* true if the user has object permission relevant described, false
* otherwise.
*
* @throws GuacamoleException
* If permission to read the user's permissions is denied.
*/
protected boolean hasObjectPermission(ModeledAuthenticatedUser user,
String identifier, ObjectPermission.Type type)
throws GuacamoleException {
// Get object permissions
ObjectPermissionSet permissionSet = getEffectivePermissionSet(user);
// Return whether permission is granted
return user.isPrivileged()
|| permissionSet.hasPermission(type, identifier);
}
/**
* Returns the permission set associated with the given user and related
* to the type of objects handled by this directory object service, taking
* into account permission inheritance via user groups.
*
* @param user
* The user whose permissions are being retrieved.
*
* @return
* A permission set which contains the permissions associated with the
* given user and related to the type of objects handled by this
* directory object service.
*
* @throws GuacamoleException
* If permission to read the user's permissions is denied.
*/
protected abstract ObjectPermissionSet getEffectivePermissionSet(ModeledAuthenticatedUser user)
throws GuacamoleException;
/**
* Returns a collection of objects which are backed by the models in the
* given collection.
*
* @param currentUser
* The user for whom these objects are being created.
*
* @param models
* The model objects to use to back the objects within the returned
* collection.
*
* @return
* A collection of objects which are backed by the models in the given
* collection.
*
* @throws GuacamoleException
* If any of the object instances cannot be created.
*/
protected Collection<InternalType> getObjectInstances(ModeledAuthenticatedUser currentUser,
Collection<ModelType> models) throws GuacamoleException {
// Create new collection of objects by manually converting each model
Collection<InternalType> objects = new ArrayList<>(models.size());
for (ModelType model : models)
objects.add(getObjectInstance(currentUser, model));
return objects;
}
/**
* Called before any object is created through this directory object
* service. This function serves as a final point of validation before
* the create operation occurs. In its default implementation,
* beforeCreate() performs basic permissions checks.
*
* @param user
* The user creating the object.
*
* @param object
* The object being created.
*
* @param model
* The model of the object being created.
*
* @throws GuacamoleException
* If the object is invalid, or an error prevents validating the given
* object.
*/
protected void beforeCreate(ModeledAuthenticatedUser user,
ExternalType object, ModelType model) throws GuacamoleException {
// Verify permission to create objects
if (!user.isPrivileged() && !hasCreatePermission(user))
throw new GuacamoleSecurityException("Permission denied.");
}
/**
* Called before any object is updated through this directory object
* service. This function serves as a final point of validation before
* the update operation occurs. In its default implementation,
* beforeUpdate() performs basic permissions checks.
*
* @param user
* The user updating the existing object.
*
* @param object
* The object being updated.
*
* @param model
* The model of the object being updated.
*
* @throws GuacamoleException
* If the object is invalid, or an error prevents validating the given
* object.
*/
protected void beforeUpdate(ModeledAuthenticatedUser user,
InternalType object, ModelType model) throws GuacamoleException {
// By default, do nothing.
if (!hasObjectPermission(user, model.getIdentifier(), ObjectPermission.Type.UPDATE))
throw new GuacamoleSecurityException("Permission denied.");
}
/**
* Called before any object is deleted through this directory object
* service. This function serves as a final point of validation before
* the delete operation occurs. In its default implementation,
* beforeDelete() performs basic permissions checks.
*
* @param user
* The user deleting the existing object.
*
* @param identifier
* The identifier of the object being deleted.
*
* @throws GuacamoleException
* If the object is invalid, or an error prevents validating the given
* object.
*/
protected void beforeDelete(ModeledAuthenticatedUser user,
String identifier) throws GuacamoleException {
// Verify permission to delete objects
if (!hasObjectPermission(user, identifier, ObjectPermission.Type.DELETE))
throw new GuacamoleSecurityException("Permission denied.");
}
/**
* Returns whether the given string is a valid identifier within the JDBC
* authentication extension. Invalid identifiers may result in SQL errors
* from the underlying database when used in queries.
*
* @param identifier
* The string to check for validity.
*
* @return
* true if the given string is a valid identifier, false otherwise.
*/
protected boolean isValidIdentifier(String identifier) {
// Empty identifiers are invalid
if (identifier.isEmpty())
return false;
// Identifier is invalid if any non-numeric characters are present
for (int i = 0; i < identifier.length(); i++) {
if (!Character.isDigit(identifier.charAt(i)))
return false;
}
// Identifier is valid - contains only numeric characters
return true;
}
/**
* Filters the given collection of strings, returning a new collection
* containing only those strings which are valid identifiers. If no strings
* within the collection are valid identifiers, the returned collection will
* simply be empty.
*
* @param identifiers
* The collection of strings to filter.
*
* @return
* A new collection containing only the strings within the provided
* collection which are valid identifiers.
*/
protected List<String> filterIdentifiers(Collection<String> identifiers) {
// Obtain enough space for a full copy of the given identifiers
List<String> validIdentifiers = new ArrayList<>(identifiers.size());
// Add only valid identifiers to the copy
for (String identifier : identifiers) {
if (isValidIdentifier(identifier))
validIdentifiers.add(identifier);
}
return validIdentifiers;
}
@Override
public InternalType retrieveObject(ModeledAuthenticatedUser user,
String identifier) throws GuacamoleException {
// Pull objects having given identifier
Collection<InternalType> objects = retrieveObjects(user, Collections.singleton(identifier));
// If no such object, return null
if (objects.isEmpty())
return null;
// The object collection will have exactly one element unless the
// database has seriously lost integrity
assert(objects.size() == 1);
// Return first and only object
return objects.iterator().next();
}
@Override
public Collection<InternalType> retrieveObjects(ModeledAuthenticatedUser user,
Collection<String> identifiers) throws GuacamoleException {
// Ignore invalid identifiers
List<String> filteredIdentifiers = filterIdentifiers(identifiers);
// Do not query if no identifiers given
if (filteredIdentifiers.isEmpty())
return Collections.<InternalType>emptyList();
int batchSize = environment.getBatchSize();
boolean userIsPrivileged = user.isPrivileged();
CaseSensitivity caseSensitivity = getCaseSensitivity();
// Process the filteredIdentifiers in batches using Lists.partition() and flatMap
Collection<ModelType> allObjects = Lists.partition(filteredIdentifiers, batchSize).stream()
.flatMap(chunk -> {
Collection<ModelType> objects;
// Bypass permission checks if the user is privileged
if (userIsPrivileged)
objects = getObjectMapper().select(chunk, caseSensitivity);
// Otherwise only return explicitly readable identifiers
else
objects = getObjectMapper().selectReadable(user.getUser().getModel(),
chunk, user.getEffectiveUserGroups(), caseSensitivity);
return objects.stream();
})
.collect(Collectors.toList());
// Return collection of requested objects
return getObjectInstances(user, allObjects);
}
/**
* Returns an immutable collection of permissions that should be granted due
* to the creation of the given object. These permissions need not be
* granted solely to the user creating the object.
*
* @param user
* The user creating the object.
*
* @param model
* The object being created.
*
* @return
* The collection of implicit permissions that should be granted due to
* the creation of the given object.
*/
protected Collection<ObjectPermissionModel> getImplicitPermissions(ModeledAuthenticatedUser user,
ModelType model) {
// Check to see if the user granting permissions is a skeleton user,
// thus lacking database backing.
if (user.getUser().isSkeleton())
return Collections.emptyList();
// Build list of implicit permissions
Collection<ObjectPermissionModel> implicitPermissions =
new ArrayList<>(IMPLICIT_OBJECT_PERMISSIONS.length);
UserModel userModel = user.getUser().getModel();
for (ObjectPermission.Type permission : IMPLICIT_OBJECT_PERMISSIONS) {
// Create model which grants this permission to the current user
ObjectPermissionModel permissionModel = new ObjectPermissionModel();
permissionModel.setEntityID(userModel.getEntityID());
permissionModel.setType(permission);
permissionModel.setObjectIdentifier(model.getIdentifier());
// Add permission
implicitPermissions.add(permissionModel);
}
return Collections.unmodifiableCollection(implicitPermissions);
}
@Override
@Transactional
public InternalType createObject(ModeledAuthenticatedUser user, ExternalType object)
throws GuacamoleException {
ModelType model = getModelInstance(user, object);
beforeCreate(user, object, model);
// Create object
getObjectMapper().insert(model);
// Set identifier on original object
object.setIdentifier(model.getIdentifier());
// Add implicit permissions
Collection<ObjectPermissionModel> implicitPermissions = getImplicitPermissions(user, model);
if (!implicitPermissions.isEmpty())
getPermissionMapper().insert(implicitPermissions, getCaseSensitivity());
// Add any arbitrary attributes
if (model.hasArbitraryAttributes())
getObjectMapper().insertAttributes(model);
return getObjectInstance(user, model);
}
@Override
public void deleteObject(ModeledAuthenticatedUser user, String identifier)
throws GuacamoleException {
beforeDelete(user, identifier);
// Delete object
getObjectMapper().delete(identifier, getCaseSensitivity());
}
@Override
@Transactional
public void updateObject(ModeledAuthenticatedUser user, InternalType object)
throws GuacamoleException {
ModelType model = object.getModel();
beforeUpdate(user, object, model);
// Update object
getObjectMapper().update(model);
// Replace any existing arbitrary attributes
getObjectMapper().deleteAttributes(model);
if (model.hasArbitraryAttributes())
getObjectMapper().insertAttributes(model);
}
@Override
public Set<String> getIdentifiers(ModeledAuthenticatedUser user)
throws GuacamoleException {
// Bypass permission checks if the user is privileged
if (user.isPrivileged())
return getObjectMapper().selectIdentifiers();
// Otherwise only return explicitly readable identifiers
else
return getObjectMapper().selectReadableIdentifiers(
user.getUser().getModel(),
user.getEffectiveUserGroups(),
getCaseSensitivity()
);
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.base;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
/**
* Common base class for objects have an underlying model. For the purposes of
* JDBC-driven authentication providers, all modeled objects are also
* restricted.
*
* @param <ModelType>
* The type of model object which corresponds to this object.
*/
public abstract class ModeledObject<ModelType> extends RestrictedObject {
/**
* The internal model object containing the values which represent this
* object in the database.
*/
private ModelType model;
/**
* Initializes this object, associating it with the current authenticated
* user and populating it with data from the given model object
*
* @param currentUser
* The user that created or retrieved this object.
*
* @param model
* The backing model object.
*/
public void init(ModeledAuthenticatedUser currentUser, ModelType model) {
super.init(currentUser);
setModel(model);
}
/**
* Returns the backing model object. Changes to the model object will
* affect this object, and changes to this object will affect the model
* object.
*
* @return
* The backing model object.
*/
public ModelType getModel() {
return model;
}
/**
* Sets the backing model object. This will effectively replace all data
* contained within this object.
*
* @param model
* The backing model object.
*/
public void setModel(ModelType model) {
this.model = model;
}
}

View File

@@ -0,0 +1,288 @@
/*
* 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.base;
import com.google.inject.Inject;
import java.util.Collections;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.permission.SystemPermissionService;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionPermissionService;
import org.apache.guacamole.auth.jdbc.permission.ConnectionGroupPermissionService;
import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionService;
import org.apache.guacamole.auth.jdbc.permission.SharingProfilePermissionService;
import org.apache.guacamole.auth.jdbc.permission.UserGroupPermissionService;
import org.apache.guacamole.auth.jdbc.permission.UserPermissionService;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.net.auth.Permissions;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.net.auth.permission.SystemPermission;
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
/**
* An implementation of the base Permissions interface which is common to both
* Users and UserGroups, backed by a database model.
*
* @param <ModelType>
* The type of model object that corresponds to this object.
*/
public abstract class ModeledPermissions<ModelType extends EntityModel>
extends ModeledDirectoryObject<ModelType> implements Permissions {
/**
* Service for retrieving entity details.
*/
@Inject
private EntityService entityService;
/**
* Service for retrieving system permissions.
*/
@Inject
private SystemPermissionService systemPermissionService;
/**
* Service for retrieving connection permissions.
*/
@Inject
private ConnectionPermissionService connectionPermissionService;
/**
* Service for retrieving connection group permissions.
*/
@Inject
private ConnectionGroupPermissionService connectionGroupPermissionService;
/**
* Service for retrieving sharing profile permissions.
*/
@Inject
private SharingProfilePermissionService sharingProfilePermissionService;
/**
* Service for retrieving active connection permissions.
*/
@Inject
private ActiveConnectionPermissionService activeConnectionPermissionService;
/**
* Service for retrieving user permissions.
*/
@Inject
private UserPermissionService userPermissionService;
/**
* Service for retrieving user group permissions.
*/
@Inject
private UserGroupPermissionService userGroupPermissionService;
/**
* Returns whether the underlying entity is a user. Entities may be either
* users or user groups.
*
* @return
* true if the underlying entity is a user, false otherwise.
*/
public boolean isUser() {
return getModel().getEntityType() == EntityType.USER;
}
/**
* Returns whether the underlying entity represents a specific user having
* the given username.
*
* @param username
* The username of a user.
*
* @return
* true if the underlying entity is a user that has the given username,
* false otherwise.
*/
public boolean isUser(String username) {
return isUser() && getIdentifier().equals(username);
}
/**
* Returns whether the underlying entity is a user group. Entities may be
* either users or user groups.
*
* @return
* true if the underlying entity is a user group, false otherwise.
*/
public boolean isUserGroup() {
return getModel().getEntityType() == EntityType.USER_GROUP;
}
/**
* Returns whether this entity is effectively unrestricted by permissions,
* such as a system administrator or an internal user operating via a
* privileged UserContext. Permission inheritance via user groups is taken
* into account.
*
* @return
* true if this entity should be unrestricted by permissions, false
* otherwise.
*
* @throws GuacamoleException
* If an error occurs while determining whether permission restrictions
* apply to the entity.
*/
public boolean isPrivileged() throws GuacamoleException {
SystemPermissionSet systemPermissionSet = getEffective().getSystemPermissions();
return systemPermissionSet.hasPermission(SystemPermission.Type.ADMINISTER);
}
@Override
public SystemPermissionSet getSystemPermissions()
throws GuacamoleException {
return systemPermissionService.getPermissionSet(getCurrentUser(), this,
Collections.<String>emptySet());
}
@Override
public ObjectPermissionSet getConnectionPermissions()
throws GuacamoleException {
return connectionPermissionService.getPermissionSet(getCurrentUser(),
this, Collections.<String>emptySet());
}
@Override
public ObjectPermissionSet getConnectionGroupPermissions()
throws GuacamoleException {
return connectionGroupPermissionService.getPermissionSet(
getCurrentUser(), this, Collections.<String>emptySet());
}
@Override
public ObjectPermissionSet getSharingProfilePermissions()
throws GuacamoleException {
return sharingProfilePermissionService.getPermissionSet(
getCurrentUser(), this, Collections.<String>emptySet());
}
@Override
public ObjectPermissionSet getActiveConnectionPermissions()
throws GuacamoleException {
return activeConnectionPermissionService.getPermissionSet(
getCurrentUser(), this, Collections.<String>emptySet());
}
@Override
public ObjectPermissionSet getUserPermissions()
throws GuacamoleException {
return userPermissionService.getPermissionSet(getCurrentUser(), this,
Collections.<String>emptySet());
}
@Override
public ObjectPermissionSet getUserGroupPermissions() throws GuacamoleException {
return userGroupPermissionService.getPermissionSet(getCurrentUser(),
this, Collections.<String>emptySet());
}
/**
* Returns the identifiers of all user groups defined within the database
* which apply to this user, including any groups inherited through
* membership in yet more groups.
*
* @return
* The identifiers of all user groups defined within the database which
* apply to this user.
*/
public Set<String> getEffectiveUserGroups() {
return entityService.retrieveEffectiveGroups(this,
Collections.<String>emptySet());
}
/**
* Returns a Permissions object which represents all permissions granted to
* this entity, including any permissions inherited through group
* membership.
*
* @return
* A Permissions object which represents all permissions granted to
* this entity.
*/
public Permissions getEffective() {
final ModeledAuthenticatedUser authenticatedUser = getCurrentUser();
final Set<String> effectiveGroups;
// If this user is the currently-authenticated user, include any
// additional effective groups declared by the authentication system
if (authenticatedUser.getIdentifier().equals(getIdentifier()))
effectiveGroups = entityService.retrieveEffectiveGroups(this,
authenticatedUser.getEffectiveUserGroups());
// Otherwise, just include effective groups from the database
else
effectiveGroups = getEffectiveUserGroups();
// Return a permissions object which describes all effective
// permissions, including any permissions inherited via user groups
return new Permissions() {
@Override
public ObjectPermissionSet getActiveConnectionPermissions()
throws GuacamoleException {
return activeConnectionPermissionService.getPermissionSet(authenticatedUser, ModeledPermissions.this, effectiveGroups);
}
@Override
public ObjectPermissionSet getConnectionGroupPermissions()
throws GuacamoleException {
return connectionGroupPermissionService.getPermissionSet(authenticatedUser, ModeledPermissions.this, effectiveGroups);
}
@Override
public ObjectPermissionSet getConnectionPermissions()
throws GuacamoleException {
return connectionPermissionService.getPermissionSet(authenticatedUser, ModeledPermissions.this, effectiveGroups);
}
@Override
public ObjectPermissionSet getSharingProfilePermissions()
throws GuacamoleException {
return sharingProfilePermissionService.getPermissionSet(authenticatedUser, ModeledPermissions.this, effectiveGroups);
}
@Override
public SystemPermissionSet getSystemPermissions()
throws GuacamoleException {
return systemPermissionService.getPermissionSet(authenticatedUser, ModeledPermissions.this, effectiveGroups);
}
@Override
public ObjectPermissionSet getUserPermissions()
throws GuacamoleException {
return userPermissionService.getPermissionSet(authenticatedUser, ModeledPermissions.this, effectiveGroups);
}
@Override
public ObjectPermissionSet getUserGroupPermissions()
throws GuacamoleException {
return userGroupPermissionService.getPermissionSet(getCurrentUser(), ModeledPermissions.this, effectiveGroups);
}
};
}
}

View File

@@ -0,0 +1,149 @@
/*
* 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.base;
import java.util.Collection;
/**
* Object representation of a Guacamole object, such as a user or connection,
* as represented in the database.
*/
public abstract class ObjectModel {
/**
* The ID of this object in the database, if any.
*/
private Integer objectID;
/**
* The unique identifier which identifies this object.
*/
private String identifier;
/**
* Map of all arbitrary attributes associated with this object but not
* directly mapped to a particular column.
*/
private ArbitraryAttributeMap arbitraryAttributes =
new ArbitraryAttributeMap();
/**
* Creates a new, empty object.
*/
public ObjectModel() {
}
/**
* Returns the identifier that uniquely identifies this object.
*
* @return
* The identifier that uniquely identifies this object.
*/
public String getIdentifier() {
return identifier;
}
/**
* Sets the identifier that uniquely identifies this object.
*
* @param identifier
* The identifier that uniquely identifies this object.
*/
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
/**
* Returns the ID of this object in the database, if it exists.
*
* @return
* The ID of this object in the database, or null if this object was
* not retrieved from the database.
*/
public Integer getObjectID() {
return objectID;
}
/**
* Sets the ID of this object to the given value.
*
* @param objectID
* The ID to assign to this object.
*/
public void setObjectID(Integer objectID) {
this.objectID = objectID;
}
/**
* Returns a map of attribute name/value pairs for all attributes associated
* with this model which do not have explicit mappings to actual model
* properties. All other attributes (those which are explicitly supported
* by the model) should instead be mapped to properties with corresponding
* and properly-typed columns.
*
* @return
* A map of attribute name/value pairs for all attributes associated
* with this model which do not otherwise have explicit mappings to
* properties.
*/
public ArbitraryAttributeMap getArbitraryAttributeMap() {
return arbitraryAttributes;
}
/**
* Returns whether at least one arbitrary attribute name/value pair has
* been associated with this object.
*
* @return
* true if this object has at least one arbitrary attribute set, false
* otherwise.
*/
public boolean hasArbitraryAttributes() {
return !arbitraryAttributes.isEmpty();
}
/**
* Returns a Collection view of the equivalent attribute model objects
* which make up the map of arbitrary attribute name/value pairs returned
* by getArbitraryAttributeMap(). Additions and removals on the returned
* Collection directly affect the attribute map.
*
* @return
* A Collection view of the map returned by
* getArbitraryAttributeMap().
*/
public Collection<ArbitraryAttributeModel> getArbitraryAttributes() {
return arbitraryAttributes.toModelCollection();
}
/**
* Replaces all arbitrary attributes associated with this object with the
* attribute name/value pairs within the given collection of model objects.
*
* @param arbitraryAttributes
* The Collection of model objects containing the attribute name/value
* pairs which should replace all currently-stored arbitrary attributes,
* if any.
*/
public void setArbitraryAttributes(Collection<ArbitraryAttributeModel> arbitraryAttributes) {
this.arbitraryAttributes = ArbitraryAttributeMap.fromModelCollection(arbitraryAttributes);
}
}

View File

@@ -0,0 +1,142 @@
/*
* 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.base;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.UserModel;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Mapper for the relations represented by a particular RelatedObjectSet
* implementation.
*
* @param <ParentModelType>
* The underlying database model of the object on the parent side of the
* one-to-many relationship represented by the RelatedObjectSet mapped by
* this ObjectRelationMapper.
*/
public interface ObjectRelationMapper<ParentModelType extends ObjectModel> {
/**
* Inserts rows as necessary to establish the one-to-many relationship
* represented by the RelatedObjectSet between the given parent and
* children. If the relation for any parent/child pair is already present,
* no attempt is made to insert a new row for that relation.
*
* @param parent
* The model of the object on the parent side of the one-to-many
* relationship represented by the RelatedObjectSet.
*
* @param children
* The identifiers of the objects on the child side of the one-to-many
* relationship represented by the RelatedObjectSet.
*
* @param caseSensitivity
* The case sensitivity configuration, used to determine whether
* usernames and/or group names will be treated as case-sensitive.
*
* @return
* The number of rows inserted.
*/
int insert(@Param("parent") ParentModelType parent,
@Param("children") Collection<String> children,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Deletes rows as necessary to modify the one-to-many relationship
* represented by the RelatedObjectSet between the given parent and
* children. If the relation for any parent/child pair does not exist,
* that specific relation is ignored, and deletion proceeds with the
* remaining relations.
*
* @param parent
* The model of the object on the parent side of the one-to-many
* relationship represented by the RelatedObjectSet.
*
* @param children
* The identifiers of the objects on the child side of the one-to-many
* relationship represented by the RelatedObjectSet.
*
* @param caseSensitivity
* The case sensitivity configuration, used to determine whether
* usernames and/or group names will be treated as case-sensitive.
*
* @return
* The number of rows deleted.
*/
int delete(@Param("parent") ParentModelType parent,
@Param("children") Collection<String> children,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Retrieves the identifiers of all objects on the child side of the
* one-to-many relationship represented by the RelatedObjectSet mapped by
* this ObjectRelationMapper. This should only be called on behalf of a
* system administrator. If identifiers are needed by a non-administrative
* user who must have explicit read rights, use
* selectReadableChildIdentifiers() instead.
*
* @param parent
* The model of the object on the parent side of the one-to-many
* relationship represented by the RelatedObjectSet.
*
* @return
* A Set containing the identifiers of all objects on the child side
* of the one-to-many relationship.
*/
Set<String> selectChildIdentifiers(@Param("parent") ParentModelType parent);
/**
* Retrieves the identifiers of all objects on the child side of the
* one-to-many relationship represented by the RelatedObjectSet mapped by
* this ObjectRelationMapper, including only those objects which are
* explicitly readable by the given user. If identifiers are needed by a
* system administrator (who, by definition, does not need explicit read
* rights), use selectChildIdentifiers() instead.
*
* @param user
* The user whose permissions should determine whether an identifier
* is returned.
*
* @param effectiveGroups
* The identifiers of any known effective groups that should be taken
* into account, such as those defined externally to the database.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @param parent
* The model of the object on the parent side of the one-to-many
* relationship represented by the RelatedObjectSet.
*
* @return
* A Set containing the identifiers of all readable objects on the
* child side of the one-to-many relationship.
*/
Set<String> selectReadableChildIdentifiers(@Param("user") UserModel user,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity,
@Param("parent") ParentModelType parent);
}

View File

@@ -0,0 +1,234 @@
/*
* 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.base;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.properties.CaseSensitivity;
/**
* A database implementation of RelatedObjectSet which provides access to a
* parent object and corresponding set of objects related to the parent, subject
* to object-level permissions. Though the parent and child objects have
* specific types, only the parent object's type is enforced through type
* parameters, as child objects are represented by identifiers only.
*
* @param <ParentObjectType>
* The type of object that represents the parent side of the relation.
*
* @param <ParentModelType>
* The underlying database model of the parent object.
*/
public abstract class RelatedObjectSet<ParentObjectType extends ModeledDirectoryObject<ParentModelType>, ParentModelType extends ObjectModel>
extends RestrictedObject implements org.apache.guacamole.net.auth.RelatedObjectSet {
/**
* The parent object which shares some arbitrary relation with the objects
* within this set.
*/
private ParentObjectType parent;
/**
* Creates a new RelatedObjectSet. The resulting object set must still be
* initialized by a call to init().
*/
public RelatedObjectSet() {
}
/**
* Initializes this RelatedObjectSet with the current user and the single
* object on the parent side of the one-to-many relation represented by the
* set.
*
* @param currentUser
* The user who queried this RelatedObjectSet, and whose permissions
* dictate the access level of all operations performed on this set.
*
* @param parent
* The parent object which shares some arbitrary relation with the
* objects within this set.
*/
public void init(ModeledAuthenticatedUser currentUser, ParentObjectType parent) {
super.init(currentUser);
this.parent = parent;
}
/**
* Return the current case sensitivity setting, which can be used to
* determine whether or not certain identifiers should be treated as
* case-sensitive.
*
* @return
* The current case sensitivity setting.
*
* @throws GuacamoleException
* If an error occurs retrieving configuration information on
* case sensitivity.
*/
protected CaseSensitivity getCaseSensitivity() throws GuacamoleException {
// Identifiers are not case-sensitive by default.
return CaseSensitivity.DISABLED;
}
/**
* Returns the mapper which provides low-level access to the database
* models which drive the relation represented by this RelatedObjectSet.
*
* @return
* The mapper which provides low-level access to the database
* models which drive the relation represented by this
* RelatedObjectSet.
*/
protected abstract ObjectRelationMapper<ParentModelType> getObjectRelationMapper();
/**
* Returns the permission set which exposes the effective permissions
* available to the current user regarding the objects on the parent side
* of the one-to-many relationship represented by this RelatedObjectSet.
* Permission inheritance through user groups is taken into account.
*
* @return
* The permission set which exposes the effective permissions
* available to the current user regarding the objects on the parent
* side of the one-to-many relationship represented by this
* RelatedObjectSet.
*
* @throws GuacamoleException
* If permission to query permission status is denied.
*/
protected abstract ObjectPermissionSet getParentObjectEffectivePermissionSet()
throws GuacamoleException;
/**
* Returns the permission set which exposes the effective permissions
* available to the current user regarding the objects on the child side
* of the one-to-many relationship represented by this RelatedObjectSet.
* Permission inheritance through user groups is taken into account.
*
* @return
* The permission set which exposes the effective permissions
* available to the current user regarding the objects on the child
* side of the one-to-many relationship represented by this
* RelatedObjectSet.
*
* @throws GuacamoleException
* If permission to query permission status is denied.
*/
protected abstract ObjectPermissionSet getChildObjectEffectivePermissionSet()
throws GuacamoleException;
/**
* Returns whether the current user has permission to alter the status of
* the relation between the parent object and the given child objects.
*
* @param identifiers
* The identifiers of all objects on the child side of the one-to-many
* relation being changed.
*
* @return
* true if the user has permission to make the described changes,
* false otherwise.
*
* @throws GuacamoleException
* If permission to query permission status is denied.
*/
private boolean canAlterRelation(Collection<String> identifiers)
throws GuacamoleException {
// Privileged users (such as system administrators) may alter any
// relations
if (getCurrentUser().isPrivileged())
return true;
// Non-admin users require UPDATE permission on the parent object ...
if (!getParentObjectEffectivePermissionSet().hasPermission(
ObjectPermission.Type.UPDATE, parent.getIdentifier()))
return false;
// ... as well as UPDATE permission on all child objects being changed
Collection<String> accessibleIdentifiers =
getChildObjectEffectivePermissionSet().getAccessibleObjects(
Collections.singleton(ObjectPermission.Type.UPDATE),
identifiers);
return accessibleIdentifiers.size() == identifiers.size();
}
@Override
public Set<String> getObjects() throws GuacamoleException {
// Bypass permission checks if the user is a privileged
ModeledAuthenticatedUser user = getCurrentUser();
if (user.isPrivileged())
return getObjectRelationMapper().selectChildIdentifiers(parent.getModel());
// Otherwise only return explicitly readable identifiers
return getObjectRelationMapper().selectReadableChildIdentifiers(
user.getUser().getModel(), user.getEffectiveUserGroups(),
getCaseSensitivity(),
parent.getModel());
}
@Override
public void addObjects(Set<String> identifiers) throws GuacamoleException {
// Nothing to do if nothing provided
if (identifiers.isEmpty())
return;
// Create relations only if permission is granted
if (canAlterRelation(identifiers))
getObjectRelationMapper().insert(parent.getModel(), identifiers,
getCaseSensitivity());
// User lacks permission to add user groups
else
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public void removeObjects(Set<String> identifiers) throws GuacamoleException {
// Nothing to do if nothing provided
if (identifiers.isEmpty())
return;
// Delete relations only if permission is granted
if (canAlterRelation(identifiers))
getObjectRelationMapper().delete(parent.getModel(), identifiers,
getCaseSensitivity());
// User lacks permission to remove user groups
else
throw new GuacamoleSecurityException("Permission denied.");
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.base;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
/**
* Common base class for objects that are associated with the users that
* obtain them.
*/
public abstract class RestrictedObject {
/**
* The user this object belongs to. Access is based on his/her permission
* settings.
*/
private ModeledAuthenticatedUser currentUser;
/**
* Initializes this object, associating it with the current authenticated
* user and populating it with data from the given model object
*
* @param currentUser
* The user that created or retrieved this object.
*/
public void init(ModeledAuthenticatedUser currentUser) {
setCurrentUser(currentUser);
}
/**
* Returns the user that created or queried this object. This user's
* permissions dictate what operations can be performed on or through this
* object.
*
* @return
* The user that created or queried this object.
*/
public ModeledAuthenticatedUser getCurrentUser() {
return currentUser;
}
/**
* Sets the user that created or queried this object. This user's
* permissions dictate what operations can be performed on or through this
* object.
*
* @param currentUser
* The user that created or queried this object.
*/
public void setCurrentUser(ModeledAuthenticatedUser currentUser) {
this.currentUser = currentUser;
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
/**
* Base classes supporting JDBC-driven authentication providers and defining
* the relationships between the model and the implementations of guacamole-ext
* classes.
*/
package org.apache.guacamole.auth.jdbc.base;

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.connection;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.JDBCDirectory;
import org.apache.guacamole.net.auth.Connection;
import org.mybatis.guice.transactional.Transactional;
/**
* Implementation of the Connection Directory which is driven by an underlying,
* arbitrary database.
*/
public class ConnectionDirectory extends JDBCDirectory<Connection> {
/**
* Service for managing connection objects.
*/
@Inject
private ConnectionService connectionService;
@Override
public Connection get(String identifier) throws GuacamoleException {
return connectionService.retrieveObject(getCurrentUser(), identifier);
}
@Override
@Transactional
public Collection<Connection> getAll(Collection<String> identifiers) throws GuacamoleException {
Collection<ModeledConnection> objects = connectionService.retrieveObjects(getCurrentUser(), identifiers);
return Collections.<Connection>unmodifiableCollection(objects);
}
@Override
@Transactional
public Set<String> getIdentifiers() throws GuacamoleException {
return connectionService.getIdentifiers(getCurrentUser());
}
@Override
@Transactional
public void add(Connection object) throws GuacamoleException {
connectionService.createObject(getCurrentUser(), object);
}
@Override
@Transactional
public void update(Connection object) throws GuacamoleException {
ModeledConnection connection = (ModeledConnection) object;
connectionService.updateObject(getCurrentUser(), connection);
}
@Override
@Transactional
public void remove(String identifier) throws GuacamoleException {
connectionService.deleteObject(getCurrentUser(), identifier);
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.connection;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper;
import org.apache.guacamole.auth.jdbc.user.UserModel;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Mapper for connection objects.
*/
public interface ConnectionMapper extends ModeledDirectoryObjectMapper<ConnectionModel> {
/**
* Selects the identifiers of all connections within the given parent
* connection group, regardless of whether they are readable by any
* particular user. This should only be called on behalf of a system
* administrator. If identifiers are needed by a non-administrative user
* who must have explicit read rights, use
* selectReadableIdentifiersWithin() instead.
*
* @param parentIdentifier
* The identifier of the parent connection group, or null if the root
* connection group is to be queried.
*
* @return
* A Set containing all identifiers of all objects.
*/
Set<String> selectIdentifiersWithin(@Param("parentIdentifier") String parentIdentifier);
/**
* Selects the identifiers of all connections within the given parent
* connection group that are explicitly readable by the given user. If
* identifiers are needed by a system administrator (who, by definition,
* does not need explicit read rights), use selectIdentifiersWithin()
* instead.
*
* @param user
* The user whose permissions should determine whether an identifier
* is returned.
*
* @param parentIdentifier
* The identifier of the parent connection group, or null if the root
* connection group is to be queried.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the user. If
* no groups are given, only permissions directly granted to the user
* will be used.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* A Set containing all identifiers of all readable objects.
*/
Set<String> selectReadableIdentifiersWithin(@Param("user") UserModel user,
@Param("parentIdentifier") String parentIdentifier,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Selects the connection within the given parent group and having the
* given name. If no such connection exists, null is returned.
*
* @param parentIdentifier
* The identifier of the parent group to search within.
*
* @param name
* The name of the connection to find.
*
* @return
* The connection having the given name within the given parent group,
* or null if no such connection exists.
*/
ConnectionModel selectOneByName(@Param("parentIdentifier") String parentIdentifier,
@Param("name") String name);
}

View File

@@ -0,0 +1,395 @@
/*
* 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.connection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.base.ChildObjectModel;
import org.apache.guacamole.net.auth.GuacamoleProxyConfiguration.EncryptionMethod;
/**
* Object representation of a Guacamole connection, as represented in the
* database.
*/
public class ConnectionModel extends ChildObjectModel {
/**
* The human-readable name associated with this connection.
*/
private String name;
/**
* The name of the protocol to use when connecting to this connection.
*/
private String protocol;
/**
* The maximum number of connections that can be established to this
* connection concurrently, zero if no restriction applies, or null if the
* default restrictions should be applied.
*/
private Integer maxConnections;
/**
* The maximum number of connections that can be established to this
* connection concurrently by any one user, zero if no restriction applies,
* or null if the default restrictions should be applied.
*/
private Integer maxConnectionsPerUser;
/**
* The weight of the connection for the purposes of calculating
* WLC algorithm. null indicates nothing has been set, and anything less
* than 1 eliminates the system from being used for connections.
*/
private Integer connectionWeight;
/**
* Whether this connection should be reserved for failover. Failover-only
* connections within a balancing group are only used when all non-failover
* connections are unavailable.
*/
private boolean failoverOnly;
/**
* The identifiers of all readable sharing profiles associated with this
* connection.
*/
private Set<String> sharingProfileIdentifiers = new HashSet<String>();
/**
* The hostname of the guacd instance to use, or null if the hostname of the
* default guacd instance should be used.
*/
private String proxyHostname;
/**
* The port of the guacd instance to use, or null if the port of the default
* guacd instance should be used.
*/
private Integer proxyPort;
/**
* The encryption method required by the desired guacd instance, or null if
* the encryption method of the default guacd instance should be used.
*/
private EncryptionMethod proxyEncryptionMethod;
/**
* The date and time that this connection was last used, or null if this
* connection has never been used.
*/
private Date lastActive;
/**
* Creates a new, empty connection.
*/
public ConnectionModel() {
}
/**
* Returns the name associated with this connection.
*
* @return
* The name associated with this connection.
*/
public String getName() {
return name;
}
/**
* Sets the name associated with this connection.
*
* @param name
* The name to associate with this connection.
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns the name of the protocol to use when connecting to this
* connection.
*
* @return
* The name of the protocol to use when connecting to this connection.
*/
public String getProtocol() {
return protocol;
}
/**
* Sets the name of the protocol to use when connecting to this connection.
*
* @param protocol
* The name of the protocol to use when connecting to this connection.
*/
public void setProtocol(String protocol) {
this.protocol = protocol;
}
/**
* Returns the maximum number of connections that can be established to
* this connection concurrently.
*
* @return
* The maximum number of connections that can be established to this
* connection concurrently, zero if no restriction applies, or null if
* the default restrictions should be applied.
*/
public Integer getMaxConnections() {
return maxConnections;
}
/**
* Sets the maximum number of connections that can be established to this
* connection concurrently.
*
* @param maxConnections
* The maximum number of connections that can be established to this
* connection concurrently, zero if no restriction applies, or null if
* the default restrictions should be applied.
*/
public void setMaxConnections(Integer maxConnections) {
this.maxConnections = maxConnections;
}
/**
* Returns the maximum number of connections that can be established to
* this connection concurrently by any one user.
*
* @return
* The maximum number of connections that can be established to this
* connection concurrently by any one user, zero if no restriction
* applies, or null if the default restrictions should be applied.
*/
public Integer getMaxConnectionsPerUser() {
return maxConnectionsPerUser;
}
/**
* Sets the connection weight for load balancing.
*
* @param connectionWeight
* The weight of the connection used in load balancing.
* The value is not required for the connection (null), and
* values less than 1 will prevent the connection from being
* used.
*/
public void setConnectionWeight(Integer connectionWeight) {
this.connectionWeight = connectionWeight;
}
/**
* Returns the connection weight used in applying weighted
* load balancing algorithms.
*
* @return
* The connection weight used in applying weighted
* load balancing aglorithms.
*/
public Integer getConnectionWeight() {
return connectionWeight;
}
/**
* Returns whether this connection should be reserved for failover.
* Failover-only connections within a balancing group are only used when
* all non-failover connections are unavailable.
*
* @return
* true if this connection should be reserved for failover, false
* otherwise.
*/
public boolean isFailoverOnly() {
return failoverOnly;
}
/**
* Sets whether this connection should be reserved for failover.
* Failover-only connections within a balancing group are only used when
* all non-failover connections are unavailable.
*
* @param failoverOnly
* true if this connection should be reserved for failover, false
* otherwise.
*/
public void setFailoverOnly(boolean failoverOnly) {
this.failoverOnly = failoverOnly;
}
/**
* Sets the maximum number of connections that can be established to this
* connection concurrently by any one user.
*
* @param maxConnectionsPerUser
* The maximum number of connections that can be established to this
* connection concurrently by any one user, zero if no restriction
* applies, or null if the default restrictions should be applied.
*/
public void setMaxConnectionsPerUser(Integer maxConnectionsPerUser) {
this.maxConnectionsPerUser = maxConnectionsPerUser;
}
/**
* Returns the hostname of the guacd instance to use. If the hostname of the
* default guacd instance should be used instead, null is returned.
*
* @return
* The hostname of the guacd instance to use, or null if the hostname
* of the default guacd instance should be used.
*/
public String getProxyHostname() {
return proxyHostname;
}
/**
* Sets the hostname of the guacd instance to use.
*
* @param proxyHostname
* The hostname of the guacd instance to use, or null if the hostname
* of the default guacd instance should be used.
*/
public void setProxyHostname(String proxyHostname) {
this.proxyHostname = proxyHostname;
}
/**
* Returns the port of the guacd instance to use. If the port of the default
* guacd instance should be used instead, null is returned.
*
* @return
* The port of the guacd instance to use, or null if the port of the
* default guacd instance should be used.
*/
public Integer getProxyPort() {
return proxyPort;
}
/**
* Sets the port of the guacd instance to use.
*
* @param proxyPort
* The port of the guacd instance to use, or null if the port of the
* default guacd instance should be used.
*/
public void setProxyPort(Integer proxyPort) {
this.proxyPort = proxyPort;
}
/**
* Returns the type of encryption required by the desired guacd instance.
* If the encryption method of the default guacd instance should be used
* instead, null is returned.
*
* @return
* The type of encryption required by the desired guacd instance, or
* null if the encryption method of the default guacd instance should
* be used.
*/
public EncryptionMethod getProxyEncryptionMethod() {
return proxyEncryptionMethod;
}
/**
* Sets the type of encryption which should be used when connecting to
* guacd, if any.
*
* @param proxyEncryptionMethod
* The type of encryption required by the desired guacd instance, or
* null if the encryption method of the default guacd instance should
* be used.
*/
public void setProxyEncryptionMethod(EncryptionMethod proxyEncryptionMethod) {
this.proxyEncryptionMethod = proxyEncryptionMethod;
}
/**
* Returns the identifiers of all readable sharing profiles associated with
* this connection. This is set only when the connection is queried, and has
* no effect when a connection is inserted, updated, or deleted.
*
* @return
* The identifiers of all readable sharing profiles associated with
* this connection.
*/
public Set<String> getSharingProfileIdentifiers() {
return sharingProfileIdentifiers;
}
/**
* Sets the identifiers of all readable sharing profiles associated with
* this connection. This should be set only when the connection is queried,
* as it has no effect when a connection is inserted, updated, or deleted.
*
* @param sharingProfileIdentifiers
* The identifiers of all readable sharing profiles associated with
* this connection.
*/
public void setSharingProfileIdentifiers(Set<String> sharingProfileIdentifiers) {
this.sharingProfileIdentifiers = sharingProfileIdentifiers;
}
/**
* Returns the date and time that this connection was last used, or null if
* this connection has never been used.
*
* @return
* The date and time that this connection was last used, or null if this
* connection has never been used.
*/
public Date getLastActive() {
return lastActive;
}
/**
* Sets the date and time that this connection was last used. This value is
* expected to be set automatically via queries, derived from connection
* history records. It does NOT correspond to an actual column, and values
* set manually through invoking this function will not persist.
*
* @param lastActive
* The date and time that this connection was last used, or null if this
* connection has never been used.
*/
public void setLastActive(Date lastActive) {
this.lastActive = lastActive;
}
@Override
public String getIdentifier() {
// If no associated ID, then no associated identifier
Integer id = getObjectID();
if (id == null)
return null;
// Otherwise, the identifier is the ID as a string
return id.toString();
}
@Override
public void setIdentifier(String identifier) {
throw new UnsupportedOperationException("Connection identifiers are derived from IDs. They cannot be set.");
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.connection;
import java.util.Collection;
import org.apache.ibatis.annotations.Param;
/**
* Mapper for connection parameter objects.
*/
public interface ConnectionParameterMapper {
/**
* Returns a collection of all parameters associated with the connection
* having the given identifier.
*
* @param identifier
* The identifier of the connection whose parameters are to be
* retrieved.
*
* @return
* A collection of all parameters associated with the connection
* having the given identifier. This collection will be empty if no
* such connection exists.
*/
Collection<ConnectionParameterModel> select(@Param("identifier") String identifier);
/**
* Inserts each of the parameter model objects in the given collection as
* new connection parameters.
*
* @param parameters
* The connection parameters to insert.
*
* @return
* The number of rows inserted.
*/
int insert(@Param("parameters") Collection<ConnectionParameterModel> parameters);
/**
* Deletes all parameters associated with the connection having the given
* identifier.
*
* @param identifier
* The identifier of the connection whose parameters should be
* deleted.
*
* @return
* The number of rows deleted.
*/
int delete(@Param("identifier") String identifier);
}

View File

@@ -0,0 +1,102 @@
/*
* 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.connection;
/**
* A single parameter name/value pair belonging to a connection.
*/
public class ConnectionParameterModel {
/**
* The identifier of the connection associated with this parameter.
*/
private String connectionIdentifier;
/**
* The name of the parameter.
*/
private String name;
/**
* The value the parameter is set to.
*/
private String value;
/**
* Returns the identifier of the connection associated with this parameter.
*
* @return
* The identifier of the connection associated with this parameter.
*/
public String getConnectionIdentifier() {
return connectionIdentifier;
}
/**
* Sets the identifier of the connection associated with this parameter.
*
* @param connectionIdentifier
* The identifier of the connection to associate with this parameter.
*/
public void setConnectionIdentifier(String connectionIdentifier) {
this.connectionIdentifier = connectionIdentifier;
}
/**
* Returns the name of this parameter.
*
* @return
* The name of this parameter.
*/
public String getName() {
return name;
}
/**
* Sets the name of this parameter.
*
* @param name
* The name of this parameter.
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns the value of this parameter.
*
* @return
* The value of this parameter.
*/
public String getValue() {
return value;
}
/**
* Sets the value of this parameter.
*
* @param value
* The value of this parameter.
*/
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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.connection;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordMapper;
/**
* Mapper for connection record objects.
*/
public interface ConnectionRecordMapper extends ActivityRecordMapper<ConnectionRecordModel> {}

View File

@@ -0,0 +1,156 @@
/*
* 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.connection;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordModel;
/**
* A single connection record representing a past usage of a particular
* connection. If the connection was being shared, the sharing profile used to
* join the connection is included in the record.
*/
public class ConnectionRecordModel extends ActivityRecordModel {
/**
* The identifier of the connection associated with this connection record.
*/
private String connectionIdentifier;
/**
* The name of the connection associated with this connection record.
*/
private String connectionName;
/**
* The identifier of the sharing profile associated with this connection
* record. If no sharing profile was used, or the sharing profile that was
* used was deleted, this will be null.
*/
private String sharingProfileIdentifier;
/**
* The name of the sharing profile associated with this connection record.
* If no sharing profile was used, this will be null. If the sharing profile
* that was used was deleted, this will still contain the name of the
* sharing profile at the time that the connection was used.
*/
private String sharingProfileName;
/**
* Returns the identifier of the connection associated with this connection
* record.
*
* @return
* The identifier of the connection associated with this connection
* record.
*/
public String getConnectionIdentifier() {
return connectionIdentifier;
}
/**
* Sets the identifier of the connection associated with this connection
* record.
*
* @param connectionIdentifier
* The identifier of the connection to associate with this connection
* record.
*/
public void setConnectionIdentifier(String connectionIdentifier) {
this.connectionIdentifier = connectionIdentifier;
}
/**
* Returns the name of the connection associated with this connection
* record.
*
* @return
* The name of the connection associated with this connection
* record.
*/
public String getConnectionName() {
return connectionName;
}
/**
* Sets the name of the connection associated with this connection
* record.
*
* @param connectionName
* The name of the connection to associate with this connection
* record.
*/
public void setConnectionName(String connectionName) {
this.connectionName = connectionName;
}
/**
* Returns the identifier of the sharing profile associated with this
* connection record. If no sharing profile was used, or the sharing profile
* that was used was deleted, this will be null.
*
* @return
* The identifier of the sharing profile associated with this connection
* record, or null if no sharing profile was used or if the sharing
* profile that was used was deleted.
*/
public String getSharingProfileIdentifier() {
return sharingProfileIdentifier;
}
/**
* Sets the identifier of the sharing profile associated with this
* connection record. If no sharing profile was used, this should be null.
*
* @param sharingProfileIdentifier
* The identifier of the sharing profile associated with this
* connection record, or null if no sharing profile was used.
*/
public void setSharingProfileIdentifier(String sharingProfileIdentifier) {
this.sharingProfileIdentifier = sharingProfileIdentifier;
}
/**
* Returns the human-readable name of the sharing profile associated with this
* connection record. If no sharing profile was used, this will be null.
*
* @return
* The human-readable name of the sharing profile associated with this
* connection record, or null if no sharing profile was used.
*/
public String getSharingProfileName() {
return sharingProfileName;
}
/**
* Sets the human-readable name of the sharing profile associated with this
* connection record. If no sharing profile was used, this should be null.
*
* @param sharingProfileName
* The human-readable name of the sharing profile associated with this
* connection record, or null if no sharing profile was used.
*/
public void setSharingProfileName(String sharingProfileName) {
this.sharingProfileName = sharingProfileName;
}
}

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.connection;
import com.google.inject.Inject;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate;
import org.apache.guacamole.auth.jdbc.base.ModeledActivityRecordSet;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.ConnectionRecord;
/**
* A JDBC implementation of ActivityRecordSet for ConnectionRecords. Calls to
* asCollection() will query connection history records from the database. Which
* records are returned will be determined by the values passed in earlier.
*/
public class ConnectionRecordSet extends ModeledActivityRecordSet<ConnectionRecord> {
/**
* The namespace for the type 3 UUIDs generated for connection history
* records. This UUID namespace is itself a type 3 UUID within the "ns:OID"
* namespace for the OID "1.3.6.1.4.1.18060.18.2.1.2", which has been
* specifically allocated for Apache Guacamole database connection
* history records.
*/
public static final UUID UUID_NAMESPACE = UUID.fromString("8b55f070-95f4-3d31-93ee-9c5845e7aa40");
/**
* Service for managing connection objects.
*/
@Inject
private ConnectionService connectionService;
/**
* The identifier of the connection to which this record set should be
* limited, if any. If null, the set should contain all records readable
* by the user making the request.
*/
private String identifier = null;
/**
* Initializes this object, associating it with the current authenticated
* user and connection identifier.
*
* @param currentUser
* The user that created or retrieved this object.
*
* @param identifier
* The connection identifier to which this record set should be limited,
* or null if the record set should contain all records readable by the
* currentUser.
*/
protected void init(ModeledAuthenticatedUser currentUser, String identifier) {
super.init(currentUser);
this.identifier = identifier;
}
@Override
protected List<ConnectionRecord> retrieveHistory(
AuthenticatedUser user, String recordIdentifier,
Set<ActivityRecordSearchTerm> requiredContents,
List<ActivityRecordSortPredicate> sortPredicates,
int limit) throws GuacamoleException {
// Retrieve history from database
return connectionService.retrieveHistory(identifier, getCurrentUser(),
recordIdentifier, requiredContents, sortPredicates, limit);
}
}

View File

@@ -0,0 +1,550 @@
/*
* 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.connection;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.auth.jdbc.base.ModeledChildDirectoryObjectService;
import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper;
import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionMapper;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.language.TranslatableGuacamoleClientOverrunException;
import org.apache.guacamole.language.TranslatableMessage;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.ConnectionRecord;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.net.auth.permission.SystemPermission;
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating connections.
*/
public class ConnectionService extends ModeledChildDirectoryObjectService<ModeledConnection, Connection, ConnectionModel> {
/**
* Mapper for accessing connections.
*/
@Inject
private ConnectionMapper connectionMapper;
/**
* Mapper for manipulating connection permissions.
*/
@Inject
private ConnectionPermissionMapper connectionPermissionMapper;
/**
* Mapper for accessing connection parameters.
*/
@Inject
private ConnectionParameterMapper parameterMapper;
/**
* Mapper for accessing connection history.
*/
@Inject
private ConnectionRecordMapper connectionRecordMapper;
/**
* Provider for creating connections.
*/
@Inject
private Provider<ModeledConnection> connectionProvider;
/**
* The server environment for retrieving configuration.
*/
@Inject
private JDBCEnvironment environment;
/**
* Service for creating and tracking tunnels.
*/
@Inject
private GuacamoleTunnelService tunnelService;
/**
* Known limit for the size of the connection parameter values.
*/
private static final int CONNECTION_PARAMETER_VALUE_LIMIT = 4096;
@Override
protected ModeledDirectoryObjectMapper<ConnectionModel> getObjectMapper() {
return connectionMapper;
}
@Override
protected ObjectPermissionMapper getPermissionMapper() {
return connectionPermissionMapper;
}
@Override
protected ModeledConnection getObjectInstance(ModeledAuthenticatedUser currentUser,
ConnectionModel model) {
ModeledConnection connection = connectionProvider.get();
connection.init(currentUser, model);
return connection;
}
@Override
protected ConnectionModel getModelInstance(ModeledAuthenticatedUser currentUser,
final Connection object) {
// Create new ModeledConnection backed by blank model
ConnectionModel model = new ConnectionModel();
ModeledConnection connection = getObjectInstance(currentUser, model);
// Set model contents through ModeledConnection, copying the provided connection
connection.setParentIdentifier(object.getParentIdentifier());
connection.setName(object.getName());
connection.setConfiguration(object.getConfiguration());
connection.setAttributes(object.getAttributes());
return model;
}
@Override
protected boolean hasCreatePermission(ModeledAuthenticatedUser user)
throws GuacamoleException {
// Return whether user has explicit connection creation permission
SystemPermissionSet permissionSet = user.getUser().getEffectivePermissions().getSystemPermissions();
return permissionSet.hasPermission(SystemPermission.Type.CREATE_CONNECTION);
}
@Override
protected ObjectPermissionSet getEffectivePermissionSet(ModeledAuthenticatedUser user)
throws GuacamoleException {
// Return permissions related to connections
return user.getUser().getEffectivePermissions().getConnectionPermissions();
}
@Override
protected ObjectPermissionSet getParentEffectivePermissionSet(ModeledAuthenticatedUser user)
throws GuacamoleException {
// Connections are contained by connection groups
return user.getUser().getEffectivePermissions().getConnectionGroupPermissions();
}
/**
* Validates that all connection parameter values are within the expected size limit.
*
* @param parameters
* The map of connection parameter name/value pairs to validate.
*
* @throws GuacamoleClientException
* If any of the parameter values exceed the defined limit.
*/
private void validateParameters(Map<String, String> parameters) throws GuacamoleClientException {
// Iterate through each parameter to validate its size
for (Map.Entry<String, String> parameter : parameters.entrySet()) {
String value = parameter.getValue();
// Check if parameter value exceeds size limit
if (value != null && value.length() > CONNECTION_PARAMETER_VALUE_LIMIT) {
Map<String, Object> vars = new HashMap<>();
vars.put("MAX_SIZE", CONNECTION_PARAMETER_VALUE_LIMIT);
vars.put("PARAMETER_NAME", parameter.getKey());
// Create a translatable message with the error key and substitution variables
TranslatableMessage translatableMessage = new TranslatableMessage(
"CONNECTION_PARAMETERS.DATABASE_PARAMETER_VALUE_TOO_LONG",
vars
);
throw new TranslatableGuacamoleClientOverrunException(
"The value provided for connection parameter \"" + parameter.getKey() +
"\" exceeds the maximum allowed length.",
translatableMessage
);
}
}
}
@Override
protected void beforeCreate(ModeledAuthenticatedUser user,
Connection object, ConnectionModel model)
throws GuacamoleException {
// Validate parameters before saving
validateParameters(object.getConfiguration().getParameters());
super.beforeCreate(user, object, model);
// Name must not be blank
if (model.getName() == null || model.getName().trim().isEmpty())
throw new GuacamoleClientException("Connection names must not be blank.");
// Do not attempt to create duplicate connections
ConnectionModel existing = connectionMapper.selectOneByName(model.getParentIdentifier(), model.getName());
if (existing != null)
throw new GuacamoleClientException("The connection \"" + model.getName() + "\" already exists.");
}
@Override
protected void beforeUpdate(ModeledAuthenticatedUser user,
ModeledConnection object, ConnectionModel model)
throws GuacamoleException {
// Validate parameters before saving
validateParameters(object.getConfiguration().getParameters());
super.beforeUpdate(user, object, model);
// Name must not be blank
if (model.getName() == null || model.getName().trim().isEmpty())
throw new GuacamoleClientException("Connection names must not be blank.");
// Check whether such a connection is already present
ConnectionModel existing = connectionMapper.selectOneByName(model.getParentIdentifier(), model.getName());
if (existing != null) {
// If the specified name matches a DIFFERENT existing connection, the update cannot continue
if (!existing.getObjectID().equals(model.getObjectID()))
throw new GuacamoleClientException("The connection \"" + model.getName() + "\" already exists.");
}
}
/**
* Given an arbitrary Guacamole connection, produces a collection of
* parameter model objects containing the name/value pairs of that
* connection's parameters.
*
* @param connection
* The connection whose configuration should be used to produce the
* collection of parameter models.
*
* @return
* A collection of parameter models containing the name/value pairs
* of the given connection's parameters.
*/
private Collection<ConnectionParameterModel> getParameterModels(ModeledConnection connection) {
Map<String, String> parameters = connection.getConfiguration().getParameters();
// Convert parameters to model objects
Collection<ConnectionParameterModel> parameterModels = new ArrayList<>(parameters.size());
for (Map.Entry<String, String> parameterEntry : parameters.entrySet()) {
// Get parameter name and value
String name = parameterEntry.getKey();
String value = parameterEntry.getValue();
// There is no need to insert empty parameters
if (value == null || value.isEmpty())
continue;
// Produce model object from parameter
ConnectionParameterModel model = new ConnectionParameterModel();
model.setConnectionIdentifier(connection.getIdentifier());
model.setName(name);
model.setValue(value);
// Add model to list
parameterModels.add(model);
}
return parameterModels;
}
@Override
public ModeledConnection createObject(ModeledAuthenticatedUser user, Connection object)
throws GuacamoleException {
// Create connection
ModeledConnection connection = super.createObject(user, object);
connection.setConfiguration(object.getConfiguration());
// Insert new parameters, if any
Collection<ConnectionParameterModel> parameterModels = getParameterModels(connection);
if (!parameterModels.isEmpty())
parameterMapper.insert(parameterModels);
return connection;
}
@Override
public void updateObject(ModeledAuthenticatedUser user, ModeledConnection object)
throws GuacamoleException {
// Update connection
super.updateObject(user, object);
// Replace existing parameters with new parameters, if any
Collection<ConnectionParameterModel> parameterModels = getParameterModels(object);
parameterMapper.delete(object.getIdentifier());
if (!parameterModels.isEmpty())
parameterMapper.insert(parameterModels);
}
/**
* Returns the set of all identifiers for all connections within the
* connection group having the given identifier. Only connections that the
* user has read access to will be returned.
*
* Permission to read the connection group having the given identifier is
* NOT checked.
*
* @param user
* The user retrieving the identifiers.
*
* @param identifier
* The identifier of the parent connection group, or null to check the
* root connection group.
*
* @return
* The set of all identifiers for all connections in the connection
* group having the given identifier that the user has read access to.
*
* @throws GuacamoleException
* If an error occurs while reading identifiers.
*/
public Set<String> getIdentifiersWithin(ModeledAuthenticatedUser user,
String identifier)
throws GuacamoleException {
// Bypass permission checks if the user is privileged
if (user.isPrivileged())
return connectionMapper.selectIdentifiersWithin(identifier);
// Otherwise only return explicitly readable identifiers
else
return connectionMapper.selectReadableIdentifiersWithin(
user.getUser().getModel(), identifier,
user.getEffectiveUserGroups(),
getCaseSensitivity());
}
/**
* Retrieves all parameters visible to the given user and associated with
* the connection having the given identifier. If the given user has no
* access to such parameters, or no such connection exists, the returned
* map will be empty.
*
* @param user
* The user retrieving connection parameters.
*
* @param identifier
* The identifier of the connection whose parameters are being
* retrieved.
*
* @return
* A new map of all parameter name/value pairs that the given user has
* access to.
*/
public Map<String, String> retrieveParameters(ModeledAuthenticatedUser user,
String identifier) {
Map<String, String> parameterMap = new HashMap<>();
// Determine whether we have permission to read parameters
boolean canRetrieveParameters;
try {
canRetrieveParameters = hasObjectPermission(user, identifier,
ObjectPermission.Type.UPDATE);
}
// Provide empty (but mutable) map if unable to check permissions
catch (GuacamoleException e) {
return parameterMap;
}
// Populate parameter map if we have permission to do so
if (canRetrieveParameters) {
for (ConnectionParameterModel parameter : parameterMapper.select(identifier))
parameterMap.put(parameter.getName(), parameter.getValue());
}
return parameterMap;
}
/**
* Returns a connection records object which is backed by the given model.
*
* @param model
* The model object to use to back the returned connection record
* object.
*
* @return
* A connection record object which is backed by the given model.
*/
protected ConnectionRecord getObjectInstance(ConnectionRecordModel model) {
return new ModeledConnectionRecord(model);
}
/**
* Returns a list of connection records objects which are backed by the
* models in the given list.
*
* @param models
* The model objects to use to back the connection record objects
* within the returned list.
*
* @return
* A list of connection record objects which are backed by the models
* in the given list.
*/
protected List<ConnectionRecord> getObjectInstances(List<ConnectionRecordModel> models) {
// Create new list of records by manually converting each model
List<ConnectionRecord> objects = new ArrayList<>(models.size());
for (ConnectionRecordModel model : models)
objects.add(getObjectInstance(model));
return objects;
}
/**
* Retrieves the connection history records matching the given criteria.
* Retrieves up to <code>limit</code> connection history records matching
* the given terms and sorted by the given predicates. Only history records
* associated with data that the given user can read are returned.
*
* @param identifier
* The optional connection identifier for which history records should
* be retrieved, or null if all readable records should be retrieved.
*
* @param user
* The user retrieving the connection history.
*
* @param recordIdentifier
* The identifier of the specific history record to retrieve, if not
* all matching records. Search terms, etc. will still be applied to
* the single record.
*
* @param requiredContents
* The search terms that must be contained somewhere within each of the
* returned records.
*
* @param sortPredicates
* A list of predicates to sort the returned records by, in order of
* priority.
*
* @param limit
* The maximum number of records that should be returned.
*
* @return
* The connection history of the given connection, including any
* active connections.
*
* @throws GuacamoleException
* If permission to read the connection history is denied.
*/
public List<ConnectionRecord> retrieveHistory(String identifier,
ModeledAuthenticatedUser user, String recordIdentifier,
Collection<ActivityRecordSearchTerm> requiredContents,
List<ActivityRecordSortPredicate> sortPredicates, int limit)
throws GuacamoleException {
List<ConnectionRecordModel> searchResults;
// Bypass permission checks if the user is privileged or has System-level audit permissions
if (user.isPrivileged() || user.getUser().getEffectivePermissions().getSystemPermissions().hasPermission(SystemPermission.Type.AUDIT))
searchResults = connectionRecordMapper.search(identifier,
recordIdentifier, requiredContents, sortPredicates, limit,
getCaseSensitivity());
// Otherwise only return explicitly readable history records
else
searchResults = connectionRecordMapper.searchReadable(identifier,
user.getUser().getModel(), recordIdentifier,
requiredContents, sortPredicates, limit,
user.getEffectiveUserGroups(),
getCaseSensitivity());
return getObjectInstances(searchResults);
}
/**
* Connects to the given connection as the given user, using the given
* client information. If the user does not have permission to read the
* connection, permission will be denied.
*
* @param user
* The user connecting to the connection.
*
* @param connection
* The connection being connected to.
*
* @param info
* Information associated with the connecting client.
*
* @param tokens
* A Map containing the token names and corresponding values to be
* applied as parameter tokens when establishing the connection.
*
* @return
* A connected GuacamoleTunnel associated with a newly-established
* connection.
*
* @throws GuacamoleException
* If permission to connect to this connection is denied.
*/
public GuacamoleTunnel connect(ModeledAuthenticatedUser user,
ModeledConnection connection, GuacamoleClientInformation info,
Map<String, String> tokens) throws GuacamoleException {
// Connect only if READ permission is granted
if (hasObjectPermission(user, connection.getIdentifier(), ObjectPermission.Type.READ))
return tunnelService.getGuacamoleTunnel(user, connection, info, tokens);
// The user does not have permission to connect
throw new GuacamoleSecurityException("Permission denied.");
}
}

View File

@@ -0,0 +1,508 @@
/*
* 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.connection;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.auth.jdbc.base.ModeledChildDirectoryObject;
import org.apache.guacamole.form.BooleanField;
import org.apache.guacamole.form.EnumField;
import org.apache.guacamole.form.Field;
import org.apache.guacamole.form.Form;
import org.apache.guacamole.form.NumericField;
import org.apache.guacamole.form.TextField;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.ActivityRecordSet;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.ConnectionRecord;
import org.apache.guacamole.net.auth.GuacamoleProxyConfiguration;
import org.apache.guacamole.net.auth.GuacamoleProxyConfiguration.EncryptionMethod;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
import org.apache.guacamole.protocol.GuacamoleConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An implementation of the Connection object which is backed by a database
* model.
*/
public class ModeledConnection extends ModeledChildDirectoryObject<ConnectionModel>
implements Connection {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(ModeledConnection.class);
/**
* The name of the attribute which overrides the hostname used to connect
* to guacd for this connection.
*/
public static final String GUACD_HOSTNAME_NAME = "guacd-hostname";
/**
* The name of the attribute which overrides the port used to connect to
* guacd for this connection.
*/
public static final String GUACD_PORT_NAME = "guacd-port";
/**
* The name of the attribute which overrides the encryption method used to
* connect to guacd for this connection.
*/
public static final String GUACD_ENCRYPTION_NAME = "guacd-encryption";
/**
* The value specified for the "guacd-encryption" attribute if encryption
* should not be used to connect to guacd.
*/
public static final String GUACD_ENCRYPTION_VALUE_NONE = "none";
/**
* The value specified for the "guacd-encryption" attribute if SSL/TLS
* encryption should be used to connect to guacd.
*/
public static final String GUACD_ENCRYPTION_VALUE_SSL = "ssl";
/**
* All attributes which describe the configuration of the guacd instance
* which will be used to connect to the remote desktop described by this
* connection.
*/
public static final Form GUACD_PARAMETERS = new Form("guacd", Arrays.<Field>asList(
new TextField(GUACD_HOSTNAME_NAME),
new NumericField(GUACD_PORT_NAME),
new EnumField(GUACD_ENCRYPTION_NAME, Arrays.asList(
"",
GUACD_ENCRYPTION_VALUE_NONE,
GUACD_ENCRYPTION_VALUE_SSL
))
));
/**
* The name of the attribute which controls the maximum number of
* concurrent connections.
*/
public static final String MAX_CONNECTIONS_NAME = "max-connections";
/**
* The name of the attribute which controls the maximum number of
* concurrent connections per user.
*/
public static final String MAX_CONNECTIONS_PER_USER_NAME = "max-connections-per-user";
/**
* The connection weight attribute used for weighted load balancing algorithms.
*/
public static final String CONNECTION_WEIGHT = "weight";
/**
* The name of the attribute which controls whether the connection should
* be used as a spare only (all other non-spare connections within the same
* balancing group should be preferred).
*/
public static final String FAILOVER_ONLY_NAME = "failover-only";
/**
* All attributes related to restricting user accounts, within a logical
* form.
*/
public static final Form CONCURRENCY_LIMITS = new Form("concurrency", Arrays.<Field>asList(
new NumericField(MAX_CONNECTIONS_NAME),
new NumericField(MAX_CONNECTIONS_PER_USER_NAME)
));
/**
* All attributes related to load balancing in a logical form.
*/
public static final Form LOAD_BALANCING = new Form("load-balancing", Arrays.<Field>asList(
new NumericField(CONNECTION_WEIGHT),
new BooleanField(FAILOVER_ONLY_NAME, "true")
));
/**
* All possible attributes of connection objects organized as individual,
* logical forms.
*/
public static final Collection<Form> ATTRIBUTES = Collections.unmodifiableCollection(Arrays.asList(
CONCURRENCY_LIMITS,
LOAD_BALANCING,
GUACD_PARAMETERS
));
/**
* The names of all attributes which are explicitly supported by this
* extension's Connection objects.
*/
public static final Set<String> ATTRIBUTE_NAMES =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
GUACD_HOSTNAME_NAME,
GUACD_PORT_NAME,
GUACD_ENCRYPTION_NAME,
MAX_CONNECTIONS_NAME,
MAX_CONNECTIONS_PER_USER_NAME,
CONNECTION_WEIGHT,
FAILOVER_ONLY_NAME
)));
/**
* The environment of the Guacamole server.
*/
@Inject
private JDBCEnvironment environment;
/**
* Service for managing connections.
*/
@Inject
private ConnectionService connectionService;
/**
* Service for creating and tracking tunnels.
*/
@Inject
private GuacamoleTunnelService tunnelService;
/**
* Provider for lazy-loaded, permission-controlled configurations.
*/
@Inject
private Provider<ModeledGuacamoleConfiguration> configProvider;
/**
* Provider for creating connection record sets.
*/
@Inject
private Provider<ConnectionRecordSet> connectionRecordSetProvider;
/**
* The manually-set GuacamoleConfiguration, if any.
*/
private GuacamoleConfiguration config = null;
/**
* Creates a new, empty ModeledConnection.
*/
public ModeledConnection() {
}
@Override
public String getName() {
return getModel().getName();
}
@Override
public void setName(String name) {
getModel().setName(name);
}
@Override
public GuacamoleConfiguration getConfiguration() {
// If configuration has been manually set, return that
if (config != null)
return config;
// Otherwise, return permission-controlled configuration
ModeledGuacamoleConfiguration restrictedConfig = configProvider.get();
restrictedConfig.init(getCurrentUser(), getModel());
return restrictedConfig;
}
@Override
public void setConfiguration(GuacamoleConfiguration config) {
// Store manually-set configuration internally
this.config = config;
// Update model
getModel().setProtocol(config.getProtocol());
}
@Override
public Set<String> getSharingProfileIdentifiers()
throws GuacamoleException {
return getModel().getSharingProfileIdentifiers();
}
@Override
public Date getLastActive() {
return getModel().getLastActive();
}
@Override
public ActivityRecordSet<ConnectionRecord> getConnectionHistory()
throws GuacamoleException {
ConnectionRecordSet connectionRecordSet = connectionRecordSetProvider.get();
connectionRecordSet.init(getCurrentUser(), this.getIdentifier());
return connectionRecordSet;
}
@Override
public GuacamoleTunnel connect(GuacamoleClientInformation info,
Map<String, String> tokens) throws GuacamoleException {
return connectionService.connect(getCurrentUser(), this, info, tokens);
}
@Override
public int getActiveConnections() {
return tunnelService.getActiveConnections(this).size();
}
@Override
public Set<String> getSupportedAttributeNames() {
return ATTRIBUTE_NAMES;
}
@Override
public Map<String, String> getAttributes() {
// Include any defined arbitrary attributes
Map<String, String> attributes = super.getAttributes();
// Set connection limit attribute
attributes.put(MAX_CONNECTIONS_NAME, NumericField.format(getModel().getMaxConnections()));
// Set per-user connection limit attribute
attributes.put(MAX_CONNECTIONS_PER_USER_NAME, NumericField.format(getModel().getMaxConnectionsPerUser()));
// Set guacd (proxy) hostname and port
attributes.put(GUACD_HOSTNAME_NAME, getModel().getProxyHostname());
attributes.put(GUACD_PORT_NAME, NumericField.format(getModel().getProxyPort()));
// Set guacd (proxy) encryption method
EncryptionMethod encryptionMethod = getModel().getProxyEncryptionMethod();
if (encryptionMethod == null)
attributes.put(GUACD_ENCRYPTION_NAME, null);
else {
switch (encryptionMethod) {
// Unencrypted
case NONE:
attributes.put(GUACD_ENCRYPTION_NAME, GUACD_ENCRYPTION_VALUE_NONE);
break;
// SSL / TLS encryption
case SSL:
attributes.put(GUACD_ENCRYPTION_NAME, GUACD_ENCRYPTION_VALUE_SSL);
break;
// Unimplemented / unspecified
default:
attributes.put(GUACD_ENCRYPTION_NAME, null);
}
}
// Set connection weight
attributes.put(CONNECTION_WEIGHT, NumericField.format(getModel().getConnectionWeight()));
// Set whether connection is failover-only
attributes.put(FAILOVER_ONLY_NAME, getModel().isFailoverOnly() ? "true" : null);
return attributes;
}
@Override
public void setAttributes(Map<String, String> attributes) {
// Set arbitrary attributes
super.setAttributes(attributes);
// Translate connection limit attribute
try { getModel().setMaxConnections(NumericField.parse(attributes.get(MAX_CONNECTIONS_NAME))); }
catch (NumberFormatException e) {
logger.warn("Not setting maximum connections: {}", e.getMessage());
logger.debug("Unable to parse numeric attribute.", e);
}
// Translate per-user connection limit attribute
try { getModel().setMaxConnectionsPerUser(NumericField.parse(attributes.get(MAX_CONNECTIONS_PER_USER_NAME))); }
catch (NumberFormatException e) {
logger.warn("Not setting maximum connections per user: {}", e.getMessage());
logger.debug("Unable to parse numeric attribute.", e);
}
// Translate guacd hostname
getModel().setProxyHostname(TextField.parse(attributes.get(GUACD_HOSTNAME_NAME)));
// Translate guacd port
try { getModel().setProxyPort(NumericField.parse(attributes.get(GUACD_PORT_NAME))); }
catch (NumberFormatException e) {
logger.warn("Not setting guacd port: {}", e.getMessage());
logger.debug("Unable to parse numeric attribute.", e);
}
// Translate guacd encryption method
String encryptionMethod = attributes.get(GUACD_ENCRYPTION_NAME);
// Unencrypted
if (GUACD_ENCRYPTION_VALUE_NONE.equals(encryptionMethod))
getModel().setProxyEncryptionMethod(EncryptionMethod.NONE);
// SSL / TLS
else if (GUACD_ENCRYPTION_VALUE_SSL.equals(encryptionMethod))
getModel().setProxyEncryptionMethod(EncryptionMethod.SSL);
// Unimplemented / unspecified
else
getModel().setProxyEncryptionMethod(null);
// Translate connection weight attribute
try { getModel().setConnectionWeight(NumericField.parse(attributes.get(CONNECTION_WEIGHT))); }
catch (NumberFormatException e) {
logger.warn("Not setting the connection weight: {}", e.getMessage());
logger.debug("Unable to parse numeric attribute.", e);
}
// Translate failover-only attribute
getModel().setFailoverOnly("true".equals(attributes.get(FAILOVER_ONLY_NAME)));
}
/**
* Returns the maximum number of connections that should be allowed to this
* connection overall. If no limit applies, zero is returned.
*
* @return
* The maximum number of connections that should be allowed to this
* connection overall, or zero if no limit applies.
*
* @throws GuacamoleException
* If an error occurs while parsing the concurrency limit properties
* specified within guacamole.properties.
*/
public int getMaxConnections() throws GuacamoleException {
// Pull default from environment if connection limit is unset
Integer value = getModel().getMaxConnections();
if (value == null)
return environment.getDefaultMaxConnections();
// Otherwise use defined value
return value;
}
/**
* Returns the maximum number of connections that should be allowed to this
* connection for any individual user. If no limit applies, zero is
* returned.
*
* @return
* The maximum number of connections that should be allowed to this
* connection for any individual user, or zero if no limit applies.
*
* @throws GuacamoleException
* If an error occurs while parsing the concurrency limit properties
* specified within guacamole.properties.
*/
public int getMaxConnectionsPerUser() throws GuacamoleException {
// Pull default from environment if per-user connection limit is unset
Integer value = getModel().getMaxConnectionsPerUser();
if (value == null)
return environment.getDefaultMaxConnectionsPerUser();
// Otherwise use defined value
return value;
}
/**
* Returns the connection information which should be used to connect to
* guacd when establishing a connection to the remote desktop described by
* this connection. If no such information is defined for this specific
* remote desktop connection, the default guacd connection information will
* be used instead, as defined by JDBCEnvironment.
*
* @return
* The connection information which should be used to connect to guacd
* when establishing a connection to the remote desktop described by
* this connection.
*
* @throws GuacamoleException
* If the connection information for guacd cannot be parsed.
*/
public GuacamoleProxyConfiguration getGuacamoleProxyConfiguration()
throws GuacamoleException {
// Retrieve default proxy configuration from environment
GuacamoleProxyConfiguration defaultConfig = environment.getDefaultGuacamoleProxyConfiguration();
// Retrieve proxy configuration overrides from model
String hostname = getModel().getProxyHostname();
Integer port = getModel().getProxyPort();
EncryptionMethod encryptionMethod = getModel().getProxyEncryptionMethod();
// Produce new proxy configuration from model, using defaults where unspecified
return new GuacamoleProxyConfiguration(
hostname != null ? hostname : defaultConfig.getHostname(),
port != null ? port : defaultConfig.getPort(),
encryptionMethod != null ? encryptionMethod : defaultConfig.getEncryptionMethod()
);
}
/**
* Returns the weight of the connection used in applying weighted
* load balancing algorithms, or a default of 1 if the
* attribute is undefined.
*
* @return
* The weight of the connection used in applying weighted
* load balancing algorithms.
*/
public int getConnectionWeight() {
Integer connectionWeight = getModel().getConnectionWeight();
if (connectionWeight == null)
return 1;
return connectionWeight;
}
/**
* Returns whether this connection should be reserved for failover.
* Failover-only connections within a balancing group are only used when
* all non-failover connections are unavailable.
*
* @return
* true if this connection should be reserved for failover, false
* otherwise.
*/
public boolean isFailoverOnly() {
return getModel().isFailoverOnly();
}
}

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.connection;
import org.apache.guacamole.auth.jdbc.base.ModeledActivityRecord;
import org.apache.guacamole.net.auth.ConnectionRecord;
/**
* A ConnectionRecord which is backed by a database model.
*/
public class ModeledConnectionRecord extends ModeledActivityRecord
implements ConnectionRecord {
/**
* The model object backing this connection record.
*/
private final ConnectionRecordModel model;
/**
* Creates a new ModeledConnectionRecord backed by the given model object.
* Changes to this record will affect the backing model object, and changes
* to the backing model object will affect this record.
*
* @param model
* The model object to use to back this connection record.
*/
public ModeledConnectionRecord(ConnectionRecordModel model) {
super(ConnectionRecordSet.UUID_NAMESPACE, model);
this.model = model;
}
@Override
public String getConnectionIdentifier() {
return model.getConnectionIdentifier();
}
@Override
public String getConnectionName() {
return model.getConnectionName();
}
@Override
public String getSharingProfileIdentifier() {
return model.getSharingProfileIdentifier();
}
@Override
public String getSharingProfileName() {
return model.getSharingProfileName();
}
@Override
public ConnectionRecordModel getModel() {
return model;
}
}

View File

@@ -0,0 +1,115 @@
/*
* 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.connection;
import com.google.inject.Inject;
import java.util.Map;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.protocol.GuacamoleConfiguration;
/**
* Implementation of GuacamoleConfiguration which loads parameter values only
* if necessary, and only if allowed.
*/
public class ModeledGuacamoleConfiguration extends GuacamoleConfiguration {
/**
* The user this configuration belongs to. Access is based on his/her
* permission settings.
*/
private ModeledAuthenticatedUser currentUser;
/**
* The internal model object containing the values which represent the
* connection associated with this configuration.
*/
private ConnectionModel connectionModel;
/**
* Service for managing connection parameters.
*/
@Inject
private ConnectionService connectionService;
/**
* The manually-set parameter map, if any.
*/
private Map<String, String> parameters = null;
/**
* Creates a new, empty ModelGuacamoleConfiguration.
*/
public ModeledGuacamoleConfiguration() {
}
/**
* Initializes this configuration, associating it with the current
* authenticated user and populating it with data from the given model
* object.
*
* @param currentUser
* The user that created or retrieved this configuration.
*
* @param connectionModel
* The model object backing this configuration.
*/
public void init(ModeledAuthenticatedUser currentUser, ConnectionModel connectionModel) {
this.currentUser = currentUser;
this.connectionModel = connectionModel;
}
@Override
public String getProtocol() {
return connectionModel.getProtocol();
}
@Override
public void setProtocol(String protocol) {
super.setProtocol(protocol);
connectionModel.setProtocol(protocol);
}
@Override
public void setParameters(Map<String, String> parameters) {
this.parameters = parameters;
super.setParameters(parameters);
}
@Override
public Map<String, String> getParameters() {
// Retrieve visible parameters, if not overridden by setParameters()
if (parameters == null) {
// Retrieve all visible parameters
Map<String, String> visibleParameters =
connectionService.retrieveParameters(currentUser, connectionModel.getIdentifier());
// Use retrieved parameters to back future operations
super.setParameters(visibleParameters);
}
return super.getParameters();
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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.
*/
/**
* Classes related to connections and their parameters and history.
*/
package org.apache.guacamole.auth.jdbc.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.connectiongroup;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.JDBCDirectory;
import org.apache.guacamole.net.auth.ConnectionGroup;
import org.mybatis.guice.transactional.Transactional;
/**
* Implementation of the ConnectionGroup Directory which is driven by an
* underlying, arbitrary database.
*/
public class ConnectionGroupDirectory extends JDBCDirectory<ConnectionGroup> {
/**
* Service for managing connection group objects.
*/
@Inject
private ConnectionGroupService connectionGroupService;
@Override
public ConnectionGroup get(String identifier) throws GuacamoleException {
return connectionGroupService.retrieveObject(getCurrentUser(), identifier);
}
@Override
@Transactional
public Collection<ConnectionGroup> getAll(Collection<String> identifiers) throws GuacamoleException {
Collection<ModeledConnectionGroup> objects = connectionGroupService.retrieveObjects(getCurrentUser(), identifiers);
return Collections.<ConnectionGroup>unmodifiableCollection(objects);
}
@Override
@Transactional
public Set<String> getIdentifiers() throws GuacamoleException {
return connectionGroupService.getIdentifiers(getCurrentUser());
}
@Override
@Transactional
public void add(ConnectionGroup object) throws GuacamoleException {
connectionGroupService.createObject(getCurrentUser(), object);
}
@Override
@Transactional
public void update(ConnectionGroup object) throws GuacamoleException {
ModeledConnectionGroup connectionGroup = (ModeledConnectionGroup) object;
connectionGroupService.updateObject(getCurrentUser(), connectionGroup);
}
@Override
@Transactional
public void remove(String identifier) throws GuacamoleException {
connectionGroupService.deleteObject(getCurrentUser(), identifier);
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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.connectiongroup;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper;
import org.apache.guacamole.auth.jdbc.user.UserModel;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Mapper for connection group objects.
*/
public interface ConnectionGroupMapper extends ModeledDirectoryObjectMapper<ConnectionGroupModel> {
/**
* Selects the identifiers of all connection groups within the given parent
* connection group, regardless of whether they are readable by any
* particular user. This should only be called on behalf of a system
* administrator. If identifiers are needed by a non-administrative user
* who must have explicit read rights, use
* selectReadableIdentifiersWithin() instead.
*
* @param parentIdentifier
* The identifier of the parent connection group, or null if the root
* connection group is to be queried.
*
* @return
* A Set containing all identifiers of all objects.
*/
Set<String> selectIdentifiersWithin(@Param("parentIdentifier") String parentIdentifier);
/**
* Selects the identifiers of all connection groups within the given parent
* connection group that are explicitly readable by the given user. If
* identifiers are needed by a system administrator (who, by definition,
* does not need explicit read rights), use selectIdentifiersWithin()
* instead.
*
* @param user
* The user whose permissions should determine whether an identifier
* is returned.
*
* @param parentIdentifier
* The identifier of the parent connection group, or null if the root
* connection group is to be queried.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the user. If
* no groups are given, only permissions directly granted to the user
* will be used.
*
* @return
* A Set containing all identifiers of all readable objects.
*/
Set<String> selectReadableIdentifiersWithin(@Param("user") UserModel user,
@Param("parentIdentifier") String parentIdentifier,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Selects the connection group within the given parent group and having
* the given name. If no such connection group exists, null is returned.
*
* @param parentIdentifier
* The identifier of the parent group to search within.
*
* @param name
* The name of the connection group to find.
*
* @return
* The connection group having the given name within the given parent
* group, or null if no such connection group exists.
*/
ConnectionGroupModel selectOneByName(@Param("parentIdentifier") String parentIdentifier,
@Param("name") String name);
}

View File

@@ -0,0 +1,275 @@
/*
* 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.connectiongroup;
import java.util.HashSet;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.base.ChildObjectModel;
import org.apache.guacamole.net.auth.ConnectionGroup;
/**
* Object representation of a Guacamole connection group, as represented in the
* database.
*/
public class ConnectionGroupModel extends ChildObjectModel {
/**
* The human-readable name associated with this connection group.
*/
private String name;
/**
* The type of this connection group, such as organizational or balancing.
*/
private ConnectionGroup.Type type;
/**
* The maximum number of connections that can be established to this
* connection group concurrently, zero if no restriction applies, or
* null if the default restrictions should be applied.
*/
private Integer maxConnections;
/**
* The maximum number of connections that can be established to this
* connection group concurrently by any one user, zero if no restriction
* applies, or null if the default restrictions should be applied.
*/
private Integer maxConnectionsPerUser;
/**
* Whether individual users should be consistently assigned the same
* connection within a balancing group until they log out.
*/
private boolean sessionAffinityEnabled;
/**
* The identifiers of all readable child connections within this connection
* group.
*/
private Set<String> connectionIdentifiers = new HashSet<String>();
/**
* The identifiers of all readable child connection groups within this
* connection group.
*/
private Set<String> connectionGroupIdentifiers = new HashSet<String>();
/**
* Creates a new, empty connection group.
*/
public ConnectionGroupModel() {
}
/**
* Returns the name associated with this connection group.
*
* @return
* The name associated with this connection group.
*/
public String getName() {
return name;
}
/**
* Sets the name associated with this connection group.
*
* @param name
* The name to associate with this connection group.
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns the type of this connection group, such as organizational or
* balancing.
*
* @return
* The type of this connection group.
*/
public ConnectionGroup.Type getType() {
return type;
}
/**
* Sets the type of this connection group, such as organizational or
* balancing.
*
* @param type
* The type of this connection group.
*/
public void setType(ConnectionGroup.Type type) {
this.type = type;
}
/**
* Returns the maximum number of connections that can be established to
* this connection group concurrently.
*
* @return
* The maximum number of connections that can be established to this
* connection group concurrently, zero if no restriction applies, or
* null if the default restrictions should be applied.
*/
public Integer getMaxConnections() {
return maxConnections;
}
/**
* Sets the maximum number of connections that can be established to this
* connection group concurrently.
*
* @param maxConnections
* The maximum number of connections that can be established to this
* connection group concurrently, zero if no restriction applies, or
* null if the default restrictions should be applied.
*/
public void setMaxConnections(Integer maxConnections) {
this.maxConnections = maxConnections;
}
/**
* Returns the maximum number of connections that can be established to
* this connection group concurrently by any one user.
*
* @return
* The maximum number of connections that can be established to this
* connection group concurrently by any one user, zero if no
* restriction applies, or null if the default restrictions should be
* applied.
*/
public Integer getMaxConnectionsPerUser() {
return maxConnectionsPerUser;
}
/**
* Sets the maximum number of connections that can be established to this
* connection group concurrently by any one user.
*
* @param maxConnectionsPerUser
* The maximum number of connections that can be established to this
* connection group concurrently by any one user, zero if no
* restriction applies, or null if the default restrictions should be
* applied.
*/
public void setMaxConnectionsPerUser(Integer maxConnectionsPerUser) {
this.maxConnectionsPerUser = maxConnectionsPerUser;
}
/**
* Returns whether individual users should be consistently assigned the same
* connection within a balancing group until they log out.
*
* @return
* Whether individual users should be consistently assigned the same
* connection within a balancing group until they log out.
*/
public boolean isSessionAffinityEnabled() {
return sessionAffinityEnabled;
}
/**
* Sets whether individual users should be consistently assigned the same
* connection within a balancing group until they log out.
*
* @param sessionAffinityEnabled
* Whether individual users should be consistently assigned the same
* connection within a balancing group until they log out.
*/
public void setSessionAffinityEnabled(boolean sessionAffinityEnabled) {
this.sessionAffinityEnabled = sessionAffinityEnabled;
}
/**
* Returns the identifiers of all readable child connections within this
* connection group. This is set only when the parent connection group is
* queried, and has no effect when a connection group is inserted, updated,
* or deleted.
*
* @return
* The identifiers of all readable child connections within this
* connection group.
*/
public Set<String> getConnectionIdentifiers() {
return connectionIdentifiers;
}
/**
* Sets the identifiers of all readable child connections within this
* connection group. This should be set only when the parent connection
* group is queried, as it has no effect when a connection group is
* inserted, updated, or deleted.
*
* @param connectionIdentifiers
* The identifiers of all readable child connections within this
* connection group.
*/
public void setConnectionIdentifiers(Set<String> connectionIdentifiers) {
this.connectionIdentifiers = connectionIdentifiers;
}
/**
* Returns the identifiers of all readable child connection groups within
* this connection group. This is set only when the parent connection group
* is queried, and has no effect when a connection group is inserted,
* updated, or deleted.
*
* @return
* The identifiers of all readable child connection groups within this
* connection group.
*/
public Set<String> getConnectionGroupIdentifiers() {
return connectionGroupIdentifiers;
}
/**
* Sets the identifiers of all readable child connection groups within this
* connection group. This should be set only when the parent connection
* group is queried, as it has no effect when a connection group is
* inserted, updated, or deleted.
*
* @param connectionGroupIdentifiers
* The identifiers of all readable child connection groups within this
* connection group.
*/
public void setConnectionGroupIdentifiers(Set<String> connectionGroupIdentifiers) {
this.connectionGroupIdentifiers = connectionGroupIdentifiers;
}
@Override
public String getIdentifier() {
// If no associated ID, then no associated identifier
Integer id = getObjectID();
if (id == null)
return null;
// Otherwise, the identifier is the ID as a string
return id.toString();
}
@Override
public void setIdentifier(String identifier) {
throw new UnsupportedOperationException("Connection group identifiers are derived from IDs. They cannot be set.");
}
}

View File

@@ -0,0 +1,272 @@
/*
* 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.connectiongroup;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Map;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.GuacamoleUnsupportedException;
import org.apache.guacamole.auth.jdbc.base.ModeledChildDirectoryObjectService;
import org.apache.guacamole.auth.jdbc.permission.ConnectionGroupPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionMapper;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.ConnectionGroup;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.net.auth.permission.SystemPermission;
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating connection groups.
*/
public class ConnectionGroupService extends ModeledChildDirectoryObjectService<ModeledConnectionGroup,
ConnectionGroup, ConnectionGroupModel> {
/**
* Mapper for accessing connection groups.
*/
@Inject
private ConnectionGroupMapper connectionGroupMapper;
/**
* Mapper for manipulating connection group permissions.
*/
@Inject
private ConnectionGroupPermissionMapper connectionGroupPermissionMapper;
/**
* Provider for creating connection groups.
*/
@Inject
private Provider<ModeledConnectionGroup> connectionGroupProvider;
/**
* Service for creating and tracking tunnels.
*/
@Inject
private GuacamoleTunnelService tunnelService;
@Override
protected ModeledDirectoryObjectMapper<ConnectionGroupModel> getObjectMapper() {
return connectionGroupMapper;
}
@Override
protected ObjectPermissionMapper getPermissionMapper() {
return connectionGroupPermissionMapper;
}
@Override
protected ModeledConnectionGroup getObjectInstance(ModeledAuthenticatedUser currentUser,
ConnectionGroupModel model) {
ModeledConnectionGroup connectionGroup = connectionGroupProvider.get();
connectionGroup.init(currentUser, model);
return connectionGroup;
}
@Override
protected ConnectionGroupModel getModelInstance(ModeledAuthenticatedUser currentUser,
final ConnectionGroup object) {
// Create new ModeledConnectionGroup backed by blank model
ConnectionGroupModel model = new ConnectionGroupModel();
ModeledConnectionGroup connectionGroup = getObjectInstance(currentUser, model);
// Set model contents through ModeledConnectionGroup, copying the provided connection group
connectionGroup.setParentIdentifier(object.getParentIdentifier());
connectionGroup.setName(object.getName());
connectionGroup.setType(object.getType());
connectionGroup.setAttributes(object.getAttributes());
return model;
}
@Override
protected boolean hasCreatePermission(ModeledAuthenticatedUser user)
throws GuacamoleException {
// Return whether user has explicit connection group creation permission
SystemPermissionSet permissionSet = user.getUser().getEffectivePermissions().getSystemPermissions();
return permissionSet.hasPermission(SystemPermission.Type.CREATE_CONNECTION_GROUP);
}
@Override
protected ObjectPermissionSet getEffectivePermissionSet(ModeledAuthenticatedUser user)
throws GuacamoleException {
// Return permissions related to connection groups
return user.getUser().getEffectivePermissions().getConnectionGroupPermissions();
}
@Override
protected ObjectPermissionSet getParentEffectivePermissionSet(ModeledAuthenticatedUser user)
throws GuacamoleException {
// Connection groups are contained by other connection groups
return user.getUser().getEffectivePermissions().getConnectionGroupPermissions();
}
@Override
protected void beforeCreate(ModeledAuthenticatedUser user,
ConnectionGroup object, ConnectionGroupModel model)
throws GuacamoleException {
super.beforeCreate(user, object, model);
// Name must not be blank
if (model.getName() == null || model.getName().trim().isEmpty())
throw new GuacamoleClientException("Connection group names must not be blank.");
// Do not attempt to create duplicate connection groups
ConnectionGroupModel existing = connectionGroupMapper.selectOneByName(model.getParentIdentifier(), model.getName());
if (existing != null)
throw new GuacamoleClientException("The connection group \"" + model.getName() + "\" already exists.");
}
@Override
protected void beforeUpdate(ModeledAuthenticatedUser user,
ModeledConnectionGroup object, ConnectionGroupModel model)
throws GuacamoleException {
super.beforeUpdate(user, object, model);
// Name must not be blank
if (model.getName() == null || model.getName().trim().isEmpty())
throw new GuacamoleClientException("Connection group names must not be blank.");
// Check whether such a connection group is already present
ConnectionGroupModel existing = connectionGroupMapper.selectOneByName(model.getParentIdentifier(), model.getName());
if (existing != null) {
// If the specified name matches a DIFFERENT existing connection group, the update cannot continue
if (!existing.getObjectID().equals(model.getObjectID()))
throw new GuacamoleClientException("The connection group \"" + model.getName() + "\" already exists.");
}
// Verify that this connection group's location does not create a cycle
String relativeParentIdentifier = model.getParentIdentifier();
while (relativeParentIdentifier != null) {
// Abort if cycle is detected
if (relativeParentIdentifier.equals(model.getIdentifier()))
throw new GuacamoleUnsupportedException("A connection group may not contain itself.");
// Advance to next parent
ModeledConnectionGroup relativeParentGroup = retrieveObject(user, relativeParentIdentifier);
relativeParentIdentifier = relativeParentGroup.getModel().getParentIdentifier();
}
}
/**
* Returns the set of all identifiers for all connection groups within the
* connection group having the given identifier. Only connection groups
* that the user has read access to will be returned.
*
* Permission to read the connection group having the given identifier is
* NOT checked.
*
* @param user
* The user retrieving the identifiers.
*
* @param identifier
* The identifier of the parent connection group, or null to check the
* root connection group.
*
* @return
* The set of all identifiers for all connection groups in the
* connection group having the given identifier that the user has read
* access to.
*
* @throws GuacamoleException
* If an error occurs while reading identifiers.
*/
public Set<String> getIdentifiersWithin(ModeledAuthenticatedUser user,
String identifier)
throws GuacamoleException {
// Bypass permission checks if the user is privileged
if (user.isPrivileged())
return connectionGroupMapper.selectIdentifiersWithin(identifier);
// Otherwise only return explicitly readable identifiers
else
return connectionGroupMapper.selectReadableIdentifiersWithin(
user.getUser().getModel(), identifier,
user.getEffectiveUserGroups(),
getCaseSensitivity());
}
/**
* Connects to the given connection group as the given user, using the
* given client information. If the user does not have permission to read
* the connection group, permission will be denied.
*
* @param user
* The user connecting to the connection group.
*
* @param connectionGroup
* The connectionGroup being connected to.
*
* @param info
* Information associated with the connecting client.
*
* @param tokens
* A Map containing the token names and corresponding values to be
* applied as parameter tokens when establishing the connection.
*
* @return
* A connected GuacamoleTunnel associated with a newly-established
* connection.
*
* @throws GuacamoleException
* If permission to connect to this connection is denied.
*/
public GuacamoleTunnel connect(ModeledAuthenticatedUser user,
ModeledConnectionGroup connectionGroup, GuacamoleClientInformation info,
Map<String, String> tokens) throws GuacamoleException {
// Connect only if READ permission is granted
if (hasObjectPermission(user, connectionGroup.getIdentifier(), ObjectPermission.Type.READ))
return tunnelService.getGuacamoleTunnel(user, connectionGroup, info, tokens);
// The user does not have permission to connect
throw new GuacamoleSecurityException("Permission denied.");
}
}

View File

@@ -0,0 +1,282 @@
/*
* 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.connectiongroup;
import com.google.inject.Inject;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.auth.jdbc.base.ModeledChildDirectoryObject;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.form.BooleanField;
import org.apache.guacamole.form.Field;
import org.apache.guacamole.form.Form;
import org.apache.guacamole.form.NumericField;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.ConnectionGroup;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An implementation of the ConnectionGroup object which is backed by a
* database model.
*/
public class ModeledConnectionGroup extends ModeledChildDirectoryObject<ConnectionGroupModel>
implements ConnectionGroup {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(ModeledConnectionGroup.class);
/**
* The name of the attribute which controls the maximum number of
* concurrent connections.
*/
public static final String MAX_CONNECTIONS_NAME = "max-connections";
/**
* The name of the attribute which controls the maximum number of
* concurrent connections per user.
*/
public static final String MAX_CONNECTIONS_PER_USER_NAME = "max-connections-per-user";
/**
* The name of the attribute which controls whether individual users will be
* consistently assigned the same connection within a balancing group until
* they log out.
*/
public static final String ENABLE_SESSION_AFFINITY = "enable-session-affinity";
/**
* All attributes related to restricting user accounts, within a logical
* form.
*/
public static final Form CONCURRENCY_LIMITS = new Form("concurrency", Arrays.<Field>asList(
new NumericField(MAX_CONNECTIONS_NAME),
new NumericField(MAX_CONNECTIONS_PER_USER_NAME),
new BooleanField(ENABLE_SESSION_AFFINITY, "true")
));
/**
* All possible attributes of connection group objects organized as
* individual, logical forms.
*/
public static final Collection<Form> ATTRIBUTES = Collections.unmodifiableCollection(Arrays.asList(
CONCURRENCY_LIMITS
));
/**
* The names of all attributes which are explicitly supported by this
* extension's ConnectionGroup objects.
*/
public static final Set<String> ATTRIBUTE_NAMES =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
MAX_CONNECTIONS_NAME,
MAX_CONNECTIONS_PER_USER_NAME,
ENABLE_SESSION_AFFINITY
)));
/**
* The environment of the Guacamole server.
*/
@Inject
private JDBCEnvironment environment;
/**
* Service for managing connection groups.
*/
@Inject
private ConnectionGroupService connectionGroupService;
/**
* Service for creating and tracking tunnels.
*/
@Inject
private GuacamoleTunnelService tunnelService;
/**
* Creates a new, empty ModeledConnectionGroup.
*/
public ModeledConnectionGroup() {
}
@Override
public String getName() {
return getModel().getName();
}
@Override
public void setName(String name) {
getModel().setName(name);
}
@Override
public GuacamoleTunnel connect(GuacamoleClientInformation info,
Map<String, String> tokens) throws GuacamoleException {
return connectionGroupService.connect(getCurrentUser(), this, info, tokens);
}
@Override
public int getActiveConnections() {
return tunnelService.getActiveConnections(this).size();
}
@Override
public void setType(Type type) {
getModel().setType(type);
}
@Override
public Type getType() {
return getModel().getType();
}
@Override
public Set<String> getConnectionIdentifiers()
throws GuacamoleException {
return getModel().getConnectionIdentifiers();
}
@Override
public Set<String> getConnectionGroupIdentifiers()
throws GuacamoleException {
return getModel().getConnectionGroupIdentifiers();
}
@Override
public Set<String> getSupportedAttributeNames() {
return ATTRIBUTE_NAMES;
}
@Override
public Map<String, String> getAttributes() {
// Include any defined arbitrary attributes
Map<String, String> attributes = super.getAttributes();
// Set connection limit attribute
attributes.put(MAX_CONNECTIONS_NAME, NumericField.format(getModel().getMaxConnections()));
// Set per-user connection limit attribute
attributes.put(MAX_CONNECTIONS_PER_USER_NAME, NumericField.format(getModel().getMaxConnectionsPerUser()));
// Set session affinity attribute
attributes.put(ENABLE_SESSION_AFFINITY,
getModel().isSessionAffinityEnabled() ? "true" : "");
return attributes;
}
@Override
public void setAttributes(Map<String, String> attributes) {
// Set arbitrary attributes
super.setAttributes(attributes);
// Translate connection limit attribute
try { getModel().setMaxConnections(NumericField.parse(attributes.get(MAX_CONNECTIONS_NAME))); }
catch (NumberFormatException e) {
logger.warn("Not setting maximum connections: {}", e.getMessage());
logger.debug("Unable to parse numeric attribute.", e);
}
// Translate per-user connection limit attribute
try { getModel().setMaxConnectionsPerUser(NumericField.parse(attributes.get(MAX_CONNECTIONS_PER_USER_NAME))); }
catch (NumberFormatException e) {
logger.warn("Not setting maximum connections per user: {}", e.getMessage());
logger.debug("Unable to parse numeric attribute.", e);
}
// Translate session affinity attribute
getModel().setSessionAffinityEnabled(
"true".equals(attributes.get(ENABLE_SESSION_AFFINITY)));
}
/**
* Returns the maximum number of connections that should be allowed to this
* connection group overall. If no limit applies, zero is returned.
*
* @return
* The maximum number of connections that should be allowed to this
* connection group overall, or zero if no limit applies.
*
* @throws GuacamoleException
* If an error occurs while parsing the concurrency limit properties
* specified within guacamole.properties.
*/
public int getMaxConnections() throws GuacamoleException {
// Pull default from environment if connection limit is unset
Integer value = getModel().getMaxConnections();
if (value == null)
return environment.getDefaultMaxGroupConnections();
// Otherwise use defined value
return value;
}
/**
* Returns the maximum number of connections that should be allowed to this
* connection group for any individual user. If no limit applies, zero is
* returned.
*
* @return
* The maximum number of connections that should be allowed to this
* connection group for any individual user, or zero if no limit
* applies.
*
* @throws GuacamoleException
* If an error occurs while parsing the concurrency limit properties
* specified within guacamole.properties.
*/
public int getMaxConnectionsPerUser() throws GuacamoleException {
// Pull default from environment if per-user connection limit is unset
Integer value = getModel().getMaxConnectionsPerUser();
if (value == null)
return environment.getDefaultMaxGroupConnectionsPerUser();
// Otherwise use defined value
return value;
}
/**
* Returns whether individual users should be consistently assigned the same
* connection within a balancing group until they log out.
*
* @return
* Whether individual users should be consistently assigned the same
* connection within a balancing group until they log out.
*/
public boolean isSessionAffinityEnabled() {
return getModel().isSessionAffinityEnabled();
}
}

View File

@@ -0,0 +1,145 @@
/*
* 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.connectiongroup;
import com.google.inject.Inject;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.connection.ConnectionService;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.base.RestrictedObject;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.ConnectionGroup;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
/**
* The root connection group, here represented as its own dedicated object as
* the database does not contain an actual root group.
*/
public class RootConnectionGroup extends RestrictedObject
implements ConnectionGroup {
/**
* The identifier used to represent the root connection group. There is no
* corresponding entry in the database, thus a reserved identifier that
* cannot collide with database-generated identifiers is needed.
*/
public static final String IDENTIFIER = "ROOT";
/**
* The human-readable name of this connection group. The name of the root
* group is not normally visible, and may even be replaced by the web
* interface for the sake of translation.
*/
public static final String NAME = "ROOT";
/**
* Service for managing connection objects.
*/
@Inject
private ConnectionService connectionService;
/**
* Service for managing connection group objects.
*/
@Inject
private ConnectionGroupService connectionGroupService;
/**
* Creates a new, empty RootConnectionGroup.
*/
public RootConnectionGroup() {
}
@Override
public String getName() {
return NAME;
}
@Override
public void setName(String name) {
throw new UnsupportedOperationException("The root connection group cannot be modified.");
}
@Override
public String getParentIdentifier() {
return null;
}
@Override
public void setParentIdentifier(String parentIdentifier) {
throw new UnsupportedOperationException("The root connection group cannot be modified.");
}
@Override
public Type getType() {
return ConnectionGroup.Type.ORGANIZATIONAL;
}
@Override
public void setType(Type type) {
throw new UnsupportedOperationException("The root connection group cannot be modified.");
}
@Override
public Set<String> getConnectionIdentifiers() throws GuacamoleException {
return connectionService.getIdentifiersWithin(getCurrentUser(), null);
}
@Override
public Set<String> getConnectionGroupIdentifiers()
throws GuacamoleException {
return connectionGroupService.getIdentifiersWithin(getCurrentUser(), null);
}
@Override
public String getIdentifier() {
return IDENTIFIER;
}
@Override
public void setIdentifier(String identifier) {
throw new UnsupportedOperationException("The root connection group cannot be modified.");
}
@Override
public GuacamoleTunnel connect(GuacamoleClientInformation info,
Map<String, String> tokens) throws GuacamoleException {
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public int getActiveConnections() {
return 0;
}
@Override
public Map<String, String> getAttributes() {
return Collections.<String, String>emptyMap();
}
@Override
public void setAttributes(Map<String, String> attributes) {
throw new UnsupportedOperationException("The root connection group cannot be modified.");
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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.
*/
/**
* Classes related to connection groups.
*/
package org.apache.guacamole.auth.jdbc.connectiongroup;

View File

@@ -0,0 +1,26 @@
/*
* 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.
*/
/**
* The base JDBC authentication provider. This authentication provider serves
* as a basis for other JDBC authentication provider implementations which are
* driven by relatively-common schemas. The only difference between such
* implementations are maintained within database-specific MyBatis mappings.
*/
package org.apache.guacamole.auth.jdbc;

View File

@@ -0,0 +1,117 @@
/*
* 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.permission;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
import org.apache.guacamole.auth.jdbc.user.ModeledUser;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.net.auth.permission.Permission;
import org.apache.guacamole.net.auth.permission.PermissionSet;
/**
* Abstract PermissionService implementation which provides additional
* convenience methods for enforcing the permission model.
*
* @param <PermissionSetType>
* The type of permission sets this service provides access to.
*
* @param <PermissionType>
* The type of permission this service provides access to.
*/
public abstract class AbstractPermissionService<PermissionSetType extends PermissionSet<PermissionType>,
PermissionType extends Permission>
implements PermissionService<PermissionSetType, PermissionType> {
/**
* Returns the ObjectPermissionSet related to the type of the given entity.
* If the given entity represents a user, then the ObjectPermissionSet
* containing user permissions is returned. If the given entity represents
* a user group, then the ObjectPermissionSet containing user group
* permissions is returned.
*
* @param user
* The user to retrieve the ObjectPermissionSet from.
*
* @param targetEntity
* The entity whose type dictates the ObjectPermissionSet returned.
*
* @return
* The ObjectPermissionSet related to the type of the given entity.
*
* @throws GuacamoleException
* If the relevant ObjectPermissionSet cannot be retrieved.
*/
protected ObjectPermissionSet getRelevantPermissionSet(ModeledUser user,
ModeledPermissions<? extends EntityModel> targetEntity)
throws GuacamoleException {
if (targetEntity.isUser())
return user.getUserPermissions();
if (targetEntity.isUserGroup())
return user.getUserGroupPermissions();
// Entities should be only users or groups
throw new UnsupportedOperationException("Unexpected entity type.");
}
/**
* Determines whether the given user can read the permissions currently
* granted to the given target entity. If the reading user and the target
* entity are not the same, then explicit READ or SYSTEM_ADMINISTER access
* is required. Permission inheritance via user groups is taken into account.
*
* @param user
* The user attempting to read permissions.
*
* @param targetEntity
* The entity whose permissions are being read.
*
* @return
* true if permission is granted, false otherwise.
*
* @throws GuacamoleException
* If an error occurs while checking permission status, or if
* permission is denied to read the current user's permissions.
*/
protected boolean canReadPermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity)
throws GuacamoleException {
// A user can always read their own permissions
if (targetEntity.isUser(user.getUser().getIdentifier()))
return true;
// Privileged users (such as system administrators) may do anything
if (user.isPrivileged())
return true;
// Can read permissions on target entity if explicit READ is granted
ObjectPermissionSet permissionSet = getRelevantPermissionSet(user.getUser(), targetEntity);
return permissionSet.hasPermission(ObjectPermission.Type.READ, targetEntity.getIdentifier());
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.permission;
/**
* Mapper for connection group permissions.
*/
public interface ConnectionGroupPermissionMapper extends ObjectPermissionMapper {}

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.permission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
/**
* Service which provides convenience methods for creating, retrieving, and
* deleting connection group permissions. This service will automatically
* enforce the permissions of the current user.
*/
public class ConnectionGroupPermissionService extends ModeledObjectPermissionService {
/**
* Mapper for connection group permissions.
*/
@Inject
private ConnectionGroupPermissionMapper connectionGroupPermissionMapper;
/**
* Provider for connection group permission sets.
*/
@Inject
private Provider<ConnectionGroupPermissionSet> connectionGroupPermissionSetProvider;
@Override
protected ObjectPermissionMapper getPermissionMapper() {
return connectionGroupPermissionMapper;
}
@Override
public ObjectPermissionSet getPermissionSet(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException {
// Create permission set for requested entity
ObjectPermissionSet permissionSet = connectionGroupPermissionSetProvider.get();
permissionSet.init(user, targetEntity, effectiveGroups);
return permissionSet;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.permission;
import com.google.inject.Inject;
/**
* A database implementation of ObjectPermissionSet which uses an injected
* service to query and manipulate the connection group permissions associated
* with a particular user.
*/
public class ConnectionGroupPermissionSet extends ObjectPermissionSet {
/**
* Service for querying and manipulating connection group permissions.
*/
@Inject
private ConnectionGroupPermissionService connectionGroupPermissionService;
@Override
protected ObjectPermissionService getObjectPermissionService() {
return connectionGroupPermissionService;
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.permission;
/**
* Mapper for connection permissions.
*/
public interface ConnectionPermissionMapper extends ObjectPermissionMapper {}

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.permission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
/**
* Service which provides convenience methods for creating, retrieving, and
* deleting connection permissions. This service will automatically enforce the
* permissions of the current user.
*/
public class ConnectionPermissionService extends ModeledObjectPermissionService {
/**
* Mapper for connection permissions.
*/
@Inject
private ConnectionPermissionMapper connectionPermissionMapper;
/**
* Provider for connection permission sets.
*/
@Inject
private Provider<ConnectionPermissionSet> connectionPermissionSetProvider;
@Override
protected ObjectPermissionMapper getPermissionMapper() {
return connectionPermissionMapper;
}
@Override
public ObjectPermissionSet getPermissionSet(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException {
// Create permission set for requested entity
ObjectPermissionSet permissionSet = connectionPermissionSetProvider.get();
permissionSet.init(user, targetEntity, effectiveGroups);
return permissionSet;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.permission;
import com.google.inject.Inject;
/**
* A database implementation of ObjectPermissionSet which uses an injected
* service to query and manipulate the connection permissions associated with
* a particular user.
*/
public class ConnectionPermissionSet extends ObjectPermissionSet {
/**
* Service for querying and manipulating connection permissions.
*/
@Inject
private ConnectionPermissionService connectionPermissionService;
@Override
protected ObjectPermissionService getObjectPermissionService() {
return connectionPermissionService;
}
}

View File

@@ -0,0 +1,220 @@
/*
* 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.permission;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.properties.CaseSensitivity;
/**
* Service which provides convenience methods for creating, retrieving, and
* deleting object permissions within a backend database model. This service
* will automatically enforce the permissions of the current user.
*/
public abstract class ModeledObjectPermissionService
extends ModeledPermissionService<ObjectPermissionSet, ObjectPermission, ObjectPermissionModel>
implements ObjectPermissionService {
@Override
protected abstract ObjectPermissionMapper getPermissionMapper();
@Override
protected ObjectPermission getPermissionInstance(ObjectPermissionModel model) {
return new ObjectPermission(model.getType(), model.getObjectIdentifier());
}
@Override
protected ObjectPermissionModel getModelInstance(
ModeledPermissions<? extends EntityModel> targetEntity,
ObjectPermission permission) {
ObjectPermissionModel model = new ObjectPermissionModel();
// Populate model object with data from entity and permission
model.setEntityID(targetEntity.getModel().getEntityID());
model.setType(permission.getType());
model.setObjectIdentifier(permission.getObjectIdentifier());
return model;
}
/**
* Determines whether the current user has permission to update the given
* target entity, adding or removing the given permissions. Such permission
* depends on whether the current user is a system administrator, whether
* they have explicit UPDATE permission on the target entity, and whether
* they have explicit ADMINISTER permission on all affected objects.
* Permission inheritance via user groups is taken into account.
*
* @param user
* The user who is changing permissions.
*
* @param targetEntity
* The entity whose permissions are being changed.
*
* @param permissions
* The permissions that are being added or removed from the target
* entity.
*
* @return
* true if the user has permission to change the target entity's
* permissions as specified, false otherwise.
*
* @throws GuacamoleException
* If an error occurs while checking permission status, or if
* permission is denied to read the current user's permissions.
*/
protected boolean canAlterPermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<ObjectPermission> permissions)
throws GuacamoleException {
// Privileged users (such as system administrators) may do anything
if (user.isPrivileged())
return true;
// Verify user has update permission on the target entity
ObjectPermissionSet permissionSet = getRelevantPermissionSet(user.getUser(), targetEntity);
if (!permissionSet.hasPermission(ObjectPermission.Type.UPDATE, targetEntity.getIdentifier()))
return false;
// Produce collection of affected identifiers
Collection<String> affectedIdentifiers = new HashSet<String>(permissions.size());
for (ObjectPermission permission : permissions)
affectedIdentifiers.add(permission.getObjectIdentifier());
// Determine subset of affected identifiers that we have admin access to
ObjectPermissionSet affectedPermissionSet = getPermissionSet(user, user.getUser(), user.getEffectiveUserGroups());
Collection<String> allowedSubset = affectedPermissionSet.getAccessibleObjects(
Collections.singleton(ObjectPermission.Type.ADMINISTER),
affectedIdentifiers
);
// The permissions can be altered if and only if the set of objects we
// are allowed to administer is equal to the set of objects we will be
// affecting.
return affectedIdentifiers.size() == allowedSubset.size();
}
@Override
public void createPermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<ObjectPermission> permissions)
throws GuacamoleException {
// Create permissions only if user has permission to do so
if (canAlterPermissions(user, targetEntity, permissions)) {
CaseSensitivity caseSensitivity = getCaseSensitivity();
batchPermissionUpdates(permissions, permissionSubset -> {
Collection<ObjectPermissionModel> models = getModelInstances(
targetEntity, permissionSubset);
getPermissionMapper().insert(models, caseSensitivity);
});
return;
}
// User lacks permission to create object permissions
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public void deletePermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<ObjectPermission> permissions)
throws GuacamoleException {
// Delete permissions only if user has permission to do so
if (canAlterPermissions(user, targetEntity, permissions)) {
CaseSensitivity caseSensitivity = getCaseSensitivity();
batchPermissionUpdates(permissions, permissionSubset -> {
Collection<ObjectPermissionModel> models = getModelInstances(
targetEntity, permissionSubset);
getPermissionMapper().delete(models, caseSensitivity);
});
return;
}
// User lacks permission to delete object permissions
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public boolean hasPermission(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
ObjectPermission.Type type, String identifier,
Set<String> effectiveGroups) throws GuacamoleException {
// Retrieve permissions only if allowed
if (canReadPermissions(user, targetEntity))
return getPermissionMapper().selectOne(targetEntity.getModel(),
type, identifier, effectiveGroups, getCaseSensitivity()) != null;
// User cannot read this entity's permissions
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public Collection<String> retrieveAccessibleIdentifiers(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<ObjectPermission.Type> permissions,
Collection<String> identifiers, Set<String> effectiveGroups)
throws GuacamoleException {
// Nothing is always accessible
if (identifiers.isEmpty())
return identifiers;
// Privileged users (such as system administrators) may access everything
if (user.isPrivileged())
return identifiers;
// Otherwise, return explicitly-retrievable identifiers only if allowed
if (canReadPermissions(user, targetEntity))
return getPermissionMapper().selectAccessibleIdentifiers(
targetEntity.getModel(), permissions, identifiers,
effectiveGroups, getCaseSensitivity());
// User cannot read this entity's permissions
throw new GuacamoleSecurityException("Permission denied.");
}
}

View File

@@ -0,0 +1,205 @@
/*
* 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.permission;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
import org.apache.guacamole.net.auth.permission.Permission;
import org.apache.guacamole.net.auth.permission.PermissionSet;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
/**
* Service which provides convenience methods for creating, retrieving, and
* deleting permissions within a backend database model, and for obtaining the
* permission sets that contain these permissions. This service will
* automatically enforce the permissions of the current user.
*
* @param <PermissionSetType>
* The type of permission sets this service provides access to.
*
* @param <PermissionType>
* The type of permission this service provides access to.
*
* @param <ModelType>
* The underlying model object used to represent PermissionType in the
* database.
*/
public abstract class ModeledPermissionService<PermissionSetType extends PermissionSet<PermissionType>,
PermissionType extends Permission, ModelType>
extends AbstractPermissionService<PermissionSetType, PermissionType> {
/**
* The environment of the Guacamole server.
*/
@Inject
private JDBCEnvironment environment;
/**
* Returns an instance of a mapper for the type of permission used by this
* service.
*
* @return
* A mapper which provides access to the model objects associated with
* the permissions used by this service.
*/
protected abstract PermissionMapper<ModelType> getPermissionMapper();
/**
* Returns an instance of a permission which is based on the given model
* object.
*
* @param model
* The model object to use to produce the returned permission.
*
* @return
* A permission which is based on the given model object.
*/
protected abstract PermissionType getPermissionInstance(ModelType model);
/**
* Returns a collection of permissions which are based on the models in
* the given collection.
*
* @param models
* The model objects to use to produce the permissions within the
* returned set.
*
* @return
* A set of permissions which are based on the models in the given
* collection.
*/
protected Set<PermissionType> getPermissionInstances(Collection<ModelType> models) {
// Create new collection of permissions by manually converting each model
Set<PermissionType> permissions = new HashSet<PermissionType>(models.size());
for (ModelType model : models)
permissions.add(getPermissionInstance(model));
return permissions;
}
/**
* Returns an instance of a model object which is based on the given
* permission and target entity.
*
* @param targetEntity
* The entity to whom this permission is granted.
*
* @param permission
* The permission to use to produce the returned model object.
*
* @return
* A model object which is based on the given permission and target
* entity.
*/
protected abstract ModelType getModelInstance(
ModeledPermissions<? extends EntityModel> targetEntity,
PermissionType permission);
/**
* Returns a collection of model objects which are based on the given
* permissions and target entity.
*
* @param targetEntity
* The entity to whom this permission is granted.
*
* @param permissions
* The permissions to use to produce the returned model objects.
*
* @return
* A collection of model objects which are based on the given
* permissions and target entity.
*/
protected Collection<ModelType> getModelInstances(
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<PermissionType> permissions) {
// Create new collection of models by manually converting each permission
Collection<ModelType> models = new ArrayList<ModelType>(permissions.size());
for (PermissionType permission : permissions)
models.add(getModelInstance(targetEntity, permission));
return models;
}
/**
* Runs the provided consumer function on subsets of the original collection
* of objects, with each subset being no larger than the maximum batch size
* configured for the JDBC environment. Any permission update that involves
* passing potentially-large lists of models to a mapper should use this
* method to perform the update to ensure that the maximum number of
* parameters for an individual query is not exceeded.
*
* @param <T>
* The type of object stored in the provided objects list, and consumed
* by the provided consumer.
*
* @param objects
* A collection of objects to be partitioned.
*
* @param consumer
* A function that will consume subsets of the objects from the provided
* collection of objects, performing any update as needed.
*
* @throws GuacamoleException
* If the batch size cannot be determined for the JDBC environment.
*/
protected <T> void batchPermissionUpdates(
Collection<T> objects, Consumer<Collection<T>> consumer)
throws GuacamoleException {
// Split the original collection into views, each no larger than the
// configured batch size, and call the collector function with each
Iterables.partition(objects, environment.getBatchSize())
.forEach(batch -> consumer.accept(batch));
}
@Override
public Set<PermissionType> retrievePermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException {
// Retrieve permissions only if allowed
if (canReadPermissions(user, targetEntity))
return getPermissionInstances(getPermissionMapper().select(
targetEntity.getModel(),
effectiveGroups,
getCaseSensitivity()));
// User cannot read this entity's permissions
throw new GuacamoleSecurityException("Permission denied.");
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.permission;
import java.util.Collection;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Mapper for object-related permissions.
*/
public interface ObjectPermissionMapper extends PermissionMapper<ObjectPermissionModel> {
/**
* Retrieve the permission of the given type associated with the given
* entity and object, if it exists. If no such permission exists, null is
* returned.
*
* @param entity
* The entity to retrieve permissions for.
*
* @param type
* The type of permission to return.
*
* @param identifier
* The identifier of the object affected by the permission to return.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the user. If
* no groups are given, only permissions directly granted to the user
* will be used.
*
* @param caseSensitivity
* The configuration of case sensitivity, used to determine whether
* usernames and/or group names will be treated as case-sensitive.
*
* @return
* The requested permission, or null if no such permission is granted
* to the given entity for the given object.
*/
ObjectPermissionModel selectOne(@Param("entity") EntityModel entity,
@Param("type") ObjectPermission.Type type,
@Param("identifier") String identifier,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Retrieves the subset of the given identifiers for which the given entity
* has at least one of the given permissions.
*
* @param entity
* The entity to check permissions of.
*
* @param permissions
* The permissions to check. An identifier will be included in the
* resulting collection if at least one of these permissions is granted
* for the associated object
*
* @param identifiers
* The identifiers of the objects affected by the permissions being
* checked.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the user. If
* no groups are given, only permissions directly granted to the user
* will be used.
*
* @param caseSensitivity
* The configuration of case sensitivity, used to determine whether
* usernames and/or group names will be treated as case-sensitive.
*
* @return
* A collection containing the subset of identifiers for which at least
* one of the specified permissions is granted.
*/
Collection<String> selectAccessibleIdentifiers(@Param("entity") EntityModel entity,
@Param("permissions") Collection<ObjectPermission.Type> permissions,
@Param("identifiers") Collection<String> identifiers,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
}

View File

@@ -0,0 +1,61 @@
/*
* 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.permission;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
/**
* Object representation of an object-related Guacamole permission, as
* represented in the database.
*/
public class ObjectPermissionModel extends PermissionModel<ObjectPermission.Type> {
/**
* The unique identifier of the object affected by this permission.
*/
private String objectIdentifier;
/**
* Creates a new, empty object permission.
*/
public ObjectPermissionModel() {
}
/**
* Returns the unique identifier of the object affected by this permission.
*
* @return
* The unique identifier of the object affected by this permission.
*/
public String getObjectIdentifier() {
return objectIdentifier;
}
/**
* Sets the unique identifier of the object affected by this permission.
*
* @param objectIdentifier
* The unique identifier of the object affected by this permission.
*/
public void setObjectIdentifier(String objectIdentifier) {
this.objectIdentifier = objectIdentifier;
}
}

View File

@@ -0,0 +1,111 @@
/*
* 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.permission;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
/**
* Service which provides convenience methods for creating, retrieving, and
* deleting object permissions. This service will automatically enforce the
* permissions of the current user.
*/
public interface ObjectPermissionService
extends PermissionService<ObjectPermissionSet, ObjectPermission> {
/**
* Returns whether the permission of the given type and associated with the
* given object has been granted to the given entity.
*
* @param user
* The user retrieving the permission.
*
* @param targetEntity
* The entity associated with the permission to be retrieved.
*
* @param type
* The type of permission to retrieve.
*
* @param identifier
* The identifier of the object affected by the permission to return.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the entity.
* If no groups are given, only permissions directly granted to the
* entity will be used.
*
* @return
* true if permission of the given type and associated with the given
* object has been granted to the given entity, false otherwise.
*
* @throws GuacamoleException
* If an error occurs while retrieving the requested permission.
*/
boolean hasPermission(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
ObjectPermission.Type type, String identifier,
Set<String> effectiveGroups) throws GuacamoleException;
/**
* Retrieves the subset of the given identifiers for which the given entity
* has at least one of the given permissions.
*
* @param user
* The user checking the permissions.
*
* @param targetEntity
* The entity to check permissions of.
*
* @param permissions
* The permissions to check. An identifier will be included in the
* resulting collection if at least one of these permissions is granted
* for the associated object
*
* @param identifiers
* The identifiers of the objects affected by the permissions being
* checked.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the entity.
* If no groups are given, only permissions directly granted to the
* entity will be used.
*
* @return
* A collection containing the subset of identifiers for which at least
* one of the specified permissions is granted.
*
* @throws GuacamoleException
* If an error occurs while retrieving permissions.
*/
Collection<String> retrieveAccessibleIdentifiers(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<ObjectPermission.Type> permissions,
Collection<String> identifiers, Set<String> effectiveGroups)
throws GuacamoleException;
}

View File

@@ -0,0 +1,136 @@
/*
* 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.permission;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
import org.apache.guacamole.auth.jdbc.base.RestrictedObject;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
/**
* A database implementation of ObjectPermissionSet which uses an injected
* service to query and manipulate the object-level permissions associated with
* a particular entity.
*/
public abstract class ObjectPermissionSet extends RestrictedObject
implements org.apache.guacamole.net.auth.permission.ObjectPermissionSet {
/**
* The entity associated with this permission set. Each of the permissions
* in this permission set is granted to this entity.
*/
private ModeledPermissions<? extends EntityModel> entity;
/**
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the entity.
*/
private Set<String> effectiveGroups;
/**
* Creates a new ObjectPermissionSet. The resulting permission set
* must still be initialized by a call to init(), or the information
* necessary to read and modify this set will be missing.
*/
public ObjectPermissionSet() {
}
/**
* Initializes this permission set with the current user and the entity
* to whom the permissions in this set are granted.
*
* @param currentUser
* The user who queried this permission set, and whose permissions
* dictate the access level of all operations performed on this set.
*
* @param entity
* The entity to whom the permissions in this set are granted.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the entity.
* If no groups are given, only permissions directly granted to the
* entity will be used.
*/
public void init(ModeledAuthenticatedUser currentUser,
ModeledPermissions<? extends EntityModel> entity,
Set<String> effectiveGroups) {
super.init(currentUser);
this.entity = entity;
this.effectiveGroups = effectiveGroups;
}
/**
* Returns an ObjectPermissionService implementation for manipulating the
* type of permissions contained within this permission set.
*
* @return
* An object permission service for manipulating the type of
* permissions contained within this permission set.
*/
protected abstract ObjectPermissionService getObjectPermissionService();
@Override
public Set<ObjectPermission> getPermissions() throws GuacamoleException {
return getObjectPermissionService().retrievePermissions(getCurrentUser(), entity, effectiveGroups);
}
@Override
public boolean hasPermission(ObjectPermission.Type permission,
String identifier) throws GuacamoleException {
return getObjectPermissionService().hasPermission(getCurrentUser(), entity, permission, identifier, effectiveGroups);
}
@Override
public void addPermission(ObjectPermission.Type permission,
String identifier) throws GuacamoleException {
addPermissions(Collections.singleton(new ObjectPermission(permission, identifier)));
}
@Override
public void removePermission(ObjectPermission.Type permission,
String identifier) throws GuacamoleException {
removePermissions(Collections.singleton(new ObjectPermission(permission, identifier)));
}
@Override
public Collection<String> getAccessibleObjects(Collection<ObjectPermission.Type> permissions,
Collection<String> identifiers) throws GuacamoleException {
return getObjectPermissionService().retrieveAccessibleIdentifiers(getCurrentUser(), entity, permissions, identifiers, effectiveGroups);
}
@Override
public void addPermissions(Set<ObjectPermission> permissions)
throws GuacamoleException {
getObjectPermissionService().createPermissions(getCurrentUser(), entity, permissions);
}
@Override
public void removePermissions(Set<ObjectPermission> permissions)
throws GuacamoleException {
getObjectPermissionService().deletePermissions(getCurrentUser(), entity, permissions);
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.permission;
import java.util.Collection;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Generic base for mappers which handle permissions.
*
* @param <PermissionType>
* The type of permission model object handled by this mapper.
*/
public interface PermissionMapper<PermissionType> {
/**
* Retrieves all permissions associated with the given entity (user or user
* group).
*
* @param entity
* The entity to retrieve permissions for.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the user. If
* no groups are given, only permissions directly granted to the user
* will be used.
*
* @param caseSensitivity
* The case sensitivity configuration for usernames and group names.
*
* @return
* All permissions associated with the given entity.
*/
Collection<PermissionType> select(@Param("entity") EntityModel entity,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Inserts the given permissions into the database. If any permissions
* already exist, they will be ignored.
*
* @param permissions
* The permissions to insert.
*
* @param caseSensitivity
* The case sensitivity configuration for usernames and group names.
*
* @return
* The number of rows inserted.
*/
int insert(@Param("permissions") Collection<PermissionType> permissions,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Deletes the given permissions from the database. If any permissions do
* not exist, they will be ignored.
*
* @param permissions
* The permissions to delete.
*
* @param caseSensitivity
* The case sensitivity configuration for usernames and group names.
*
* @return
* The number of rows deleted.
*/
int delete(@Param("permissions") Collection<PermissionType> permissions,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
}

View File

@@ -0,0 +1,82 @@
/*
* 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.permission;
/**
* Generic base permission model which grants a permission of a particular type
* to a specific entity (user or user group).
*
* @param <PermissionType>
* The type of permissions allowed within this model.
*/
public abstract class PermissionModel<PermissionType> {
/**
* The database ID of the entity to whom this permission is granted.
*/
private Integer entityID;
/**
* The type of action granted by this permission.
*/
private PermissionType type;
/**
* Returns the database ID of the entity to whom this permission is
* granted.
*
* @return
* The database ID of the entity to whom this permission is granted.
*/
public Integer getEntityID() {
return entityID;
}
/**
* Sets the database ID of the entity to whom this permission is granted.
*
* @param entityID
* The database ID of the entity to whom this permission is granted.
*/
public void setEntityID(Integer entityID) {
this.entityID = entityID;
}
/**
* Returns the type of action granted by this permission.
*
* @return
* The type of action granted by this permission.
*/
public PermissionType getType() {
return type;
}
/**
* Sets the type of action granted by this permission.
*
* @param type
* The type of action granted by this permission.
*/
public void setType(PermissionType type) {
this.type = type;
}
}

View File

@@ -0,0 +1,166 @@
/*
* 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.permission;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
import org.apache.guacamole.net.auth.permission.Permission;
import org.apache.guacamole.net.auth.permission.PermissionSet;
import org.apache.guacamole.properties.CaseSensitivity;
/**
* Service which provides convenience methods for creating, retrieving, and
* deleting permissions, and for obtaining the permission sets that contain
* these permissions. This service will automatically enforce the permissions
* of the current user.
*
* @param <PermissionSetType>
* The type of permission sets this service provides access to.
*
* @param <PermissionType>
* The type of permission this service provides access to.
*/
public interface PermissionService<PermissionSetType extends PermissionSet<PermissionType>,
PermissionType extends Permission> {
/**
* Return the current case sensitivity setting, allowing the system to
* determine if usernames and/or group names should be treated as case-
* sensitive.
*
* @return
* The current case sensitivity configuration.
*
* @throws GuacamoleException
* If an error occurs retrieving configuration information related to
* case sensitivity.
*/
default CaseSensitivity getCaseSensitivity() throws GuacamoleException {
// By default identifiers are case-sensitive.
return CaseSensitivity.ENABLED;
}
/**
* Returns a permission set that can be used to retrieve and manipulate the
* permissions of the given entity.
*
* @param user
* The user who will be retrieving or manipulating permissions through
* the returned permission set.
*
* @param targetEntity
* The entity to whom the permissions in the returned permission set are
* granted.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the entity.
* If no groups are given, only permissions directly granted to the
* entity will be used.
*
* @return
* A permission set that contains all permissions associated with the
* given entity, and can be used to manipulate that entity's
* permissions.
*
* @throws GuacamoleException
* If an error occurs while retrieving the permissions of the given
* entity, or if permission to retrieve the permissions of the given
* entity is denied.
*/
PermissionSetType getPermissionSet(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException;
/**
* Retrieves all permissions associated with the given entity.
*
* @param user
* The user retrieving the permissions.
*
* @param targetEntity
* The entity associated with the permissions to be retrieved.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the entity.
* If no groups are given, only permissions directly granted to the
* entity will be used.
*
* @return
* The permissions associated with the given entity.
*
* @throws GuacamoleException
* If an error occurs while retrieving the requested permissions.
*/
Set<PermissionType> retrievePermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException;
/**
* Creates the given permissions within the database. If any permissions
* already exist, they will be ignored.
*
* @param user
* The user creating the permissions.
*
* @param targetEntity
* The entity associated with the permissions to be created.
*
* @param permissions
* The permissions to create.
*
* @throws GuacamoleException
* If the user lacks permission to create the permissions, or an error
* occurs while creating the permissions.
*/
void createPermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<PermissionType> permissions)
throws GuacamoleException;
/**
* Deletes the given permissions. If any permissions do not exist, they
* will be ignored.
*
* @param user
* The user deleting the permissions.
*
* @param targetEntity
* The entity associated with the permissions to be deleted.
*
* @param permissions
* The permissions to delete.
*
* @throws GuacamoleException
* If the user lacks permission to delete the permissions, or an error
* occurs while deleting the permissions.
*/
void deletePermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<PermissionType> permissions)
throws GuacamoleException;
}

View File

@@ -0,0 +1,26 @@
/*
* 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.permission;
/**
* Mapper for sharing profile permissions.
*/
public interface SharingProfilePermissionMapper
extends ObjectPermissionMapper {}

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.permission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
/**
* Service which provides convenience methods for creating, retrieving, and
* deleting sharing profile permissions. This service will automatically enforce
* the permissions of the current user.
*/
public class SharingProfilePermissionService extends ModeledObjectPermissionService {
/**
* Mapper for sharing profile permissions.
*/
@Inject
private SharingProfilePermissionMapper sharingProfilePermissionMapper;
/**
* Provider for sharing profile permission sets.
*/
@Inject
private Provider<SharingProfilePermissionSet> sharingProfilePermissionSetProvider;
@Override
protected ObjectPermissionMapper getPermissionMapper() {
return sharingProfilePermissionMapper;
}
@Override
public ObjectPermissionSet getPermissionSet(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException {
// Create permission set for requested entity
ObjectPermissionSet permissionSet = sharingProfilePermissionSetProvider.get();
permissionSet.init(user, targetEntity, effectiveGroups);
return permissionSet;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.permission;
import com.google.inject.Inject;
/**
* A database implementation of ObjectPermissionSet which uses an injected
* service to query and manipulate the sharing profile permissions associated
* with a particular user.
*/
public class SharingProfilePermissionSet extends ObjectPermissionSet {
/**
* Service for querying and manipulating sharing profile permissions.
*/
@Inject
private SharingProfilePermissionService sharingProfilePermissionService;
@Override
protected ObjectPermissionService getObjectPermissionService() {
return sharingProfilePermissionService;
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.permission;
import java.util.Collection;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.net.auth.permission.SystemPermission;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Mapper for system-level permissions.
*/
public interface SystemPermissionMapper extends PermissionMapper<SystemPermissionModel> {
/**
* Retrieve the permission of the given type associated with the given
* entity, if it exists. If no such permission exists, null is returned.
*
* @param entity
* The entity to retrieve permissions for.
*
* @param type
* The type of permission to return.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the user. If
* no groups are given, only permissions directly granted to the user
* will be used.
*
* @param caseSensitivity
* The case sensitivity configuration, used to determine whether usernames
* and/or group names will be treated as case-sensitive.
*
* @return
* The requested permission, or null if no such permission is granted
* to the given entity.
*/
SystemPermissionModel selectOne(@Param("entity") EntityModel entity,
@Param("type") SystemPermission.Type type,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
}

View File

@@ -0,0 +1,36 @@
/*
* 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.permission;
import org.apache.guacamole.net.auth.permission.SystemPermission;
/**
* Object representation of an system-level Guacamole permission, as
* represented in the database.
*/
public class SystemPermissionModel extends PermissionModel<SystemPermission.Type> {
/**
* Creates a new, empty System permission.
*/
public SystemPermissionModel() {
}
}

View File

@@ -0,0 +1,190 @@
/*
* 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.permission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.GuacamoleUnsupportedException;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.net.auth.permission.SystemPermission;
import org.apache.guacamole.properties.CaseSensitivity;
/**
* Service which provides convenience methods for creating, retrieving, and
* deleting system permissions. This service will automatically enforce
* the permissions of the current user.
*/
public class SystemPermissionService
extends ModeledPermissionService<SystemPermissionSet, SystemPermission, SystemPermissionModel> {
/**
* Mapper for system-level permissions.
*/
@Inject
private SystemPermissionMapper systemPermissionMapper;
/**
* Provider for creating system permission sets.
*/
@Inject
private Provider<SystemPermissionSet> systemPermissionSetProvider;
@Override
protected SystemPermissionMapper getPermissionMapper() {
return systemPermissionMapper;
}
@Override
protected SystemPermission getPermissionInstance(SystemPermissionModel model) {
return new SystemPermission(model.getType());
}
@Override
protected SystemPermissionModel getModelInstance(
final ModeledPermissions<? extends EntityModel> targetEntity,
final SystemPermission permission) {
SystemPermissionModel model = new SystemPermissionModel();
// Populate model object with data from entity and permission
model.setEntityID(targetEntity.getModel().getEntityID());
model.setType(permission.getType());
return model;
}
@Override
public SystemPermissionSet getPermissionSet(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException {
// Create permission set for requested entity
SystemPermissionSet permissionSet = systemPermissionSetProvider.get();
permissionSet.init(user, targetEntity, effectiveGroups);
return permissionSet;
}
@Override
public void createPermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<SystemPermission> permissions) throws GuacamoleException {
// Only privileged users (such as system administrators) can create
// system permissions
if (user.isPrivileged()) {
// Pull identifier case sensitivity
CaseSensitivity caseSensitivity = getCaseSensitivity();
batchPermissionUpdates(permissions, permissionSubset -> {
Collection<SystemPermissionModel> models = getModelInstances(
targetEntity, permissionSubset);
systemPermissionMapper.insert(models, caseSensitivity);
});
return;
}
// User lacks permission to create system permissions
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public void deletePermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<SystemPermission> permissions) throws GuacamoleException {
// Only privileged users (such as system administrators) can delete
// system permissions
if (user.isPrivileged()) {
// Do not allow users to remove their own admin powers
if (user.getUser().getIdentifier().equals(targetEntity.getIdentifier()))
throw new GuacamoleUnsupportedException("Removing your own administrative permissions is not allowed.");
// Pull case sensitivity
CaseSensitivity caseSensitivity = getCaseSensitivity();
batchPermissionUpdates(permissions, permissionSubset -> {
Collection<SystemPermissionModel> models = getModelInstances(
targetEntity, permissionSubset);
systemPermissionMapper.delete(models, caseSensitivity);
});
return;
}
// User lacks permission to delete system permissions
throw new GuacamoleSecurityException("Permission denied.");
}
/**
* Retrieves whether the permission of the given type has been granted to
* the given entity. Permission inheritance through group membership is
* taken into account.
*
* @param user
* The user retrieving the permission.
*
* @param targetEntity
* The entity associated with the permission to be retrieved.
*
* @param type
* The type of permission to retrieve.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the entity.
* If no groups are given, only permissions directly granted to the
* entity will be used.
*
* @return
* true if permission of the given type has been granted to the given
* entity, false otherwise.
*
* @throws GuacamoleException
* If an error occurs while retrieving the requested permission.
*/
public boolean hasPermission(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
SystemPermission.Type type, Set<String> effectiveGroups)
throws GuacamoleException {
// Retrieve permissions only if allowed
if (canReadPermissions(user, targetEntity))
return getPermissionMapper().selectOne(targetEntity.getModel(), type, effectiveGroups, getCaseSensitivity()) != null;
// User cannot read this entity's permissions
throw new GuacamoleSecurityException("Permission denied.");
}
}

View File

@@ -0,0 +1,126 @@
/*
* 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.permission;
import com.google.inject.Inject;
import java.util.Collections;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
import org.apache.guacamole.auth.jdbc.base.RestrictedObject;
import org.apache.guacamole.net.auth.permission.SystemPermission;
/**
* A database implementation of SystemPermissionSet which uses an injected
* service to query and manipulate the system permissions associated with a
* particular entity.
*/
public class SystemPermissionSet extends RestrictedObject
implements org.apache.guacamole.net.auth.permission.SystemPermissionSet {
/**
* The entity associated with this permission set. Each of the permissions
* in this permission set is granted to this entity.
*/
private ModeledPermissions<? extends EntityModel> entity;
/**
* The identifiers of all groups that should be taken into account when
* determining the permissions effectively granted to the entity.
*/
private Set<String> effectiveGroups;
/**
* Service for reading and manipulating system permissions.
*/
@Inject
private SystemPermissionService systemPermissionService;
/**
* Creates a new SystemPermissionSet. The resulting permission set
* must still be initialized by a call to init(), or the information
* necessary to read and modify this set will be missing.
*/
public SystemPermissionSet() {
}
/**
* Initializes this permission set with the current user and the entity
* to whom the permissions in this set are granted.
*
* @param currentUser
* The user who queried this permission set, and whose permissions
* dictate the access level of all operations performed on this set.
*
* @param entity
* The entity to whom the permissions in this set are granted.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the entity.
* If no groups are given, only permissions directly granted to the
* entity will be used.
*/
public void init(ModeledAuthenticatedUser currentUser,
ModeledPermissions<? extends EntityModel> entity,
Set<String> effectiveGroups) {
super.init(currentUser);
this.entity = entity;
this.effectiveGroups = effectiveGroups;
}
@Override
public Set<SystemPermission> getPermissions() throws GuacamoleException {
return systemPermissionService.retrievePermissions(getCurrentUser(), entity, effectiveGroups);
}
@Override
public boolean hasPermission(SystemPermission.Type permission)
throws GuacamoleException {
return systemPermissionService.hasPermission(getCurrentUser(), entity, permission, effectiveGroups);
}
@Override
public void addPermission(SystemPermission.Type permission)
throws GuacamoleException {
addPermissions(Collections.singleton(new SystemPermission(permission)));
}
@Override
public void removePermission(SystemPermission.Type permission)
throws GuacamoleException {
removePermissions(Collections.singleton(new SystemPermission(permission)));
}
@Override
public void addPermissions(Set<SystemPermission> permissions)
throws GuacamoleException {
systemPermissionService.createPermissions(getCurrentUser(), entity, permissions);
}
@Override
public void removePermissions(Set<SystemPermission> permissions)
throws GuacamoleException {
systemPermissionService.deletePermissions(getCurrentUser(), entity, permissions);
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.permission;
/**
* Mapper for user group permissions.
*/
public interface UserGroupPermissionMapper extends ObjectPermissionMapper {}

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.permission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
/**
* Service which provides convenience methods for creating, retrieving, and
* deleting user group permissions. This service will automatically enforce the
* permissions of the current user.
*/
public class UserGroupPermissionService extends ModeledObjectPermissionService {
/**
* Mapper for user group permissions.
*/
@Inject
private UserGroupPermissionMapper userGroupPermissionMapper;
/**
* Provider for user group permission sets.
*/
@Inject
private Provider<UserGroupPermissionSet> userGroupPermissionSetProvider;
@Override
protected ObjectPermissionMapper getPermissionMapper() {
return userGroupPermissionMapper;
}
@Override
public ObjectPermissionSet getPermissionSet(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException {
// Create permission set for requested entity
ObjectPermissionSet permissionSet = userGroupPermissionSetProvider.get();
permissionSet.init(user, targetEntity, effectiveGroups);
return permissionSet;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.permission;
import com.google.inject.Inject;
/**
* A database implementation of ObjectPermissionSet which uses an injected
* service to query and manipulate the user group permissions associated with a
* particular user.
*/
public class UserGroupPermissionSet extends ObjectPermissionSet {
/**
* Service for querying and manipulating user group permissions.
*/
@Inject
private UserGroupPermissionService userGroupPermissionService;
@Override
protected ObjectPermissionService getObjectPermissionService() {
return userGroupPermissionService;
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.permission;
/**
* Mapper for user permissions.
*/
public interface UserPermissionMapper extends ObjectPermissionMapper {}

View File

@@ -0,0 +1,80 @@
/*
* 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.permission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
import org.apache.guacamole.properties.CaseSensitivity;
/**
* Service which provides convenience methods for creating, retrieving, and
* deleting user permissions. This service will automatically enforce the
* permissions of the current user.
*/
public class UserPermissionService extends ModeledObjectPermissionService {
/**
* Mapper for user permissions.
*/
@Inject
private UserPermissionMapper userPermissionMapper;
/**
* Provider for user permission sets.
*/
@Inject
private Provider<UserPermissionSet> userPermissionSetProvider;
/**
* The server environment for retrieving configuration data.
*/
@Inject
private JDBCEnvironment environment;
@Override
public CaseSensitivity getCaseSensitivity() throws GuacamoleException {
return environment.getCaseSensitivity();
}
@Override
protected ObjectPermissionMapper getPermissionMapper() {
return userPermissionMapper;
}
@Override
public ObjectPermissionSet getPermissionSet(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException {
// Create permission set for requested entity
ObjectPermissionSet permissionSet = userPermissionSetProvider.get();
permissionSet.init(user, targetEntity, effectiveGroups);
return permissionSet;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.permission;
import com.google.inject.Inject;
/**
* A database implementation of ObjectPermissionSet which uses an injected
* service to query and manipulate the user permissions associated with a
* particular user.
*/
public class UserPermissionSet extends ObjectPermissionSet {
/**
* Service for querying and manipulating user permissions.
*/
@Inject
private UserPermissionService userPermissionService;
@Override
protected ObjectPermissionService getObjectPermissionService() {
return userPermissionService;
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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.
*/
/**
* Classes related to object- and system-level permissions.
*/
package org.apache.guacamole.auth.jdbc.permission;

View File

@@ -0,0 +1,40 @@
/*
* 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.security;
/**
* Thrown when an attempt is made to set a user's password to a string which
* contains their own username, in violation of the defined password policy.
*/
public class PasswordContainsUsernameException extends PasswordPolicyException {
/**
* Creates a new PasswordContainsUsernameException with the given
* human-readable message. The translatable message is already defined.
*
* @param message
* A human-readable message describing the password policy violation
* that occurred.
*/
public PasswordContainsUsernameException(String message) {
super(message, "PASSWORD_POLICY.ERROR_CONTAINS_USERNAME");
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.security;
/**
* A service to perform password encryption and checking.
*/
public interface PasswordEncryptionService {
/**
* Creates a password hash based on the provided username, password, and
* salt. If the provided salt is null, only the password itself is hashed.
*
* @param password
* The password to hash.
*
* @param salt
* The salt to use when hashing the password, if any.
*
* @return
* The generated password hash.
*/
public byte[] createPasswordHash(String password, byte[] salt);
}

Some files were not shown because too many files have changed in this diff Show More