GUAC-1100: Migrate REST services to new guacamole-ext API. This is partial and temporary - batch operations are not being used, and the MySQL auth has not been updated.

This commit is contained in:
Michael Jumper
2015-02-11 16:14:38 -08:00
parent 3f5d398f13
commit 9fe2eb6bbd
5 changed files with 189 additions and 198 deletions

View File

@@ -45,10 +45,10 @@ import org.glyptodon.guacamole.net.auth.ConnectionRecord;
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.auth.permission.ConnectionPermission;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermission;
import org.glyptodon.guacamole.net.auth.permission.Permission;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
import org.glyptodon.guacamole.net.auth.permission.SystemPermission;
import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
@@ -71,12 +71,6 @@ public class ConnectionRESTService {
*/
private static final Logger logger = LoggerFactory.getLogger(ConnectionRESTService.class);
/**
* System administration permission.
*/
private static final Permission SYSTEM_PERMISSION =
new SystemPermission(SystemPermission.Type.ADMINISTER);
/**
* A service for authenticating users from auth tokens.
*/
@@ -143,9 +137,13 @@ public class ConnectionRESTService {
UserContext userContext = authenticationService.getUserContext(authToken);
User self = userContext.self();
// Retrieve permission sets
SystemPermissionSet systemPermissions = self.getSystemPermissions();
ObjectPermissionSet<String> connectionPermissions = self.getConnectionPermissions();
// Deny access if adminstrative or update permission is missing
if (!self.hasPermission(SYSTEM_PERMISSION)
&& !self.hasPermission(new ConnectionPermission(ObjectPermission.Type.UPDATE, connectionID)))
if (!systemPermissions.hasPermission(SystemPermission.Type.ADMINISTER)
&& !connectionPermissions.hasPermission(ObjectPermission.Type.UPDATE, connectionID))
throw new GuacamoleSecurityException("Permission to read connection parameters denied.");
// Retrieve the requested connection

View File

@@ -45,10 +45,10 @@ import org.glyptodon.guacamole.net.auth.ConnectionGroup;
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.auth.permission.ConnectionGroupPermission;
import org.glyptodon.guacamole.net.auth.permission.ConnectionPermission;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermission;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
import org.glyptodon.guacamole.net.auth.permission.SystemPermission;
import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
@@ -102,14 +102,14 @@ public class ConnectionGroupRESTService {
*/
private boolean hasConnectionPermission(User user, String identifier,
List<ObjectPermission.Type> permissions) throws GuacamoleException {
// Retrieve connection permissions
ObjectPermissionSet<String> connectionPermissions = user.getConnectionPermissions();
// Determine whether user has at least one of the given permissions
for (ObjectPermission.Type permission : permissions) {
ConnectionPermission connectionPermission = new ConnectionPermission(permission, identifier);
if (user.hasPermission(connectionPermission))
if (connectionPermissions.hasPermission(permission, identifier))
return true;
}
// None of the given permissions were present
@@ -137,13 +137,13 @@ public class ConnectionGroupRESTService {
private boolean hasConnectionGroupPermission(User user, String identifier,
List<ObjectPermission.Type> permissions) throws GuacamoleException {
// Retrieve connection group permissions
ObjectPermissionSet<String> connectionGroupPermissions = user.getConnectionGroupPermissions();
// Determine whether user has at least one of the given permissions
for (ObjectPermission.Type permission : permissions) {
ConnectionGroupPermission connectionGroupPermission = new ConnectionGroupPermission(permission, identifier);
if (user.hasPermission(connectionGroupPermission))
if (connectionGroupPermissions.hasPermission(permission, identifier))
return true;
}
// None of the given permissions were present
@@ -185,7 +185,8 @@ public class ConnectionGroupRESTService {
User self = userContext.self();
// An admin user has access to any connection or connection group
boolean isAdmin = self.hasPermission(new SystemPermission(SystemPermission.Type.ADMINISTER));
SystemPermissionSet systemPermissions = self.getSystemPermissions();
boolean isAdmin = systemPermissions.hasPermission(SystemPermission.Type.ADMINISTER);
// Retrieve specified connection group
ConnectionGroup connectionGroup;

View File

@@ -24,17 +24,14 @@ package org.glyptodon.guacamole.net.basic.rest.permission;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleServerException;
import org.glyptodon.guacamole.net.auth.permission.ConnectionGroupPermission;
import org.glyptodon.guacamole.net.auth.permission.ConnectionPermission;
import org.glyptodon.guacamole.net.auth.User;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermission;
import org.glyptodon.guacamole.net.auth.permission.Permission;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
import org.glyptodon.guacamole.net.auth.permission.SystemPermission;
import org.glyptodon.guacamole.net.auth.permission.UserPermission;
import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
/**
* The set of permissions which are granted to a specific user, organized by
@@ -50,97 +47,26 @@ public class APIPermissionSet {
/**
* Map of connection ID to the set of granted permissions.
*/
private Map<String, EnumSet<ObjectPermission.Type>> connectionPermissions = new HashMap<String, EnumSet<ObjectPermission.Type>>();
private Map<String, Set<ObjectPermission.Type>> connectionPermissions =
new HashMap<String, Set<ObjectPermission.Type>>();
/**
* Map of connection group ID to the set of granted permissions.
*/
private Map<String, EnumSet<ObjectPermission.Type>> connectionGroupPermissions = new HashMap<String, EnumSet<ObjectPermission.Type>>();
private Map<String, Set<ObjectPermission.Type>> connectionGroupPermissions =
new HashMap<String, Set<ObjectPermission.Type>>();
/**
* Map of user ID to the set of granted permissions.
*/
private Map<String, EnumSet<ObjectPermission.Type>> userPermissions = new HashMap<String, EnumSet<ObjectPermission.Type>>();
private Map<String, Set<ObjectPermission.Type>> userPermissions =
new HashMap<String, Set<ObjectPermission.Type>>();
/**
* Set of all granted system-level permissions.
*/
private EnumSet<SystemPermission.Type> systemPermissions = EnumSet.noneOf(SystemPermission.Type.class);
/**
* Adds the given object permission to the given map of object identifier
* to permission set.
*
* @param permissions
* The map to add the given permission to.
*
* @param permission
* The permission to add.
*/
private void addPermission(Map<String, EnumSet<ObjectPermission.Type>> permissions, ObjectPermission<String> permission) {
// Pull set of permissions for given object
String id = permission.getObjectIdentifier();
EnumSet<ObjectPermission.Type> types = permissions.get(id);
// If set does not yet exist, create it
if (types == null) {
types = EnumSet.of(permission.getType());
permissions.put(id, types);
}
// Otherwise, add the specified permission
else
types.add(permission.getType());
}
/**
* Adds the given system-level permission to the given set of granted
* system permissions.
*
* @param permissions
* The set of system permissions to add the given permission to.
*
* @param permission
* The permission to add.
*/
private void addPermission(EnumSet<SystemPermission.Type> permissions, SystemPermission permission) {
permissions.add(permission.getType());
}
/**
* Adds the given permission to the appropriate type-specific set or map of
* permissions based on the permission class. Only connection, connection
* group, user, and system permissions are supported. Unsupported
* permission types will result in a GuacamoleException being thrown.
*
* @param permission The permission to add.
* @throws GuacamoleException If the permission is of an unsupported type.
*/
private void addPermission(Permission<?> permission) throws GuacamoleException {
// Connection permissions
if (permission instanceof ConnectionPermission)
addPermission(connectionPermissions, (ConnectionPermission) permission);
// Connection group permissions
else if (permission instanceof ConnectionGroupPermission)
addPermission(connectionGroupPermissions, (ConnectionGroupPermission) permission);
// User permissions
else if (permission instanceof UserPermission)
addPermission(userPermissions, (UserPermission) permission);
// System permissions
else if (permission instanceof SystemPermission)
addPermission(systemPermissions, (SystemPermission) permission);
// Unknown / unsupported permission type
else
throw new GuacamoleServerException("Serialization of permission type \"" + permission.getClass() + "\" not implemented.");
}
private Set<SystemPermission.Type> systemPermissions =
EnumSet.noneOf(SystemPermission.Type.class);
/**
* Creates a new permission set which contains no granted permissions. Any
@@ -151,37 +77,83 @@ public class APIPermissionSet {
}
/**
* Creates a new permission set having the given permissions.
* Adds the system permissions from the given SystemPermissionSet to the
* Set of system permissions provided.
*
* @param permissions
* The permissions to initially store within the permission set.
* The Set to add system permissions to.
*
* @param permSet
* The SystemPermissionSet containing the system permissions to add.
*
* @throws GuacamoleException
* If any of the given permissions are of an unsupported type.
* If an error occurs while retrieving system permissions from the
* SystemPermissionSet.
*/
public APIPermissionSet(Iterable<Permission> permissions) throws GuacamoleException {
private void addSystemPermissions(Set<SystemPermission.Type> permissions,
SystemPermissionSet permSet) throws GuacamoleException {
// Add all provided permissions
for (Permission permission : permissions)
addPermission(permission);
// Add all provided system permissions
for (SystemPermission permission : permSet.getPermissions())
permissions.add(permission.getType());
}
/**
* Creates a new permission set having the given permissions.
* Adds the object permissions from the given ObjectPermissionSet to the
* Map of object permissions provided.
*
* @param permissions
* The permissions to initially store within the permission set.
* The Map to add object permissions to.
*
* @param permSet
* The ObjectPermissionSet containing the object permissions to add.
*
* @throws GuacamoleException
* If any of the given permissions are of an unsupported type.
* If an error occurs while retrieving object permissions from the
* ObjectPermissionSet.
*/
public APIPermissionSet(Permission... permissions) throws GuacamoleException {
private void addObjectPermissions(Map<String, Set<ObjectPermission.Type>> permissions,
ObjectPermissionSet<String> permSet) throws GuacamoleException {
// Add all provided permissions
for (Permission permission : permissions)
addPermission(permission);
// Add all provided object permissions
for (ObjectPermission<String> permission : permSet.getPermissions()) {
// Get associated set of permissions
String identifier = permission.getObjectIdentifier();
Set<ObjectPermission.Type> objectPermissions = permissions.get(identifier);
// Create new set if none yet exists
if (objectPermissions == null)
permissions.put(identifier, EnumSet.of(permission.getType()));
// Otherwise add to existing set
else
objectPermissions.add(permission.getType());
}
}
/**
* Creates a new permission set containing all permissions currently
* granted to the given user.
*
* @param user
* The user whose permissions should be stored within this permission
* set.
*
* @throws GuacamoleException
* If an error occurs while retrieving the user's permissions.
*/
public APIPermissionSet(User user) throws GuacamoleException {
// Add all permissions from the provided user
addSystemPermissions(systemPermissions, user.getSystemPermissions());
addObjectPermissions(connectionPermissions, user.getConnectionPermissions());
addObjectPermissions(connectionGroupPermissions, user.getConnectionGroupPermissions());
addObjectPermissions(userPermissions, user.getUserPermissions());
}
/**
@@ -195,7 +167,7 @@ public class APIPermissionSet {
* A map of connection IDs to the set of permissions granted for that
* connection.
*/
public Map<String, EnumSet<ObjectPermission.Type>> getConnectionPermissions() {
public Map<String, Set<ObjectPermission.Type>> getConnectionPermissions() {
return connectionPermissions;
}
@@ -210,7 +182,7 @@ public class APIPermissionSet {
* A map of connection group IDs to the set of permissions granted for
* that connection group.
*/
public Map<String, EnumSet<ObjectPermission.Type>> getConnectionGroupPermissions() {
public Map<String, Set<ObjectPermission.Type>> getConnectionGroupPermissions() {
return connectionGroupPermissions;
}
@@ -223,7 +195,7 @@ public class APIPermissionSet {
* @return
* A map of user IDs to the set of permissions granted for that user.
*/
public Map<String, EnumSet<ObjectPermission.Type>> getUserPermissions() {
public Map<String, Set<ObjectPermission.Type>> getUserPermissions() {
return userPermissions;
}
@@ -236,7 +208,7 @@ public class APIPermissionSet {
* @return
* The set of granted system-level permissions.
*/
public EnumSet<SystemPermission.Type> getSystemPermissions() {
public Set<SystemPermission.Type> getSystemPermissions() {
return systemPermissions;
}
@@ -249,7 +221,7 @@ public class APIPermissionSet {
* @param connectionPermissions
* The map which must replace the currently-stored map of permissions.
*/
public void setConnectionPermissions(Map<String, EnumSet<ObjectPermission.Type>> connectionPermissions) {
public void setConnectionPermissions(Map<String, Set<ObjectPermission.Type>> connectionPermissions) {
this.connectionPermissions = connectionPermissions;
}
@@ -262,7 +234,7 @@ public class APIPermissionSet {
* @param connectionGroupPermissions
* The map which must replace the currently-stored map of permissions.
*/
public void setConnectionGroupPermissions(Map<String, EnumSet<ObjectPermission.Type>> connectionGroupPermissions) {
public void setConnectionGroupPermissions(Map<String, Set<ObjectPermission.Type>> connectionGroupPermissions) {
this.connectionGroupPermissions = connectionGroupPermissions;
}
@@ -274,7 +246,7 @@ public class APIPermissionSet {
* @param userPermissions
* The map which must replace the currently-stored map of permissions.
*/
public void setUserPermissions(Map<String, EnumSet<ObjectPermission.Type>> userPermissions) {
public void setUserPermissions(Map<String, Set<ObjectPermission.Type>> userPermissions) {
this.userPermissions = userPermissions;
}
@@ -286,7 +258,7 @@ public class APIPermissionSet {
* @param systemPermissions
* The set which must replace the currently-stored set of permissions.
*/
public void setSystemPermissions(EnumSet<SystemPermission.Type> systemPermissions) {
public void setSystemPermissions(Set<SystemPermission.Type> systemPermissions) {
this.systemPermissions = systemPermissions;
}

View File

@@ -22,15 +22,17 @@
package org.glyptodon.guacamole.net.basic.rest.user;
import java.util.Collections;
import java.util.Set;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleUnsupportedException;
import org.glyptodon.guacamole.net.auth.User;
import org.glyptodon.guacamole.net.auth.permission.Permission;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
/**
* A wrapper to make an APIConnection look like a User. Useful where a
* org.glyptodon.guacamole.net.auth.User is required.
* A wrapper to make an APIUser look like a User. Useful where an
* org.glyptodon.guacamole.net.auth.User is required. As a simple wrapper for
* APIUser, access to permissions is not provided. Any attempt to access or
* manipulate permissions on an APIUserWrapper will result in an exception.
*
* @author James Muehlner
*/
@@ -41,12 +43,6 @@ public class APIUserWrapper implements User {
*/
private final 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.
@@ -55,16 +51,6 @@ public class APIUserWrapper implements User {
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();
@@ -86,23 +72,27 @@ public class APIUserWrapper implements User {
}
@Override
public Set<Permission> getPermissions() throws GuacamoleException {
return permissionSet;
public SystemPermissionSet getSystemPermissions()
throws GuacamoleException {
throw new GuacamoleUnsupportedException("APIUserWrapper does not provide permission access.");
}
@Override
public boolean hasPermission(Permission permission) throws GuacamoleException {
return permissionSet.contains(permission);
public ObjectPermissionSet<String> getConnectionPermissions()
throws GuacamoleException {
throw new GuacamoleUnsupportedException("APIUserWrapper does not provide permission access.");
}
@Override
public void addPermission(Permission permission) throws GuacamoleException {
throw new UnsupportedOperationException("Operation not supported.");
public ObjectPermissionSet<String> getConnectionGroupPermissions()
throws GuacamoleException {
throw new GuacamoleUnsupportedException("APIUserWrapper does not provide permission access.");
}
@Override
public void removePermission(Permission permission) throws GuacamoleException {
throw new UnsupportedOperationException("Operation not supported.");
public ObjectPermissionSet<String> getUserPermissions()
throws GuacamoleException {
throw new GuacamoleUnsupportedException("APIUserWrapper does not provide permission access.");
}
}

View File

@@ -24,6 +24,7 @@ package org.glyptodon.guacamole.net.basic.rest.user;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import javax.ws.rs.Consumes;
@@ -43,12 +44,12 @@ import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
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.auth.permission.ConnectionGroupPermission;
import org.glyptodon.guacamole.net.auth.permission.ConnectionPermission;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermission;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
import org.glyptodon.guacamole.net.auth.permission.Permission;
import org.glyptodon.guacamole.net.auth.permission.PermissionSet;
import org.glyptodon.guacamole.net.auth.permission.SystemPermission;
import org.glyptodon.guacamole.net.auth.permission.UserPermission;
import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
import org.glyptodon.guacamole.net.basic.rest.APIPatch;
import static org.glyptodon.guacamole.net.basic.rest.APIPatch.Operation.add;
import static org.glyptodon.guacamole.net.basic.rest.APIPatch.Operation.remove;
@@ -132,13 +133,13 @@ public class UserRESTService {
private boolean hasUserPermission(User user, String username,
List<ObjectPermission.Type> permissions) throws GuacamoleException {
// Retrieve user permissions
ObjectPermissionSet<String> userPermissions = user.getUserPermissions();
// Determine whether user has at least one of the given permissions
for (ObjectPermission.Type permission : permissions) {
UserPermission userPermission = new UserPermission(permission, username);
if (user.hasPermission(userPermission))
if (userPermissions.hasPermission(permission, username))
return true;
}
// None of the given permissions were present
@@ -181,7 +182,8 @@ public class UserRESTService {
permissions = null;
// An admin user has access to any user
boolean isAdmin = self.hasPermission(new SystemPermission(SystemPermission.Type.ADMINISTER));
SystemPermissionSet systemPermissions = self.getSystemPermissions();
boolean isAdmin = systemPermissions.hasPermission(SystemPermission.Type.ADMINISTER);
// Get the directory
Directory<String, User> userDirectory = userContext.getUserDirectory();
@@ -379,7 +381,53 @@ public class UserRESTService {
throw new GuacamoleResourceNotFoundException("No such user: \"" + username + "\"");
}
return new APIPermissionSet(user.getPermissions());
return new APIPermissionSet(user);
}
/**
* Updates the given permission set by adding or removing 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 permissionSet
* The permission set being modified.
*
* @param permission
* The permission being added or removed from the set.
*
* @throws GuacamoleException
* If an error occurs while modifying the permission set.
*/
private <PermissionType extends Permission> void updatePermissionSet(
APIPatch.Operation operation,
PermissionSet<PermissionType> permissionSet,
PermissionType permission) throws GuacamoleException {
// Add or remove permission based on operation
switch (operation) {
// Add permission
case add:
permissionSet.addPermissions(Collections.singleton(permission));
break;
// Remove permission
case remove:
permissionSet.removePermissions(Collections.singleton(permission));
break;
// Unsupported patch operation
default:
throw new HTTPException(Status.BAD_REQUEST,
"Unsupported patch operation: \"" + operation + "\"");
}
}
@@ -423,8 +471,6 @@ public class UserRESTService {
// Apply all patch operations individually
for (APIPatch<String> patch : patches) {
Permission permission;
String path = patch.getPath();
// Create connection permission if path has connection prefix
@@ -434,8 +480,9 @@ public class UserRESTService {
String identifier = path.substring(CONNECTION_PERMISSION_PATCH_PATH_PREFIX.length());
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
// Create corresponding permission
permission = new ConnectionPermission(type, identifier);
// Create and update corresponding permission
ObjectPermission<String> permission = new ObjectPermission<String>(type, identifier);
updatePermissionSet(patch.getOp(), user.getConnectionPermissions(), permission);
}
@@ -446,8 +493,9 @@ public class UserRESTService {
String identifier = path.substring(CONNECTION_GROUP_PERMISSION_PATCH_PATH_PREFIX.length());
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
// Create corresponding permission
permission = new ConnectionGroupPermission(type, identifier);
// Create and update corresponding permission
ObjectPermission<String> permission = new ObjectPermission<String>(type, identifier);
updatePermissionSet(patch.getOp(), user.getConnectionGroupPermissions(), permission);
}
@@ -458,9 +506,10 @@ public class UserRESTService {
String identifier = path.substring(USER_PERMISSION_PATCH_PATH_PREFIX.length());
ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue());
// Create corresponding permission
permission = new UserPermission(type, identifier);
// Create and update corresponding permission
ObjectPermission<String> permission = new ObjectPermission<String>(type, identifier);
updatePermissionSet(patch.getOp(), user.getUserPermissions(), permission);
}
// Create system permission if path is system path
@@ -469,8 +518,9 @@ public class UserRESTService {
// Get identifier and type from patch operation
SystemPermission.Type type = SystemPermission.Type.valueOf(patch.getValue());
// Create corresponding permission
permission = new SystemPermission(type);
// Create and update corresponding permission
SystemPermission permission = new SystemPermission(type);
updatePermissionSet(patch.getOp(), user.getSystemPermissions(), permission);
}
@@ -478,26 +528,6 @@ public class UserRESTService {
else
throw new HTTPException(Status.BAD_REQUEST, "Unsupported patch path: \"" + path + "\"");
// Add or remove permission based on operation
switch (patch.getOp()) {
// Add permission
case add:
user.addPermission(permission);
break;
// Remove permission
case remove:
user.removePermission(permission);
break;
// Unsupported patch operation
default:
throw new HTTPException(Status.BAD_REQUEST,
"Unsupported patch operation: \"" + patch.getOp() + "\"");
}
} // end for each patch operation
// Save the permission changes