Ticket #362: Created user CRUD.

This commit is contained in:
James Muehlner
2013-12-09 20:26:06 -08:00
parent 82413b2103
commit 756ffa7637
9 changed files with 479 additions and 5 deletions

View File

@@ -30,6 +30,7 @@ import org.glyptodon.guacamole.net.basic.rest.auth.TokenUserContextMap;
import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionService;
import org.glyptodon.guacamole.net.basic.rest.connectiongroup.ConnectionGroupService;
import org.glyptodon.guacamole.net.basic.rest.permission.PermissionService;
import org.glyptodon.guacamole.net.basic.rest.user.UserService;
import org.glyptodon.guacamole.properties.GuacamoleProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -69,6 +70,7 @@ public class RESTModule extends AbstractModule {
bind(ConnectionService.class);
bind(ConnectionGroupService.class);
bind(PermissionService.class);
bind(UserService.class);
bind(AuthenticationService.class);
bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class);

View File

@@ -26,6 +26,7 @@ import org.glyptodon.guacamole.net.basic.rest.auth.LoginRESTService;
import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionRESTService;
import org.glyptodon.guacamole.net.basic.rest.connectiongroup.ConnectionGroupRESTService;
import org.glyptodon.guacamole.net.basic.rest.permission.PermissionRESTService;
import org.glyptodon.guacamole.net.basic.rest.user.UserRESTService;
/**
* A Guice Module to set up the servlet mappings for the Guacamole REST API.
@@ -41,6 +42,7 @@ public class RESTServletModule extends ServletModule {
bind(ConnectionRESTService.class);
bind(ConnectionGroupRESTService.class);
bind(PermissionRESTService.class);
bind(UserRESTService.class);
bind(LoginRESTService.class);
// Set up the servlet and JSON mappings

View File

@@ -19,9 +19,7 @@ package org.glyptodon.guacamole.net.basic.rest.permission;
*/
import com.google.inject.Inject;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
@@ -36,7 +34,6 @@ import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleSecurityException;
import org.glyptodon.guacamole.net.auth.User;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.auth.permission.Permission;
import org.glyptodon.guacamole.net.basic.rest.HTTPException;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
import org.slf4j.Logger;

View File

@@ -39,8 +39,7 @@ public class PermissionService {
* @param permissions The Connections to convert for REST endpoint use.
* @return A List of APIPermission objects for use with the REST endpoint.
*/
public List<APIPermission> convertPermissionList(Iterable<? extends Permission> permissions)
throws GuacamoleException {
public List<APIPermission> convertPermissionList(Iterable<? extends Permission> permissions) {
List<APIPermission> restPermissions = new ArrayList<APIPermission>();
for(Permission permission : permissions) {

View File

@@ -0,0 +1,82 @@
package org.glyptodon.guacamole.net.basic.rest.user;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.glyptodon.guacamole.net.auth.User;
/*
* Guacamole - Clientless Remote Desktop
* Copyright (C) 2010 Michael Jumper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* A simple User to expose through the REST endpoints.
*
* @author James Muehlner
*/
@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class APIUser {
/**
* The username of this user.
*/
private String username;
/**
* The password of this user.
*/
private String password;
/**
* Construct a new APIUser from the provided User.
* @param user The User to construct the APIUser from.
*/
public APIUser(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
}
/**
* Returns the username for this user.
* @return The username for this user.
*/
public String getUsername() {
return username;
}
/**
* Set the username for this user.
* @param username The username for this user.
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Returns the password for this user.
* @return The password for this user.
*/
public String getPassword() {
return password;
}
/**
* Set the password for this user.
* @param password The password for this user.
*/
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,103 @@
package org.glyptodon.guacamole.net.basic.rest.user;
/*
* Guacamole - Clientless Remote Desktop
* Copyright (C) 2010 Michael Jumper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import java.util.Collections;
import java.util.Set;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.auth.User;
import org.glyptodon.guacamole.net.auth.permission.Permission;
/**
* A wrapper to make an APIConnection look like a User. Useful where a
* org.glyptodon.guacamole.net.auth.User is required.
*
* @author James Muehlner
*/
public class APIUserWrapper implements User {
/**
* The wrapped APIUser.
*/
private APIUser apiUser;
/**
* The set of permissions for this user.
* NOTE: Not exposed by the REST endpoints.
*/
private Set<Permission> permissionSet = Collections.EMPTY_SET;
/**
* Wrap a given APIUser to expose as a User.
* @param apiUser The APIUser to wrap.
*/
public APIUserWrapper(APIUser apiUser) {
this.apiUser = apiUser;
}
/**
* Wrap a given APIUser to expose as a User, with the given permission set.
* @param apiUser The APIUser to wrap.
* @param permissionSet The set of permissions for the wrapped user.
*/
public APIUserWrapper(APIUser apiUser, Set<Permission> permissionSet) {
this.apiUser = apiUser;
this.permissionSet = permissionSet;
}
@Override
public String getUsername() {
return apiUser.getUsername();
}
@Override
public void setUsername(String username) {
apiUser.setUsername(username);
}
@Override
public String getPassword() {
return apiUser.getPassword();
}
@Override
public void setPassword(String password) {
apiUser.setPassword(password);
}
@Override
public Set<Permission> getPermissions() throws GuacamoleException {
return permissionSet;
}
@Override
public boolean hasPermission(Permission permission) throws GuacamoleException {
return permissionSet.contains(permission);
}
@Override
public void addPermission(Permission permission) throws GuacamoleException {
throw new UnsupportedOperationException("Operation not supported.");
}
@Override
public void removePermission(Permission permission) throws GuacamoleException {
throw new UnsupportedOperationException("Operation not supported.");
}
}

View File

@@ -0,0 +1,229 @@
package org.glyptodon.guacamole.net.basic.rest.user;
import com.google.inject.Inject;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
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.MediaType;
import javax.ws.rs.core.Response;
import org.glyptodon.guacamole.GuacamoleClientException;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleSecurityException;
import org.glyptodon.guacamole.net.auth.Directory;
import org.glyptodon.guacamole.net.auth.User;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.rest.HTTPException;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/*
* Guacamole - Clientless Remote Desktop
* Copyright (C) 2010 Michael Jumper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* A REST Service for handling user CRUD operations.
*
* @author James Muehlner
*/
@Path("/api/user")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserRESTService {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(UserRESTService.class);
/**
* A service for authenticating users from auth tokens.
*/
@Inject
private AuthenticationService authenticationService;
/**
* A service for managing the REST endpoint APIPermission objects.
*/
@Inject
private UserService userService;
/**
* Get a list of users in the system.
* @param authToken The authentication token that is used to authenticate
* the user performing the operation.
* @return The user list.
*/
@GET
public List<APIUser> getUsers(@QueryParam("token") String authToken) {
UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken);
try {
// Get the directory
Directory<String, User> userDirectory = userContext.getUserDirectory();
// Convert and return the user directory listing
return userService.convertUserList(userDirectory);
} catch(GuacamoleSecurityException e) {
throw new HTTPException(Response.Status.UNAUTHORIZED, e.getMessage() != null ? e.getMessage() : "Permission denied.");
} catch(GuacamoleClientException e) {
throw new HTTPException(Response.Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request.");
} catch(GuacamoleException e) {
logger.error("Unexpected GuacamoleException caught while listing permissions.", e);
throw new HTTPException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error.");
}
}
/**
* Get a user.
* @param authToken The authentication token that is used to authenticate
* the user performing the operation.
* @return user The user.
*/
@GET
@Path("/{userID}")
public APIUser getUser(@QueryParam("token") String authToken, @PathParam("userID") String userID) {
UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken);
try {
// Get the directory
Directory<String, User> userDirectory = userContext.getUserDirectory();
// Get the user
User user = userDirectory.get(userID);
if(user == null)
throw new HTTPException(Response.Status.NOT_FOUND, "User not found with the provided userID.");
// Return the user
return new APIUser(user);
} catch(GuacamoleSecurityException e) {
throw new HTTPException(Response.Status.UNAUTHORIZED, e.getMessage() != null ? e.getMessage() : "Permission denied.");
} catch(GuacamoleClientException e) {
throw new HTTPException(Response.Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request.");
} catch(GuacamoleException e) {
logger.error("Unexpected GuacamoleException caught while listing permissions.", e);
throw new HTTPException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error.");
}
}
/**
* Create a new user.
* @param authToken The authentication token that is used to authenticate
* the user performing the operation.
* @param user The new user to create.
*/
@POST
public void createUser(@QueryParam("token") String authToken, APIUser user) {
UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken);
try {
// Get the directory
Directory<String, User> userDirectory = userContext.getUserDirectory();
// Create the user
userDirectory.add(new APIUserWrapper(user));
} catch(GuacamoleSecurityException e) {
throw new HTTPException(Response.Status.UNAUTHORIZED, e.getMessage() != null ? e.getMessage() : "Permission denied.");
} catch(GuacamoleClientException e) {
throw new HTTPException(Response.Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request.");
} catch(GuacamoleException e) {
logger.error("Unexpected GuacamoleException caught while listing permissions.", e);
throw new HTTPException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error.");
}
}
/**
* Update an existing user.
* @param authToken The authentication token that is used to authenticate
* the user performing the operation.
* @param userID The unique identifier of the user to update.
* @param user The updated user.
*/
@POST
@Path("/{userID}")
public void updateUser(@QueryParam("token") String authToken, @PathParam("userID") String userID, APIUser user) {
UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken);
try {
// Get the directory
Directory<String, User> userDirectory = userContext.getUserDirectory();
if(!user.getUsername().equals(userID))
throw new HTTPException(Response.Status.BAD_REQUEST, "Username does not match provided userID.");
// Get the user
User existingUser = userDirectory.get(userID);
if(existingUser == null)
throw new HTTPException(Response.Status.NOT_FOUND, "User not found with the provided userID.");
/*
* Update the user with the permission set from the existing user
* since the user REST endpoints do not expose permissions
*/
userDirectory.update(new APIUserWrapper(user, existingUser.getPermissions()));
} catch(GuacamoleSecurityException e) {
throw new HTTPException(Response.Status.UNAUTHORIZED, e.getMessage() != null ? e.getMessage() : "Permission denied.");
} catch(GuacamoleClientException e) {
throw new HTTPException(Response.Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request.");
} catch(GuacamoleException e) {
logger.error("Unexpected GuacamoleException caught while listing permissions.", e);
throw new HTTPException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error.");
}
}
/**
* Delete an existing user.
* @param authToken The authentication token that is used to authenticate
* the user performing the operation.
* @param userID The unique identifier of the user to delete.
*/
@DELETE
@Path("/{userID}")
public void deleteUser(@QueryParam("token") String authToken, @PathParam("userID") String userID) {
UserContext userContext = authenticationService.getUserContextFromAuthToken(authToken);
try {
// Get the directory
Directory<String, User> userDirectory = userContext.getUserDirectory();
// Get the user
User existingUser = userDirectory.get(userID);
if(existingUser == null)
throw new HTTPException(Response.Status.NOT_FOUND, "User not found with the provided userID.");
// Delete the user
userDirectory.remove(userID);
} catch(GuacamoleSecurityException e) {
throw new HTTPException(Response.Status.UNAUTHORIZED, e.getMessage() != null ? e.getMessage() : "Permission denied.");
} catch(GuacamoleClientException e) {
throw new HTTPException(Response.Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request.");
} catch(GuacamoleException e) {
logger.error("Unexpected GuacamoleException caught while listing permissions.", e);
throw new HTTPException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error.");
}
}
}

View File

@@ -0,0 +1,54 @@
package org.glyptodon.guacamole.net.basic.rest.user;
import java.util.ArrayList;
import java.util.List;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.auth.Directory;
import org.glyptodon.guacamole.net.auth.User;
/*
* Guacamole - Clientless Remote Desktop
* Copyright (C) 2010 Michael Jumper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* A service for performing useful manipulations on REST Users.
*
* @author James Muehlner
*/
public class UserService {
/**
* Converts a user directory to a list of APIUser objects for
* exposing with the REST endpoints.
*
* @param userDirectory The user directory to convert for REST endpoint use.
* @return A List of APIUser objects for use with the REST endpoint.
* @throws GuacamoleException If an error occurs while converting the
* user directory.
*/
public List<APIUser> convertUserList(Directory<String, User> userDirectory)
throws GuacamoleException {
List<APIUser> restUsers = new ArrayList<APIUser>();
for(String username : userDirectory.getIdentifiers()) {
restUsers.add(new APIUser(userDirectory.get(username)));
}
return restUsers;
}
}

View File

@@ -0,0 +1,6 @@
/**
* Classes related to the user manipulation aspect of the Guacamole REST API.
*/
package org.glyptodon.guacamole.net.basic.rest.user;