| /* |
| * 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.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.auth.jdbc.base.ActivityRecordSearchTerm; |
| import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate; |
| import org.apache.guacamole.auth.jdbc.base.ModeledChildDirectoryObjectService; |
| import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionMapper; |
| import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionMapper; |
| 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; |
| |
| /** |
| * Service for creating and tracking tunnels. |
| */ |
| @Inject |
| private GuacamoleTunnelService tunnelService; |
| |
| @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().getSystemPermissions(); |
| return permissionSet.hasPermission(SystemPermission.Type.CREATE_CONNECTION); |
| |
| } |
| |
| @Override |
| protected ObjectPermissionSet getPermissionSet(ModeledAuthenticatedUser user) |
| throws GuacamoleException { |
| |
| // Return permissions related to connections |
| return user.getUser().getConnectionPermissions(); |
| |
| } |
| |
| @Override |
| protected ObjectPermissionSet getParentPermissionSet(ModeledAuthenticatedUser user) |
| throws GuacamoleException { |
| |
| // Connections are contained by connection groups |
| return user.getUser().getConnectionGroupPermissions(); |
| |
| } |
| |
| @Override |
| protected void beforeCreate(ModeledAuthenticatedUser user, |
| Connection object, ConnectionModel 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 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 { |
| |
| 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<ConnectionParameterModel>(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 a system admin |
| if (user.getUser().isAdministrator()) |
| return connectionMapper.selectIdentifiersWithin(identifier); |
| |
| // Otherwise only return explicitly readable identifiers |
| else |
| return connectionMapper.selectReadableIdentifiersWithin(user.getUser().getModel(), identifier); |
| |
| } |
| |
| /** |
| * 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<String, String>(); |
| |
| // 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<ConnectionRecord>(models.size()); |
| for (ConnectionRecordModel model : models) |
| objects.add(getObjectInstance(model)); |
| |
| return objects; |
| |
| } |
| |
| /** |
| * Retrieves the connection history of the given connection, including any |
| * active connections. |
| * |
| * @param user |
| * The user retrieving the connection history. |
| * |
| * @param connection |
| * The connection whose history is being retrieved. |
| * |
| * @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(ModeledAuthenticatedUser user, |
| ModeledConnection connection) throws GuacamoleException { |
| |
| String identifier = connection.getIdentifier(); |
| |
| // Retrieve history only if READ permission is granted |
| if (hasObjectPermission(user, identifier, ObjectPermission.Type.READ)) { |
| |
| // Retrieve history |
| List<ConnectionRecordModel> models = connectionRecordMapper.select(identifier); |
| |
| // Get currently-active connections |
| List<ConnectionRecord> records = new ArrayList<ConnectionRecord>(tunnelService.getActiveConnections(connection)); |
| Collections.reverse(records); |
| |
| // Add past connections from model objects |
| for (ConnectionRecordModel model : models) |
| records.add(getObjectInstance(model)); |
| |
| // Return converted history list |
| return records; |
| |
| } |
| |
| // The user does not have permission to read the history |
| throw new GuacamoleSecurityException("Permission denied."); |
| |
| } |
| |
| /** |
| * 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 user |
| * The user retrieving the connection history. |
| * |
| * @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(ModeledAuthenticatedUser user, |
| Collection<ActivityRecordSearchTerm> requiredContents, |
| List<ActivityRecordSortPredicate> sortPredicates, int limit) |
| throws GuacamoleException { |
| |
| List<ConnectionRecordModel> searchResults; |
| |
| // Bypass permission checks if the user is a system admin |
| if (user.getUser().isAdministrator()) |
| searchResults = connectionRecordMapper.search(requiredContents, |
| sortPredicates, limit); |
| |
| // Otherwise only return explicitly readable history records |
| else |
| searchResults = connectionRecordMapper.searchReadable(user.getUser().getModel(), |
| requiredContents, sortPredicates, limit); |
| |
| 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. |
| * |
| * @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) |
| throws GuacamoleException { |
| |
| // Connect only if READ permission is granted |
| if (hasObjectPermission(user, connection.getIdentifier(), ObjectPermission.Type.READ)) |
| return tunnelService.getGuacamoleTunnel(user, connection, info); |
| |
| // The user does not have permission to connect |
| throw new GuacamoleSecurityException("Permission denied."); |
| |
| } |
| |
| } |