From 9f7d0d64c7073f0adab2a06627dbed369e79011a Mon Sep 17 00:00:00 2001
From: Mike McKay <mfmckay@wustl.edu>
Date: Thu, 5 May 2016 17:38:50 -0500
Subject: [PATCH] Added support for a bunch of new user REST calls to support
 the UI Will designed.

---
 .../java/org/nrg/xapi/model/users/User.java   |  30 +++
 .../org/nrg/xapi/rest/users/UsersApi.java     | 250 ++++++++++++++++--
 2 files changed, 256 insertions(+), 24 deletions(-)

diff --git a/src/main/java/org/nrg/xapi/model/users/User.java b/src/main/java/org/nrg/xapi/model/users/User.java
index 862323e0..1e88b433 100644
--- a/src/main/java/org/nrg/xapi/model/users/User.java
+++ b/src/main/java/org/nrg/xapi/model/users/User.java
@@ -33,6 +33,8 @@ public class User {
         _salt = "";
         _lastModified = null;
         _authorization = null;
+        _isEnabled = user.isEnabled();
+        _isVerified = user.isVerified();
     }
 
     public User(final XdatUser user) {
@@ -119,6 +121,32 @@ public class User {
         _isAdmin = isAdmin;
     }
 
+    /**
+     * Whether the user is enabled.
+     **/
+    @ApiModelProperty(value = "Whether the user is enabled.")
+    @JsonProperty("enabled")
+    public boolean isEnabled() {
+        return _isEnabled;
+    }
+
+    public void setEnabled(final boolean isEnabled) {
+        _isEnabled = isEnabled;
+    }
+
+    /**
+     * Whether the user is verified.
+     **/
+    @ApiModelProperty(value = "Whether the user is verified.")
+    @JsonProperty("verified")
+    public boolean isVerified() {
+        return _isVerified;
+    }
+
+    public void setVerified(final boolean isVerified) {
+        _isVerified = isVerified;
+    }
+
     /**
      * The user's primary database (deprecated).
      **/
@@ -243,4 +271,6 @@ public class User {
     private String _salt = null;
     private Date _lastModified = null;
     private UserAuth _authorization = null;
+    private boolean _isEnabled;
+    private boolean _isVerified;
 }
diff --git a/src/main/java/org/nrg/xapi/rest/users/UsersApi.java b/src/main/java/org/nrg/xapi/rest/users/UsersApi.java
index 0b2cc17a..5dec06a5 100644
--- a/src/main/java/org/nrg/xapi/rest/users/UsersApi.java
+++ b/src/main/java/org/nrg/xapi/rest/users/UsersApi.java
@@ -5,10 +5,15 @@ import org.apache.commons.lang3.StringUtils;
 import org.nrg.framework.annotations.XapiRestController;
 import org.nrg.xapi.model.users.User;
 import org.nrg.xapi.rest.NotFoundException;
+import org.nrg.xdat.XDAT;
 import org.nrg.xdat.rest.AbstractXnatRestApi;
+import org.nrg.xdat.security.UserGroupI;
+import org.nrg.xdat.security.helpers.Groups;
+import org.nrg.xdat.security.helpers.Roles;
 import org.nrg.xdat.security.helpers.Users;
 import org.nrg.xdat.security.user.exceptions.UserInitException;
 import org.nrg.xdat.security.user.exceptions.UserNotFoundException;
+import org.nrg.xdat.services.AliasTokenService;
 import org.nrg.xft.event.EventDetails;
 import org.nrg.xft.event.EventUtils;
 import org.nrg.xft.security.UserI;
@@ -19,8 +24,7 @@ import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
 
 @Api(description = "The XNAT POC User Management API")
 @XapiRestController
@@ -36,9 +40,35 @@ public class UsersApi extends AbstractXnatRestApi {
         return new ResponseEntity<List<String>>(new ArrayList<>(Users.getAllLogins()), HttpStatus.OK);
     }
 
+    @ApiOperation(value = "Get list of user profiles.", notes = "The users' profiles function returns a list of all users of the XNAT system with brief information about each.", response = User.class, responseContainer = "List")
+    @ApiResponses({@ApiResponse(code = 200, message = "An array of user profiles"), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(value = {"/profiles"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET)
+    @ResponseBody
+    public ResponseEntity<List<Map<String,String>>> usersProfilesGet() {
+        List<UserI> users = Users.getUsers();
+        List<Map<String,String>> userMaps = new ArrayList<Map<String,String>>();
+        for(UserI user : users){
+            try{
+                Map<String,String> userMap = new HashMap<String,String>();
+                userMap.put("firstname",user.getFirstname());
+                userMap.put("lastname",user.getLastname());
+                userMap.put("username",user.getUsername());
+                userMap.put("email",user.getEmail());
+                userMap.put("id",String.valueOf(user.getID()));
+                userMap.put("enabled",String.valueOf(user.isEnabled()));
+                userMap.put("verified",String.valueOf(user.isVerified()));
+                userMaps.add(userMap);
+            }
+            catch(Exception e){
+                _log.error("", e);
+            }
+        }
+        return new ResponseEntity<List<Map<String,String>>>(userMaps, HttpStatus.OK);
+    }
+
     @ApiOperation(value = "Gets the user with the specified user ID.", notes = "Returns the serialized user object with the specified user ID.", response = User.class)
     @ApiResponses({@ApiResponse(code = 200, message = "User successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to view this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")})
-    @RequestMapping(value = {"/{id}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
+    @RequestMapping(value = {"/profiles/{id}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<User> usersIdGet(@ApiParam(value = "ID of the user to fetch", required = true) @PathVariable("id") String id) {
         HttpStatus status = isPermitted(id);
         if (status != null) {
@@ -56,38 +86,49 @@ public class UsersApi extends AbstractXnatRestApi {
         }
     }
 
-    @ApiOperation(value = "Creates or updates the user object with the specified user ID.", notes = "Returns the updated serialized user object with the specified user ID.", response = User.class)
+    @ApiOperation(value = "Creates or updates the user object with the specified username.", notes = "Returns the updated serialized user object with the specified username.", response = User.class)
     @ApiResponses({@ApiResponse(code = 200, message = "User successfully created or updated."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to create or update this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")})
-    @RequestMapping(value = {"/{id}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
-    public ResponseEntity<User> usersIdPut(@ApiParam(value = "The ID of the user to create or update.", required = true) @PathVariable("id") String id, @RequestBody User model) throws NotFoundException {
-        HttpStatus status = isPermitted(id);
+    @RequestMapping(value = {"/profiles/{id}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
+    public ResponseEntity<User> usersIdPut(@ApiParam(value = "The username of the user to create or update.", required = true) @PathVariable("id") String username, @RequestBody User model) throws NotFoundException {
+        HttpStatus status = isPermitted(username);
         if (status != null) {
             return new ResponseEntity<>(status);
         }
-        final UserI user;
+        UserI user = null;
         try {
-            user = Users.getUser(id);
-        } catch (UserInitException e) {
-            _log.error("An error occurred initializing the user " + id, e);
-            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
-        } catch (UserNotFoundException e) {
-            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+            user = Users.getUser(username);
+        } catch (Exception e) {
+            user = null;
         }
         if (user == null) {
-            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+            //Create new User
+            user = Users.createUser();
+            user.setLogin(username);
         }
-        if ((StringUtils.isNotBlank(model.getFirstName())) && (StringUtils.equals(model.getFirstName(), user.getFirstname()))) {
+        if ((StringUtils.isNotBlank(model.getFirstName())) && (!StringUtils.equals(model.getFirstName(), user.getFirstname()))) {
             user.setFirstname(model.getFirstName());
         }
-        if ((StringUtils.isNotBlank(model.getLastName())) && (StringUtils.equals(model.getLastName(), user.getLastname()))) {
+        if ((StringUtils.isNotBlank(model.getLastName())) && (!StringUtils.equals(model.getLastName(), user.getLastname()))) {
             user.setLastname(model.getLastName());
         }
-        if ((StringUtils.isNotBlank(model.getEmail())) && (StringUtils.equals(model.getEmail(), user.getEmail()))) {
+        if ((StringUtils.isNotBlank(model.getEmail())) && (!StringUtils.equals(model.getEmail(), user.getEmail()))) {
             user.setEmail(model.getEmail());
         }
-        if (StringUtils.isNotBlank(model.getPassword())) {
-            user.setPassword(model.getPassword());
+        if (model.isEnabled()!=user.isEnabled()) {
+            user.setEnabled(model.isEnabled());
+            if(!model.isEnabled()){
+                //When a user is disabled, deactivate all their AliasTokens
+                try {
+                    XDAT.getContextService().getBean(AliasTokenService.class).deactivateAllTokensForUser(user.getLogin());
+                } catch (Exception e) {
+                    _log.error("", e);
+                }
+            }
+        }
+        if (model.isVerified()!=user.isVerified()) {
+            user.setVerified(model.isVerified());
         }
+
         try {
             Users.save(user, getSessionUser(), false, new EventDetails(EventUtils.CATEGORY.DATA, EventUtils.TYPE.WEB_SERVICE, Event.Modified, "", ""));
             return new ResponseEntity<>(HttpStatus.OK);
@@ -99,7 +140,7 @@ public class UsersApi extends AbstractXnatRestApi {
 
     @ApiOperation(value = "Returns whether the user with the specified user ID is enabled.", notes = "Returns true or false based on whether the specified user is enabled or not.", response = Boolean.class)
     @ApiResponses({@ApiResponse(code = 200, message = "User enabled status successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to view this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")})
-    @RequestMapping(value = {"/{id}/enabled"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
+    @RequestMapping(value = {"/profiles/{id}/enabled"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<Boolean> usersIdEnabledGet(@ApiParam(value = "The ID of the user to retrieve the enabled status for.", required = true) @PathVariable("id") String id) {
         HttpStatus status = isPermitted(id);
         if (status != null) {
@@ -121,7 +162,7 @@ public class UsersApi extends AbstractXnatRestApi {
 
     @ApiOperation(value = "Sets the user's enabled state.", notes = "Sets the enabled state of the user with the specified user ID to the value of the flag parameter.", response = Boolean.class)
     @ApiResponses({@ApiResponse(code = 200, message = "User enabled status successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")})
-    @RequestMapping(value = {"/{id}/enabled/{flag}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
+    @RequestMapping(value = {"/profiles/{id}/enabled/{flag}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
     public ResponseEntity<Boolean> usersIdEnabledFlagPut(@ApiParam(value = "ID of the user to fetch", required = true) @PathVariable("id") String id, @ApiParam(value = "The value to set for the enabled status.", required = true) @PathVariable("flag") Boolean flag) {
         HttpStatus status = isPermitted(id);
         if (status != null) {
@@ -150,7 +191,7 @@ public class UsersApi extends AbstractXnatRestApi {
 
     @ApiOperation(value = "Returns whether the user with the specified user ID is verified.", notes = "Returns true or false based on whether the specified user is verified or not.", response = Boolean.class)
     @ApiResponses({@ApiResponse(code = 200, message = "User verified status successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to view this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")})
-    @RequestMapping(value = {"/{id}/verified"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
+    @RequestMapping(value = {"/profiles/{id}/verified"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
     public ResponseEntity<Boolean> usersIdVerifiedGet(@ApiParam(value = "The ID of the user to retrieve the verified status for.", required = true) @PathVariable("id") String id) {
         HttpStatus status = isPermitted(id);
         if (status != null) {
@@ -172,7 +213,7 @@ public class UsersApi extends AbstractXnatRestApi {
 
     @ApiOperation(value = "Sets the user's verified state.", notes = "Sets the verified state of the user with the specified user ID to the value of the flag parameter.", response = Boolean.class)
     @ApiResponses({@ApiResponse(code = 200, message = "User verified status successfully set."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to verify or un-verify this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")})
-    @RequestMapping(value = {"/{id}/verified/{flag}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
+    @RequestMapping(value = {"/profiles/{id}/verified/{flag}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
     public ResponseEntity<Boolean> usersIdVerifiedFlagPut(@ApiParam(value = "ID of the user to fetch", required = true) @PathVariable("id") String id, @ApiParam(value = "The value to set for the verified status.", required = true) @PathVariable("flag") Boolean flag) {
         HttpStatus status = isPermitted(id);
         if (status != null) {
@@ -199,6 +240,167 @@ public class UsersApi extends AbstractXnatRestApi {
         }
     }
 
+    @ApiOperation(value = "Returns the roles for the user with the specified user ID.", notes = "Returns a collection of the user's roles.", response = Collection.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "User roles successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to view this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(value = {"/profiles/{id}/roles"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
+    public ResponseEntity<Collection<String>> usersIdRolesGet(@ApiParam(value = "The ID of the user to retrieve the roles for.", required = true) @PathVariable("id") String id) {
+        HttpStatus status = isPermitted(id);
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+        try {
+            final UserI user = Users.getUser(id);
+            if (user == null) {
+                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+            }
+            Collection<String> roles =  Roles.getRoles(user);
+            return new ResponseEntity<>(roles, HttpStatus.OK);
+        } catch (UserInitException e) {
+            _log.error("An error occurred initializing the user " + id, e);
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        } catch (UserNotFoundException e) {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+    }
+
+    @ApiOperation(value = "Adds a role to a user.", notes = "Assigns a new role to a user.", response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "User role successfully added."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(value = {"/profiles/{id}/addrole/{role}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
+    public ResponseEntity<Boolean> usersIdAddRole(@ApiParam(value = "ID of the user to add a role to", required = true) @PathVariable("id") String id, @ApiParam(value = "The user's new role.", required = true) @PathVariable("role") String role) {
+        HttpStatus status = isPermitted(id);
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+        try {
+            final UserI user = Users.getUser(id);
+            if (user == null) {
+                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+            }
+            try {
+                Roles.addRole(getSessionUser(),user,role);
+                return new ResponseEntity<>(HttpStatus.OK);
+            } catch (Exception e) {
+                _log.error("Error occurred adding role "+role+" to user " + user.getLogin()+".");
+            }
+            return new ResponseEntity<>(false, HttpStatus.INTERNAL_SERVER_ERROR);
+        } catch (UserInitException e) {
+            _log.error("An error occurred initializing the user " + id, e);
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        } catch (UserNotFoundException e) {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+    }
+
+
+    @ApiOperation(value = "Remove a user's role.", notes = "Removes a user's role.", response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "User role successfully removed."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(value = {"/profiles/{id}/removerole/{role}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
+    public ResponseEntity<Boolean> usersIdRemoveRole(@ApiParam(value = "ID of the user to delete a role from", required = true) @PathVariable("id") String id, @ApiParam(value = "The user role to delete.", required = true) @PathVariable("role") String role) {
+        HttpStatus status = isPermitted(id);
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+        try {
+            final UserI user = Users.getUser(id);
+            if (user == null) {
+                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+            }
+            try {
+                Roles.deleteRole(getSessionUser(),user,role);
+                return new ResponseEntity<>(HttpStatus.OK);
+            } catch (Exception e) {
+                _log.error("Error occurred removing role "+role+" from user " + user.getLogin()+".");
+            }
+            return new ResponseEntity<>(false, HttpStatus.INTERNAL_SERVER_ERROR);
+        } catch (UserInitException e) {
+            _log.error("An error occurred initializing the user " + id, e);
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        } catch (UserNotFoundException e) {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+    }
+
+    @ApiOperation(value = "Returns the groups for the user with the specified user ID.", notes = "Returns a collection of the user's groups.", response = Set.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "User groups successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to view this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(value = {"/profiles/{id}/groups"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET})
+    public ResponseEntity<Set<String>> usersIdGroupsGet(@ApiParam(value = "The ID of the user to retrieve the groups for.", required = true) @PathVariable("id") String id) {
+        HttpStatus status = isPermitted(id);
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+        try {
+            final UserI user = Users.getUser(id);
+            if (user == null) {
+                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+            }
+            Map<String, UserGroupI> groups = Groups.getGroupsForUser(user);
+            return new ResponseEntity<>(groups.keySet(), HttpStatus.OK);
+        } catch (UserInitException e) {
+            _log.error("An error occurred initializing the user " + id, e);
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        } catch (UserNotFoundException e) {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+    }
+
+    @ApiOperation(value = "Adds a user to a group.", notes = "Assigns user to a group.", response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "User successfully added to group."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(value = {"/profiles/{id}/addgroup/{group}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
+    public ResponseEntity<Boolean> usersIdAddGroup(@ApiParam(value = "ID of the user to add to a group", required = true) @PathVariable("id") String id, @ApiParam(value = "The user's new group.", required = true) @PathVariable("group") String group) {
+        HttpStatus status = isPermitted(id);
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+        try {
+            final UserI user = Users.getUser(id);
+            if (user == null) {
+                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+            }
+            try {
+                Groups.addUserToGroup(group, user, getSessionUser(),null);
+                return new ResponseEntity<>(HttpStatus.OK);
+            } catch (Exception e) {
+                _log.error("Error occurred adding user " + user.getLogin()+ " to group "+group+".");
+            }
+            return new ResponseEntity<>(false, HttpStatus.INTERNAL_SERVER_ERROR);
+        } catch (UserInitException e) {
+            _log.error("An error occurred initializing the user " + id, e);
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        } catch (UserNotFoundException e) {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+    }
+
+
+    @ApiOperation(value = "Removes a user from a group.", notes = "Removes a user from a group.", response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "User's group successfully removed."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "Unexpected error")})
+    @RequestMapping(value = {"/profiles/{id}/removegroup/{group}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT})
+    public ResponseEntity<Boolean> usersIdRemoveGroup(@ApiParam(value = "ID of the user to remove from group", required = true) @PathVariable("id") String id, @ApiParam(value = "The group to remove the user from.", required = true) @PathVariable("group") String group) {
+        HttpStatus status = isPermitted(id);
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+        try {
+            final UserI user = Users.getUser(id);
+            if (user == null) {
+                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+            }
+            try {
+                Groups.removeUserFromGroup(user, group, null);
+                return new ResponseEntity<>(HttpStatus.OK);
+            } catch (Exception e) {
+                _log.error("Error occurred removing user "+user.getLogin()+" from group " + group+".");
+            }
+            return new ResponseEntity<>(false, HttpStatus.INTERNAL_SERVER_ERROR);
+        } catch (UserInitException e) {
+            _log.error("An error occurred initializing the user " + id, e);
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        } catch (UserNotFoundException e) {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+    }
+
+
     @SuppressWarnings("unused")
     public static class Event {
         public static String Added                 = "Added User";
-- 
GitLab