GUACAMOLE-5: Expose sharing profiles via a Directory on the UserContext according to granted permissions.

This commit is contained in:
Michael Jumper
2016-07-19 16:11:45 -07:00
parent 53a856b285
commit a03b76d9dd
7 changed files with 655 additions and 4 deletions

View File

@@ -60,8 +60,13 @@ import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionService;
import org.apache.guacamole.auth.jdbc.activeconnection.TrackedActiveConnection;
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.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.net.auth.AuthenticationProvider;
import org.mybatis.guice.MyBatisModule;
@@ -141,8 +146,11 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule {
bind(ModeledConnection.class);
bind(ModeledConnectionGroup.class);
bind(ModeledGuacamoleConfiguration.class);
bind(ModeledSharingProfile.class);
bind(ModeledUser.class);
bind(RootConnectionGroup.class);
bind(SharingProfileDirectory.class);
bind(SharingProfilePermissionSet.class);
bind(SystemPermissionSet.class);
bind(TrackedActiveConnection.class);
bind(UserContext.class);
@@ -159,6 +167,8 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule {
bind(GuacamoleTunnelService.class).to(RestrictedGuacamoleTunnelService.class);
bind(PasswordEncryptionService.class).to(SHA256PasswordEncryptionService.class);
bind(SaltService.class).to(SecureRandomSaltService.class);
bind(SharingProfilePermissionService.class);
bind(SharingProfileService.class);
bind(SystemPermissionService.class);
bind(UserPermissionService.class);
bind(UserService.class);

View File

@@ -0,0 +1,66 @@
/*
* 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 org.apache.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.user.ModeledUser;
/**
* Service which provides convenience methods for creating, retrieving, and
* deleting sharing profile permissions. This service will automatically enforce
* the permissions of the current user.
*
* @author Michael Jumper
*/
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(AuthenticatedUser user,
ModeledUser targetUser) throws GuacamoleException {
// Create permission set for requested user
ObjectPermissionSet permissionSet = sharingProfilePermissionSetProvider.get();
permissionSet.init(user, targetUser);
return permissionSet;
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.
*
* @author Michael Jumper
*/
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,110 @@
/*
* 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.sharingprofile;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObject;
import org.apache.guacamole.form.Form;
import org.apache.guacamole.net.auth.SharingProfile;
/**
* An implementation of the SharingProfile object which is backed by a database
* model.
*
* @author Michael Jumper
*/
public class ModeledSharingProfile
extends ModeledDirectoryObject<SharingProfileModel>
implements SharingProfile {
/**
* All possible attributes of sharing profile objects organized as
* individual, logical forms. Currently, there are no such attributes.
*/
public static final Collection<Form> ATTRIBUTES = Collections.<Form>emptyList();
/**
* The manually-set parameter map, if any.
*/
private Map<String, String> parameters = null;
/**
* Service for managing sharing profiles.
*/
@Inject
private SharingProfileService sharingProfileService;
/**
* Creates a new, empty ModeledSharingProfile.
*/
public ModeledSharingProfile() {
}
@Override
public String getName() {
return getModel().getName();
}
@Override
public void setName(String name) {
getModel().setName(name);
}
@Override
public String getPrimaryConnectionIdentifier() {
return getModel().getPrimaryConnectionIdentifier();
}
@Override
public void setPrimaryConnectionIdentifier(String identifier) {
getModel().setPrimaryConnectionIdentifier(identifier);
}
@Override
public Map<String, String> getParameters() {
// Retrieve visible parameters, if not overridden by setParameters()
if (parameters == null)
return sharingProfileService.retrieveParameters(getCurrentUser(),
getModel().getIdentifier());
return parameters;
}
@Override
public void setParameters(Map<String, String> parameters) {
this.parameters = parameters;
}
@Override
public Map<String, String> getAttributes() {
return Collections.<String, String>emptyMap();
}
@Override
public void setAttributes(Map<String, String> attributes) {
// Do nothing - no attributes
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.sharingprofile;
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.RestrictedObject;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.SharingProfile;
import org.mybatis.guice.transactional.Transactional;
/**
* Implementation of the SharingProfile Directory which is driven by an
* underlying, arbitrary database.
*
* @author Michael Jumper
*/
public class SharingProfileDirectory extends RestrictedObject
implements Directory<SharingProfile> {
/**
* Service for managing sharing profile objects.
*/
@Inject
private SharingProfileService sharingProfileService;
@Override
public SharingProfile get(String identifier) throws GuacamoleException {
return sharingProfileService.retrieveObject(getCurrentUser(), identifier);
}
@Override
@Transactional
public Collection<SharingProfile> getAll(Collection<String> identifiers) throws GuacamoleException {
return Collections.<SharingProfile>unmodifiableCollection(
sharingProfileService.retrieveObjects(getCurrentUser(), identifiers)
);
}
@Override
@Transactional
public Set<String> getIdentifiers() throws GuacamoleException {
return sharingProfileService.getIdentifiers(getCurrentUser());
}
@Override
@Transactional
public void add(SharingProfile object) throws GuacamoleException {
sharingProfileService.createObject(getCurrentUser(), object);
}
@Override
@Transactional
public void update(SharingProfile object) throws GuacamoleException {
ModeledSharingProfile sharingProfile = (ModeledSharingProfile) object;
sharingProfileService.updateObject(getCurrentUser(), sharingProfile);
}
@Override
@Transactional
public void remove(String identifier) throws GuacamoleException {
sharingProfileService.deleteObject(getCurrentUser(), identifier);
}
}

View File

@@ -0,0 +1,328 @@
/*
* 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.sharingprofile;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectService;
import org.apache.guacamole.auth.jdbc.permission.SharingProfilePermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionMapper;
import org.apache.guacamole.net.auth.SharingProfile;
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;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating sharing profiles.
*
* @author Michael Jumper
*/
public class SharingProfileService
extends ModeledDirectoryObjectService<ModeledSharingProfile,
SharingProfile, SharingProfileModel> {
/**
* Mapper for accessing sharing profiles.
*/
@Inject
private SharingProfileMapper sharingProfileMapper;
/**
* Mapper for manipulating sharing profile permissions.
*/
@Inject
private SharingProfilePermissionMapper sharingProfilePermissionMapper;
/**
* Mapper for accessing sharing profile parameters.
*/
@Inject
private SharingProfileParameterMapper parameterMapper;
/**
* Provider for creating sharing profiles.
*/
@Inject
private Provider<ModeledSharingProfile> sharingProfileProvider;
@Override
protected ModeledDirectoryObjectMapper<SharingProfileModel> getObjectMapper() {
return sharingProfileMapper;
}
@Override
protected ObjectPermissionMapper getPermissionMapper() {
return sharingProfilePermissionMapper;
}
@Override
protected ModeledSharingProfile getObjectInstance(AuthenticatedUser currentUser,
SharingProfileModel model) {
ModeledSharingProfile sharingProfile = sharingProfileProvider.get();
sharingProfile.init(currentUser, model);
return sharingProfile;
}
@Override
protected SharingProfileModel getModelInstance(AuthenticatedUser currentUser,
final SharingProfile object) {
// Create new ModeledSharingProfile backed by blank model
SharingProfileModel model = new SharingProfileModel();
ModeledSharingProfile sharingProfile = getObjectInstance(currentUser, model);
// Set model contents through ModeledSharingProfile, copying the
// provided sharing profile
sharingProfile.setPrimaryConnectionIdentifier(object.getPrimaryConnectionIdentifier());
sharingProfile.setName(object.getName());
sharingProfile.setParameters(object.getParameters());
sharingProfile.setAttributes(object.getAttributes());
return model;
}
@Override
protected boolean hasCreatePermission(AuthenticatedUser user)
throws GuacamoleException {
// Return whether user has explicit sharing profile creation permission
SystemPermissionSet permissionSet = user.getUser().getSystemPermissions();
return permissionSet.hasPermission(SystemPermission.Type.CREATE_SHARING_PROFILE);
}
@Override
protected ObjectPermissionSet getPermissionSet(AuthenticatedUser user)
throws GuacamoleException {
// Return permissions related to sharing profiles
return user.getUser().getSharingProfilePermissions();
}
@Override
protected void beforeCreate(AuthenticatedUser user,
SharingProfileModel model) throws GuacamoleException {
super.beforeCreate(user, model);
// Name must not be blank
if (model.getName() == null || model.getName().trim().isEmpty())
throw new GuacamoleClientException("Sharing profile names must not be blank.");
// Do not attempt to create duplicate sharing profiles
SharingProfileModel existing = sharingProfileMapper.selectOneByName(model.getPrimaryConnectionIdentifier(), model.getName());
if (existing != null)
throw new GuacamoleClientException("The sharing profile \"" + model.getName() + "\" already exists.");
}
@Override
protected void beforeUpdate(AuthenticatedUser user,
SharingProfileModel model) throws GuacamoleException {
super.beforeUpdate(user, model);
// Name must not be blank
if (model.getName() == null || model.getName().trim().isEmpty())
throw new GuacamoleClientException("Sharing profile names must not be blank.");
// Check whether such a sharing profile is already present
SharingProfileModel existing = sharingProfileMapper.selectOneByName(model.getPrimaryConnectionIdentifier(), model.getName());
if (existing != null) {
// If the specified name matches a DIFFERENT existing sharing profile, the update cannot continue
if (!existing.getObjectID().equals(model.getObjectID()))
throw new GuacamoleClientException("The sharing profile \"" + model.getName() + "\" already exists.");
}
}
/**
* Given an arbitrary Guacamole sharing profile, produces a collection of
* parameter model objects containing the name/value pairs of that
* sharing profile's parameters.
*
* @param sharingProfile
* The sharing profile 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 sharing profile's parameters.
*/
private Collection<SharingProfileParameterModel> getParameterModels(ModeledSharingProfile sharingProfile) {
Map<String, String> parameters = sharingProfile.getParameters();
// Convert parameters to model objects
Collection<SharingProfileParameterModel> parameterModels = new ArrayList<SharingProfileParameterModel>(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
SharingProfileParameterModel model = new SharingProfileParameterModel();
model.setSharingProfileIdentifier(sharingProfile.getIdentifier());
model.setName(name);
model.setValue(value);
// Add model to list
parameterModels.add(model);
}
return parameterModels;
}
@Override
public ModeledSharingProfile createObject(AuthenticatedUser user, SharingProfile object)
throws GuacamoleException {
// Create sharing profile
ModeledSharingProfile sharingProfile = super.createObject(user, object);
sharingProfile.setParameters(object.getParameters());
// Insert new parameters, if any
Collection<SharingProfileParameterModel> parameterModels = getParameterModels(sharingProfile);
if (!parameterModels.isEmpty())
parameterMapper.insert(parameterModels);
return sharingProfile;
}
@Override
public void updateObject(AuthenticatedUser user, ModeledSharingProfile object)
throws GuacamoleException {
// Update sharing profile
super.updateObject(user, object);
// Replace existing parameters with new parameters, if any
Collection<SharingProfileParameterModel> parameterModels = getParameterModels(object);
parameterMapper.delete(object.getIdentifier());
if (!parameterModels.isEmpty())
parameterMapper.insert(parameterModels);
}
/**
* Returns the set of all identifiers for all sharing profiles associated
* with the given primary connection. Only sharing profiles that the user
* has read access to will be returned.
*
* Permission to read the primary connection having the given identifier is
* NOT checked.
*
* @param user
* The user retrieving the identifiers.
*
* @param identifier
* The identifier of the primary connection.
*
* @return
* The set of all identifiers for all sharing profiles associated with
* the primary connection having the given identifier that the user has
* read access to.
*
* @throws GuacamoleException
* If an error occurs while reading identifiers.
*/
public Set<String> getIdentifiersWithin(AuthenticatedUser user,
String identifier)
throws GuacamoleException {
// Bypass permission checks if the user is a system admin
if (user.getUser().isAdministrator())
return sharingProfileMapper.selectIdentifiersWithin(identifier);
// Otherwise only return explicitly readable identifiers
else
return sharingProfileMapper.selectReadableIdentifiersWithin(
user.getUser().getModel(), identifier);
}
/**
* Retrieves all parameters visible to the given user and associated with
* the sharing profile having the given identifier. If the given user has no
* access to such parameters, or no such sharing profile exists, the
* returned map will be empty.
*
* @param user
* The user retrieving sharing profile parameters.
*
* @param identifier
* The identifier of the sharing profile 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(AuthenticatedUser 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 (SharingProfileParameterModel parameter : parameterMapper.select(identifier))
parameterMap.put(parameter.getName(), parameter.getValue());
}
return parameterMap;
}
}

View File

@@ -26,13 +26,14 @@ import org.apache.guacamole.auth.jdbc.connection.ConnectionDirectory;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Collection;
import java.util.Collections;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.RestrictedObject;
import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionDirectory;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordSet;
import org.apache.guacamole.auth.jdbc.connection.ModeledConnection;
import org.apache.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile;
import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileDirectory;
import org.apache.guacamole.form.Form;
import org.apache.guacamole.net.auth.ActiveConnection;
import org.apache.guacamole.net.auth.AuthenticationProvider;
@@ -41,7 +42,6 @@ import org.apache.guacamole.net.auth.ConnectionGroup;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.SharingProfile;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.net.auth.simple.SimpleDirectory;
/**
* UserContext implementation which is driven by an arbitrary, underlying
@@ -80,6 +80,13 @@ public class UserContext extends RestrictedObject
@Inject
private ConnectionGroupDirectory connectionGroupDirectory;
/**
* Sharing profile directory restricted by the permissions of the user
* associated with this context.
*/
@Inject
private SharingProfileDirectory sharingProfileDirectory;
/**
* ActiveConnection directory restricted by the permissions of the user
* associated with this context.
@@ -108,6 +115,7 @@ public class UserContext extends RestrictedObject
userDirectory.init(currentUser);
connectionDirectory.init(currentUser);
connectionGroupDirectory.init(currentUser);
sharingProfileDirectory.init(currentUser);
activeConnectionDirectory.init(currentUser);
}
@@ -140,7 +148,7 @@ public class UserContext extends RestrictedObject
@Override
public Directory<SharingProfile> getSharingProfileDirectory()
throws GuacamoleException {
return new SimpleDirectory<SharingProfile>();
return sharingProfileDirectory;
}
@Override
@@ -184,7 +192,7 @@ public class UserContext extends RestrictedObject
@Override
public Collection<Form> getSharingProfileAttributes() {
return Collections.<Form>emptyList();
return ModeledSharingProfile.ATTRIBUTES;
}
}