GUACAMOLE-5: Replace UserRESTService with new resource-driven implementation.

This commit is contained in:
Michael Jumper
2016-07-12 13:01:42 -07:00
parent 61d581ef0d
commit 374ad3eb23
9 changed files with 592 additions and 651 deletions

View File

@@ -41,7 +41,7 @@ import org.apache.guacamole.rest.language.LanguageRESTService;
import org.apache.guacamole.rest.patch.PatchRESTService; import org.apache.guacamole.rest.patch.PatchRESTService;
import org.apache.guacamole.rest.schema.SchemaRESTService; import org.apache.guacamole.rest.schema.SchemaRESTService;
import org.apache.guacamole.rest.tunnel.TunnelRESTService; import org.apache.guacamole.rest.tunnel.TunnelRESTService;
import org.apache.guacamole.rest.user.UserRESTService; import org.apache.guacamole.rest.user.UserModule;
/** /**
* A Guice Module to set up the servlet mappings and authentication-specific * A Guice Module to set up the servlet mappings and authentication-specific
@@ -94,7 +94,6 @@ public class RESTServiceModule extends ServletModule {
bind(SchemaRESTService.class); bind(SchemaRESTService.class);
bind(TokenRESTService.class); bind(TokenRESTService.class);
bind(TunnelRESTService.class); bind(TunnelRESTService.class);
bind(UserRESTService.class);
// Root-level resources // Root-level resources
bind(SessionResource.class); bind(SessionResource.class);
@@ -104,6 +103,7 @@ public class RESTServiceModule extends ServletModule {
install(new ActiveConnectionModule()); install(new ActiveConnectionModule());
install(new ConnectionModule()); install(new ConnectionModule());
install(new ConnectionGroupModule()); install(new ConnectionGroupModule());
install(new UserModule());
// Set up the servlet and JSON mappings // Set up the servlet and JSON mappings
bind(GuiceContainer.class); bind(GuiceContainer.class);

View File

@@ -32,10 +32,12 @@ import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.ActiveConnection; import org.apache.guacamole.net.auth.ActiveConnection;
import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.ConnectionGroup; import org.apache.guacamole.net.auth.ConnectionGroup;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.net.auth.UserContext; import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.rest.activeconnection.APIActiveConnection; import org.apache.guacamole.rest.activeconnection.APIActiveConnection;
import org.apache.guacamole.rest.connection.APIConnection; import org.apache.guacamole.rest.connection.APIConnection;
import org.apache.guacamole.rest.connectiongroup.APIConnectionGroup; import org.apache.guacamole.rest.connectiongroup.APIConnectionGroup;
import org.apache.guacamole.rest.user.APIUser;
/** /**
* A REST resource which exposes the contents of a particular UserContext. * A REST resource which exposes the contents of a particular UserContext.
@@ -75,6 +77,13 @@ public class UserContextResource {
private DirectoryResourceFactory<ConnectionGroup, APIConnectionGroup> private DirectoryResourceFactory<ConnectionGroup, APIConnectionGroup>
connectionGroupDirectoryResourceFactory; connectionGroupDirectoryResourceFactory;
/**
* Factory for creating DirectoryResources which expose a given
* User Directory.
*/
@Inject
private DirectoryResourceFactory<User, APIUser> userDirectoryResourceFactory;
/** /**
* Creates a new UserContextResource which exposes the data within the * Creates a new UserContextResource which exposes the data within the
* given UserContext. * given UserContext.
@@ -142,4 +151,22 @@ public class UserContextResource {
userContext.getConnectionGroupDirectory()); userContext.getConnectionGroupDirectory());
} }
/**
* Returns a new resource which represents the User Directory contained
* within the UserContext exposed by this UserContextResource.
*
* @return
* A new resource which represents the User Directory contained within
* the UserContext exposed by this UserContextResource.
*
* @throws GuacamoleException
* If an error occurs while retrieving the User Directory.
*/
@Path("users")
public DirectoryResource<User, APIUser> getUserDirectoryResource()
throws GuacamoleException {
return userDirectoryResourceFactory.create(userContext,
userContext.getUserDirectory());
}
} }

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.rest.user;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.rest.directory.DirectoryObjectResource;
import org.apache.guacamole.rest.directory.DirectoryObjectResourceFactory;
import org.apache.guacamole.rest.directory.DirectoryObjectTranslator;
import org.apache.guacamole.rest.directory.DirectoryResource;
/**
* A REST resource which abstracts the operations available on a Directory of
* Users.
*
* @author Michael Jumper
*/
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserDirectoryResource extends DirectoryResource<User, APIUser> {
/**
* The UserContext associated with the Directory which contains the
* User exposed by this resource.
*/
private final UserContext userContext;
/**
* The Directory exposed by this resource.
*/
private final Directory<User> directory;
/**
* A factory which can be used to create instances of resources representing
* Users.
*/
private final DirectoryObjectResourceFactory<User, APIUser> resourceFactory;
/**
* Creates a new UserDirectoryResource which exposes the operations and
* subresources available for the given User Directory.
*
* @param userContext
* The UserContext associated with the given Directory.
*
* @param directory
* The Directory being exposed.
*
* @param translator
* A DirectoryObjectTranslator implementation which handles
* Users.
*
* @param resourceFactory
* A factory which can be used to create instances of resources
* representing Users.
*/
@AssistedInject
public UserDirectoryResource(@Assisted UserContext userContext,
@Assisted Directory<User> directory,
DirectoryObjectTranslator<User, APIUser> translator,
DirectoryObjectResourceFactory<User, APIUser> resourceFactory) {
super(userContext, directory, translator, resourceFactory);
this.userContext = userContext;
this.directory = directory;
this.resourceFactory = resourceFactory;
}
@Override
public DirectoryObjectResource<User, APIUser>
getObjectResource(String identifier) throws GuacamoleException {
// If username is own username, just use self - might not have query permissions
if (userContext.self().getIdentifier().equals(identifier))
return resourceFactory.create(userContext, directory, userContext.self());
return super.getObjectResource(identifier);
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.rest.user;
import com.google.inject.AbstractModule;
import org.apache.guacamole.rest.directory.DirectoryObjectResourceFactory;
import org.apache.guacamole.rest.directory.DirectoryResourceFactory;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.rest.directory.DirectoryObjectResource;
import org.apache.guacamole.rest.directory.DirectoryObjectTranslator;
import org.apache.guacamole.rest.directory.DirectoryResource;
/**
* Guice Module which configures injections required for handling User resources
* via the REST API.
*
* @author Michael Jumper
*/
public class UserModule extends AbstractModule {
@Override
protected void configure() {
// Create the required DirectoryResourceFactory implementation
install(new FactoryModuleBuilder()
.implement(
new TypeLiteral<DirectoryResource<User, APIUser>>() {},
UserDirectoryResource.class
)
.build(new TypeLiteral<DirectoryResourceFactory<User, APIUser>>() {}));
// Create the required DirectoryObjectResourceFactory implementation
install(new FactoryModuleBuilder()
.implement(
new TypeLiteral<DirectoryObjectResource<User, APIUser>>() {},
UserResource.class
)
.build(new TypeLiteral<DirectoryObjectResourceFactory<User, APIUser>>() {}));
// Bind translator for converting between User and APIUser
bind(new TypeLiteral<DirectoryObjectTranslator<User, APIUser>>() {})
.to(UserObjectTranslator.class);
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.rest.user;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.rest.directory.DirectoryObjectTranslator;
/**
* Translator which converts between User objects and APIUser objects.
*
* @author Michael Jumper
*/
public class UserObjectTranslator
implements DirectoryObjectTranslator<User, APIUser> {
@Override
public APIUser toExternalObject(User object)
throws GuacamoleException {
return new APIUser(object);
}
@Override
public User toInternalObject(APIUser object)
throws GuacamoleException {
return new APIUserWrapper(object);
}
@Override
public void applyExternalChanges(User existingObject,
APIUser object) throws GuacamoleException {
// Do not update the user password if no password was provided
if (object.getPassword() != null)
existingObject.setPassword(object.getPassword());
// Update user attributes
existingObject.setAttributes(object.getAttributes());
}
}

View File

@@ -1,644 +0,0 @@
/*
* 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.rest.user;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceNotFoundException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
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.SystemPermission;
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
import org.apache.guacamole.GuacamoleSession;
import org.apache.guacamole.rest.APIPatch;
import static org.apache.guacamole.rest.APIPatch.Operation.add;
import static org.apache.guacamole.rest.APIPatch.Operation.remove;
import org.apache.guacamole.rest.ObjectRetrievalService;
import org.apache.guacamole.rest.PATCH;
import org.apache.guacamole.rest.auth.AuthenticationService;
import org.apache.guacamole.rest.permission.APIPermissionSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A REST Service for handling user CRUD operations.
*
* @author James Muehlner
* @author Michael Jumper
*/
@Path("/data/{dataSource}/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserRESTService {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(UserRESTService.class);
/**
* The prefix of any path within an operation of a JSON patch which
* modifies the permissions of a user regarding a specific connection.
*/
private static final String CONNECTION_PERMISSION_PATCH_PATH_PREFIX = "/connectionPermissions/";
/**
* The prefix of any path within an operation of a JSON patch which
* modifies the permissions of a user regarding a specific connection group.
*/
private static final String CONNECTION_GROUP_PERMISSION_PATCH_PATH_PREFIX = "/connectionGroupPermissions/";
/**
* The prefix of any path within an operation of a JSON patch which
* modifies the permissions of a user regarding a specific active connection.
*/
private static final String ACTIVE_CONNECTION_PERMISSION_PATCH_PATH_PREFIX = "/activeConnectionPermissions/";
/**
* The prefix of any path within an operation of a JSON patch which
* modifies the permissions of a user regarding another, specific user.
*/
private static final String USER_PERMISSION_PATCH_PATH_PREFIX = "/userPermissions/";
/**
* The path of any operation within a JSON patch which modifies the
* permissions of a user regarding the entire system.
*/
private static final String SYSTEM_PERMISSION_PATCH_PATH = "/systemPermissions";
/**
* A service for authenticating users from auth tokens.
*/
@Inject
private AuthenticationService authenticationService;
/**
* Service for convenient retrieval of objects.
*/
@Inject
private ObjectRetrievalService retrievalService;
/**
* Gets a list of users in the given data source (UserContext), filtering
* the returned list by the given permission, if specified.
*
* @param authToken
* The authentication token that is used to authenticate the user
* performing the operation.
*
* @param authProviderIdentifier
* The unique identifier of the AuthenticationProvider associated with
* the UserContext from which the users are to be retrieved.
*
* @param permissions
* The set of permissions to filter with. A user must have one or more
* of these permissions for a user to appear in the result.
* If null, no filtering will be performed.
*
* @return
* A list of all visible users. If a permission was specified, this
* list will contain only those users for whom the current user has
* that permission.
*
* @throws GuacamoleException
* If an error is encountered while retrieving users.
*/
@GET
public List<APIUser> getUsers(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@QueryParam("permission") List<ObjectPermission.Type> permissions)
throws GuacamoleException {
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
// An admin user has access to any user
User self = userContext.self();
SystemPermissionSet systemPermissions = self.getSystemPermissions();
boolean isAdmin = systemPermissions.hasPermission(SystemPermission.Type.ADMINISTER);
// Get the directory
Directory<User> userDirectory = userContext.getUserDirectory();
// Filter users, if requested
Collection<String> userIdentifiers = userDirectory.getIdentifiers();
if (!isAdmin && permissions != null && !permissions.isEmpty()) {
ObjectPermissionSet userPermissions = self.getUserPermissions();
userIdentifiers = userPermissions.getAccessibleObjects(permissions, userIdentifiers);
}
// Retrieve all users, converting to API users
List<APIUser> apiUsers = new ArrayList<APIUser>();
for (User user : userDirectory.getAll(userIdentifiers))
apiUsers.add(new APIUser(user));
return apiUsers;
}
/**
* Retrieves an individual user.
*
* @param authToken
* The authentication token that is used to authenticate the user
* performing the operation.
*
* @param authProviderIdentifier
* The unique identifier of the AuthenticationProvider associated with
* the UserContext from which the requested user is to be retrieved.
*
* @param username
* The username of the user to retrieve.
*
* @return user
* The user having the given username.
*
* @throws GuacamoleException
* If an error occurs while retrieving the user.
*/
@GET
@Path("/{username}")
public APIUser getUser(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("username") String username)
throws GuacamoleException {
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
// Retrieve the requested user
User user = retrievalService.retrieveUser(session, authProviderIdentifier, username);
return new APIUser(user);
}
/**
* Creates a new user and returns the user that was created.
*
* @param authToken
* The authentication token that is used to authenticate the user
* performing the operation.
*
* @param authProviderIdentifier
* The unique identifier of the AuthenticationProvider associated with
* the UserContext in which the requested user is to be created.
*
* @param user
* The new user to create.
*
* @throws GuacamoleException
* If a problem is encountered while creating the user.
*
* @return
* The newly created user.
*/
@POST
public APIUser createUser(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier, APIUser user)
throws GuacamoleException {
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
// Get the directory
Directory<User> userDirectory = userContext.getUserDirectory();
// Randomly set the password if it wasn't provided
if (user.getPassword() == null)
user.setPassword(UUID.randomUUID().toString());
// Create the user
userDirectory.add(new APIUserWrapper(user));
return user;
}
/**
* Updates an individual existing user.
*
* @param authToken
* The authentication token that is used to authenticate the user
* performing the operation.
*
* @param authProviderIdentifier
* The unique identifier of the AuthenticationProvider associated with
* the UserContext in which the requested user is to be updated.
*
* @param username
* The username of the user to update.
*
* @param user
* The data to update the user with.
*
* @throws GuacamoleException
* If an error occurs while updating the user.
*/
@PUT
@Path("/{username}")
public void updateUser(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("username") String username, APIUser user)
throws GuacamoleException {
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
// Get the directory
Directory<User> userDirectory = userContext.getUserDirectory();
// Validate data and path are sane
if (!user.getUsername().equals(username))
throw new GuacamoleClientException("Username in path does not match username provided JSON data.");
// A user may not use this endpoint to modify himself
if (userContext.self().getIdentifier().equals(user.getUsername()))
throw new GuacamoleSecurityException("Permission denied.");
// Get the user
User existingUser = retrievalService.retrieveUser(userContext, username);
// Do not update the user password if no password was provided
if (user.getPassword() != null)
existingUser.setPassword(user.getPassword());
// Update user attributes
existingUser.setAttributes(user.getAttributes());
// Update the user
userDirectory.update(existingUser);
}
/**
* Updates the password for an individual existing user.
*
* @param authToken
* The authentication token that is used to authenticate the user
* performing the operation.
*
* @param authProviderIdentifier
* The unique identifier of the AuthenticationProvider associated with
* the UserContext in which the requested user is to be updated.
*
* @param username
* The username of the user to update.
*
* @param userPasswordUpdate
* The object containing the old password for the user, as well as the
* new password to set for that user.
*
* @param request
* The HttpServletRequest associated with the password update attempt.
*
* @throws GuacamoleException
* If an error occurs while updating the user's password.
*/
@PUT
@Path("/{username}/password")
public void updatePassword(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("username") String username,
APIUserPasswordUpdate userPasswordUpdate,
@Context HttpServletRequest request) throws GuacamoleException {
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
// Build credentials
Credentials credentials = new Credentials();
credentials.setUsername(username);
credentials.setPassword(userPasswordUpdate.getOldPassword());
credentials.setRequest(request);
credentials.setSession(request.getSession(true));
// Verify that the old password was correct
try {
AuthenticationProvider authProvider = userContext.getAuthenticationProvider();
if (authProvider.authenticateUser(credentials) == null)
throw new GuacamoleSecurityException("Permission denied.");
}
// Pass through any credentials exceptions as simple permission denied
catch (GuacamoleCredentialsException e) {
throw new GuacamoleSecurityException("Permission denied.");
}
// Get the user directory
Directory<User> userDirectory = userContext.getUserDirectory();
// Get the user that we want to updates
User user = retrievalService.retrieveUser(userContext, username);
// Set password to the newly provided one
user.setPassword(userPasswordUpdate.getNewPassword());
// Update the user
userDirectory.update(user);
}
/**
* Deletes an individual existing user.
*
* @param authToken
* The authentication token that is used to authenticate the user
* performing the operation.
*
* @param authProviderIdentifier
* The unique identifier of the AuthenticationProvider associated with
* the UserContext from which the requested user is to be deleted.
*
* @param username
* The username of the user to delete.
*
* @throws GuacamoleException
* If an error occurs while deleting the user.
*/
@DELETE
@Path("/{username}")
public void deleteUser(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("username") String username)
throws GuacamoleException {
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
// Get the directory
Directory<User> userDirectory = userContext.getUserDirectory();
// Get the user
User existingUser = userDirectory.get(username);
if (existingUser == null)
throw new GuacamoleResourceNotFoundException("No such user: \"" + username + "\"");
// Delete the user
userDirectory.remove(username);
}
/**
* Gets a list of permissions for the user with the given username.
*
* @param authToken
* The authentication token that is used to authenticate the user
* performing the operation.
*
* @param authProviderIdentifier
* The unique identifier of the AuthenticationProvider associated with
* the UserContext in which the requested user is to be found.
*
* @param username
* The username of the user to retrieve permissions for.
*
* @return
* A list of all permissions granted to the specified user.
*
* @throws GuacamoleException
* If an error occurs while retrieving permissions.
*/
@GET
@Path("/{username}/permissions")
public APIPermissionSet getPermissions(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("username") String username)
throws GuacamoleException {
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
User user;
// If username is own username, just use self - might not have query permissions
if (userContext.self().getIdentifier().equals(username))
user = userContext.self();
// If not self, query corresponding user from directory
else {
user = userContext.getUserDirectory().get(username);
if (user == null)
throw new GuacamoleResourceNotFoundException("No such user: \"" + username + "\"");
}
return new APIPermissionSet(user);
}
/**
* Updates the given permission set patch by queuing an add or remove
* operation for the given permission based on the given patch operation.
*
* @param <PermissionType>
* The type of permission stored within the permission set.
*
* @param operation
* The patch operation to perform.
*
* @param permissionSetPatch
* The permission set patch being modified.
*
* @param permission
* The permission being added or removed from the set.
*
* @throws GuacamoleException
* If the requested patch operation is not supported.
*/
private <PermissionType extends Permission> void updatePermissionSet(
APIPatch.Operation operation,
PermissionSetPatch<PermissionType> permissionSetPatch,
PermissionType permission) throws GuacamoleException {
// Add or remove permission based on operation
switch (operation) {
// Add permission
case add:
permissionSetPatch.addPermission(permission);
break;
// Remove permission
case remove:
permissionSetPatch.removePermission(permission);
break;
// Unsupported patch operation
default:
throw new GuacamoleClientException("Unsupported patch operation: \"" + operation + "\"");
}
}
/**
* Applies a given list of permission patches. Each patch specifies either
* an "add" or a "remove" operation for a permission type, represented by
* a string. Valid permission types depend on the path of each patch
* operation, as the path dictates the permission being modified, such as
* "/connectionPermissions/42" or "/systemPermissions".
*
* @param authToken
* The authentication token that is used to authenticate the user
* performing the operation.
*
* @param authProviderIdentifier
* The unique identifier of the AuthenticationProvider associated with
* the UserContext in which the requested user is to be found.
*
* @param username
* The username of the user to modify the permissions of.
*
* @param patches
* The permission patches to apply for this request.
*
* @throws GuacamoleException
* If a problem is encountered while modifying permissions.
*/
@PATCH
@Path("/{username}/permissions")
public void patchPermissions(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("username") String username,
List<APIPatch<String>> patches) throws GuacamoleException {
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
// Get the user
User user = userContext.getUserDirectory().get(username);
if (user == null)
throw new GuacamoleResourceNotFoundException("No such user: \"" + username + "\"");
// Permission patches for all types of permissions
PermissionSetPatch<ObjectPermission> connectionPermissionPatch = new PermissionSetPatch<ObjectPermission>();
PermissionSetPatch<ObjectPermission> connectionGroupPermissionPatch = new PermissionSetPatch<ObjectPermission>();
PermissionSetPatch<ObjectPermission> activeConnectionPermissionPatch = new PermissionSetPatch<ObjectPermission>();
PermissionSetPatch<ObjectPermission> userPermissionPatch = new PermissionSetPatch<ObjectPermission>();
PermissionSetPatch<SystemPermission> systemPermissionPatch = new PermissionSetPatch<SystemPermission>();
// Apply all patch operations individually
for (APIPatch<String> patch : patches) {
String path = patch.getPath();
// Create connection permission if path has connection prefix
if (path.startsWith(CONNECTION_PERMISSION_PATCH_PATH_PREFIX)) {
// Get identifier and type from patch operation
String identifier = path.substring(CONNECTION_PERMISSION_PATCH_PATH_PREFIX.length());
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
// Create and update corresponding permission
ObjectPermission permission = new ObjectPermission(type, identifier);
updatePermissionSet(patch.getOp(), connectionPermissionPatch, permission);
}
// Create connection group permission if path has connection group prefix
else if (path.startsWith(CONNECTION_GROUP_PERMISSION_PATCH_PATH_PREFIX)) {
// Get identifier and type from patch operation
String identifier = path.substring(CONNECTION_GROUP_PERMISSION_PATCH_PATH_PREFIX.length());
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
// Create and update corresponding permission
ObjectPermission permission = new ObjectPermission(type, identifier);
updatePermissionSet(patch.getOp(), connectionGroupPermissionPatch, permission);
}
// Create active connection permission if path has active connection prefix
else if (path.startsWith(ACTIVE_CONNECTION_PERMISSION_PATCH_PATH_PREFIX)) {
// Get identifier and type from patch operation
String identifier = path.substring(ACTIVE_CONNECTION_PERMISSION_PATCH_PATH_PREFIX.length());
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
// Create and update corresponding permission
ObjectPermission permission = new ObjectPermission(type, identifier);
updatePermissionSet(patch.getOp(), activeConnectionPermissionPatch, permission);
}
// Create user permission if path has user prefix
else if (path.startsWith(USER_PERMISSION_PATCH_PATH_PREFIX)) {
// Get identifier and type from patch operation
String identifier = path.substring(USER_PERMISSION_PATCH_PATH_PREFIX.length());
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
// Create and update corresponding permission
ObjectPermission permission = new ObjectPermission(type, identifier);
updatePermissionSet(patch.getOp(), userPermissionPatch, permission);
}
// Create system permission if path is system path
else if (path.equals(SYSTEM_PERMISSION_PATCH_PATH)) {
// Get identifier and type from patch operation
SystemPermission.Type type = SystemPermission.Type.valueOf(patch.getValue());
// Create and update corresponding permission
SystemPermission permission = new SystemPermission(type);
updatePermissionSet(patch.getOp(), systemPermissionPatch, permission);
}
// Otherwise, the path is not supported
else
throw new GuacamoleClientException("Unsupported patch path: \"" + path + "\"");
} // end for each patch operation
// Save the permission changes
connectionPermissionPatch.apply(user.getConnectionPermissions());
connectionGroupPermissionPatch.apply(user.getConnectionGroupPermissions());
activeConnectionPermissionPatch.apply(user.getActiveConnectionPermissions());
userPermissionPatch.apply(user.getUserPermissions());
systemPermissionPatch.apply(user.getSystemPermissions());
}
}

View File

@@ -0,0 +1,329 @@
/*
* 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.rest.user;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.Permission;
import org.apache.guacamole.net.auth.permission.SystemPermission;
import org.apache.guacamole.rest.APIPatch;
import org.apache.guacamole.rest.PATCH;
import org.apache.guacamole.rest.directory.DirectoryObjectResource;
import org.apache.guacamole.rest.directory.DirectoryObjectTranslator;
import org.apache.guacamole.rest.permission.APIPermissionSet;
/**
* A REST resource which abstracts the operations available on an existing
* User.
*
* @author Michael Jumper
*/
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource
extends DirectoryObjectResource<User, APIUser> {
/**
* The UserContext associated with the Directory which contains the User
* exposed by this resource.
*/
private final UserContext userContext;
/**
* The Directory which contains the User object represented by this
* UserResource.
*/
private final Directory<User> directory;
/**
* The User object represented by this UserResource.
*/
private final User user;
/**
* Creates a new UserResource which exposes the operations and subresources
* available for the given User.
*
* @param userContext
* The UserContext associated with the given Directory.
*
* @param directory
* The Directory which contains the given User.
*
* @param user
* The User that this UserResource should represent.
*
* @param translator
* A DirectoryObjectTranslator implementation which handles Users.
*/
@AssistedInject
public UserResource(@Assisted UserContext userContext,
@Assisted Directory<User> directory,
@Assisted User user,
DirectoryObjectTranslator<User, APIUser> translator) {
super(directory, user, translator);
this.userContext = userContext;
this.directory = directory;
this.user = user;
}
@Override
public void updateObject(APIUser modifiedObject) throws GuacamoleException {
// A user may not use this endpoint to modify himself
if (userContext.self().getIdentifier().equals(modifiedObject.getUsername()))
throw new GuacamoleSecurityException("Permission denied.");
super.updateObject(modifiedObject);
}
/**
* Updates the password for an individual existing user.
*
* @param userPasswordUpdate
* The object containing the old password for the user, as well as the
* new password to set for that user.
*
* @param request
* The HttpServletRequest associated with the password update attempt.
*
* @throws GuacamoleException
* If an error occurs while updating the user's password.
*/
@PUT
@Path("password")
public void updatePassword(APIUserPasswordUpdate userPasswordUpdate,
@Context HttpServletRequest request) throws GuacamoleException {
// Build credentials
Credentials credentials = new Credentials();
credentials.setUsername(user.getIdentifier());
credentials.setPassword(userPasswordUpdate.getOldPassword());
credentials.setRequest(request);
credentials.setSession(request.getSession(true));
// Verify that the old password was correct
try {
AuthenticationProvider authProvider = userContext.getAuthenticationProvider();
if (authProvider.authenticateUser(credentials) == null)
throw new GuacamoleSecurityException("Permission denied.");
}
// Pass through any credentials exceptions as simple permission denied
catch (GuacamoleCredentialsException e) {
throw new GuacamoleSecurityException("Permission denied.");
}
// Set password to the newly provided one
user.setPassword(userPasswordUpdate.getNewPassword());
directory.update(user);
}
/**
* Gets a list of permissions for the user with the given username.
*
* @return
* A list of all permissions granted to the specified user.
*
* @throws GuacamoleException
* If an error occurs while retrieving permissions.
*/
@GET
@Path("permissions")
public APIPermissionSet getPermissions() throws GuacamoleException {
return new APIPermissionSet(user);
}
/**
* Updates the given permission set patch by queuing an add or remove
* operation for the given permission based on the given patch operation.
*
* @param <PermissionType>
* The type of permission stored within the permission set.
*
* @param operation
* The patch operation to perform.
*
* @param permissionSetPatch
* The permission set patch being modified.
*
* @param permission
* The permission being added or removed from the set.
*
* @throws GuacamoleException
* If the requested patch operation is not supported.
*/
private <PermissionType extends Permission> void updatePermissionSet(
APIPatch.Operation operation,
PermissionSetPatch<PermissionType> permissionSetPatch,
PermissionType permission) throws GuacamoleException {
// Add or remove permission based on operation
switch (operation) {
// Add permission
case add:
permissionSetPatch.addPermission(permission);
break;
// Remove permission
case remove:
permissionSetPatch.removePermission(permission);
break;
// Unsupported patch operation
default:
throw new GuacamoleClientException("Unsupported patch operation: \"" + operation + "\"");
}
}
/**
* Applies a given list of permission patches. Each patch specifies either
* an "add" or a "remove" operation for a permission type, represented by
* a string. Valid permission types depend on the path of each patch
* operation, as the path dictates the permission being modified, such as
* "/connectionPermissions/42" or "/systemPermissions".
*
* @param patches
* The permission patches to apply for this request.
*
* @throws GuacamoleException
* If a problem is encountered while modifying permissions.
*/
@PATCH
@Path("permissions")
public void patchPermissions(List<APIPatch<String>> patches)
throws GuacamoleException {
// Permission patches for all types of permissions
PermissionSetPatch<ObjectPermission> connectionPermissionPatch = new PermissionSetPatch<ObjectPermission>();
PermissionSetPatch<ObjectPermission> connectionGroupPermissionPatch = new PermissionSetPatch<ObjectPermission>();
PermissionSetPatch<ObjectPermission> activeConnectionPermissionPatch = new PermissionSetPatch<ObjectPermission>();
PermissionSetPatch<ObjectPermission> userPermissionPatch = new PermissionSetPatch<ObjectPermission>();
PermissionSetPatch<SystemPermission> systemPermissionPatch = new PermissionSetPatch<SystemPermission>();
// Apply all patch operations individually
for (APIPatch<String> patch : patches) {
String path = patch.getPath();
// Create connection permission if path has connection prefix
if (path.startsWith(CONNECTION_PERMISSION_PATCH_PATH_PREFIX)) {
// Get identifier and type from patch operation
String identifier = path.substring(CONNECTION_PERMISSION_PATCH_PATH_PREFIX.length());
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
// Create and update corresponding permission
ObjectPermission permission = new ObjectPermission(type, identifier);
updatePermissionSet(patch.getOp(), connectionPermissionPatch, permission);
}
// Create connection group permission if path has connection group prefix
else if (path.startsWith(CONNECTION_GROUP_PERMISSION_PATCH_PATH_PREFIX)) {
// Get identifier and type from patch operation
String identifier = path.substring(CONNECTION_GROUP_PERMISSION_PATCH_PATH_PREFIX.length());
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
// Create and update corresponding permission
ObjectPermission permission = new ObjectPermission(type, identifier);
updatePermissionSet(patch.getOp(), connectionGroupPermissionPatch, permission);
}
// Create active connection permission if path has active connection prefix
else if (path.startsWith(ACTIVE_CONNECTION_PERMISSION_PATCH_PATH_PREFIX)) {
// Get identifier and type from patch operation
String identifier = path.substring(ACTIVE_CONNECTION_PERMISSION_PATCH_PATH_PREFIX.length());
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
// Create and update corresponding permission
ObjectPermission permission = new ObjectPermission(type, identifier);
updatePermissionSet(patch.getOp(), activeConnectionPermissionPatch, permission);
}
// Create user permission if path has user prefix
else if (path.startsWith(USER_PERMISSION_PATCH_PATH_PREFIX)) {
// Get identifier and type from patch operation
String identifier = path.substring(USER_PERMISSION_PATCH_PATH_PREFIX.length());
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
// Create and update corresponding permission
ObjectPermission permission = new ObjectPermission(type, identifier);
updatePermissionSet(patch.getOp(), userPermissionPatch, permission);
}
// Create system permission if path is system path
else if (path.equals(SYSTEM_PERMISSION_PATCH_PATH)) {
// Get identifier and type from patch operation
SystemPermission.Type type = SystemPermission.Type.valueOf(patch.getValue());
// Create and update corresponding permission
SystemPermission permission = new SystemPermission(type);
updatePermissionSet(patch.getOp(), systemPermissionPatch, permission);
}
// Otherwise, the path is not supported
else
throw new GuacamoleClientException("Unsupported patch path: \"" + path + "\"");
} // end for each patch operation
// Save the permission changes
connectionPermissionPatch.apply(user.getConnectionPermissions());
connectionGroupPermissionPatch.apply(user.getConnectionGroupPermissions());
activeConnectionPermissionPatch.apply(user.getActiveConnectionPermissions());
userPermissionPatch.apply(user.getUserPermissions());
systemPermissionPatch.apply(user.getSystemPermissions());
}
}

View File

@@ -50,9 +50,10 @@ angular.module('rest').factory('userService', ['$injector',
* If null, no filtering will be performed. Valid values are listed * If null, no filtering will be performed. Valid values are listed
* within PermissionSet.ObjectType. * within PermissionSet.ObjectType.
* *
* @returns {Promise.<User[]>} * @returns {Promise.<Object.<String, User>>}
* A promise which will resolve with an array of @link{User} objects * A promise which will resolve with a map of @link{User} objects
* upon success. * where each key is the identifier (username) of the corresponding
* user.
*/ */
service.getUsers = function getUsers(dataSource, permissionTypes) { service.getUsers = function getUsers(dataSource, permissionTypes) {

View File

@@ -227,14 +227,14 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings
PermissionSet.ObjectPermissionType.DELETE PermissionSet.ObjectPermissionType.DELETE
]); ]);
userPromise.then(function usersReceived(userArrays) { userPromise.then(function usersReceived(allUsers) {
var addedUsers = {}; var addedUsers = {};
$scope.manageableUsers = []; $scope.manageableUsers = [];
// For each user in each data source // For each user in each data source
angular.forEach(dataSources, function addUserList(dataSource) { angular.forEach(dataSources, function addUserList(dataSource) {
angular.forEach(userArrays[dataSource], function addUser(user) { angular.forEach(allUsers[dataSource], function addUser(user) {
// Do not add the same user twice // Do not add the same user twice
if (addedUsers[user.username]) if (addedUsers[user.username])