From 6ce89971c6080f270796eef0820ae607baa894c2 Mon Sep 17 00:00:00 2001 From: Rick Herrick <jrherrick@wustl.edu> Date: Tue, 6 Sep 2016 16:17:20 -0500 Subject: [PATCH] XNAT-2840 XNAT-4394 XNAT-4500 Added REST API functions to list and invalidate user sessions. Fixed investigator initialization error. Fixed password handling for new user operations. --- .../java/org/nrg/xapi/model/users/User.java | 51 ++-- .../org/nrg/xapi/rest/data/Investigator.java | 46 +-- .../nrg/xapi/rest/data/InvestigatorsApi.java | 1 - .../org/nrg/xapi/rest/users/UsersApi.java | 271 ++++++++++++++---- 4 files changed, 264 insertions(+), 105 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 53d039c0..b8b7574e 100644 --- a/src/main/java/org/nrg/xapi/model/users/User.java +++ b/src/main/java/org/nrg/xapi/model/users/User.java @@ -1,20 +1,19 @@ package org.nrg.xapi.model.users; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; -import org.apache.commons.lang3.StringUtils; +import org.nrg.xdat.entities.UserAuthI; import org.nrg.xdat.om.XdatUser; -import org.nrg.xdat.om.base.auto.AutoXdatUser; import org.nrg.xdat.security.XDATUser; import org.nrg.xft.security.UserI; import java.util.Date; @ApiModel(description = "Contains the properties that define a user on the system.") -@JsonIgnoreProperties(value = {"FullName", "Password", "Salt", "XdatUser"}, ignoreUnknown = true) public class User { public User() { + // Nothing to see here... } public User(final String username) { @@ -28,11 +27,11 @@ public class User { _lastName = user.getLastname(); _email = user.getEmail(); _isAdmin = (user instanceof XDATUser && ((XDATUser) user).isSiteAdmin()); - _dbName = ""; - _password = ""; - _salt = ""; - _lastModified = null; - _authorization = null; + _dbName = user.getDBName(); + _password = user.getPassword(); + _salt = user.getSalt(); + _lastModified = user.getLastModified(); + _authorization = user.getAuthorization(); _isEnabled = user.isEnabled(); _isVerified = user.isVerified(); } @@ -143,6 +142,7 @@ public class User { * The user's encrypted password. **/ @ApiModelProperty(value = "The user's encrypted password.") + @JsonIgnore public String getPassword() { return _password; } @@ -155,6 +155,7 @@ public class User { * The _salt used to encrypt the user's _password. **/ @ApiModelProperty(value = "The salt used to encrypt the user's password.") + @JsonIgnore public String getSalt() { return _salt; } @@ -180,23 +181,23 @@ public class User { * The user's authorization record used when logging in. **/ @ApiModelProperty(value = "The user's authorization record used when logging in.") - public UserAuth getAuthorization() { + public UserAuthI getAuthorization() { return _authorization; } @SuppressWarnings("unused") - public void setAuthorization(UserAuth authorization) { + public void setAuthorization(UserAuthI authorization) { _authorization = authorization; } @ApiModelProperty(value = "The user's full name.") + @JsonIgnore public String getFullName() { return String.format("%s %s", getFirstName(), getLastName()); } @Override public String toString() { - return "class User {\n" + " id: " + _id + "\n" + " username: " + _username + "\n" + @@ -211,17 +212,17 @@ public class User { "}\n"; } - private Integer _id = null; - private String _username = null; - private String _firstName = null; - private String _lastName = null; - private String _email = null; - private boolean _isAdmin; - private String _dbName = null; - private String _password = null; - private String _salt = null; - private Date _lastModified = null; - private UserAuth _authorization = null; - private boolean _isEnabled; - private boolean _isVerified; + private Integer _id; + private String _username; + private String _firstName; + private String _lastName; + private String _email; + private String _dbName; + private String _password; + private String _salt; + private Date _lastModified; + private UserAuthI _authorization; + private boolean _isAdmin; + private boolean _isEnabled; + private boolean _isVerified; } diff --git a/src/main/java/org/nrg/xapi/rest/data/Investigator.java b/src/main/java/org/nrg/xapi/rest/data/Investigator.java index aa0d4792..a32b6843 100644 --- a/src/main/java/org/nrg/xapi/rest/data/Investigator.java +++ b/src/main/java/org/nrg/xapi/rest/data/Investigator.java @@ -1,7 +1,6 @@ package org.nrg.xapi.rest.data; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.apache.commons.lang3.NotImplementedException; import org.nrg.xdat.bean.XnatInvestigatordataBean; import org.nrg.xdat.model.XnatInvestigatordataI; import org.nrg.xdat.om.XnatInvestigatordata; @@ -19,6 +18,10 @@ import java.util.*; * associated. */ public class Investigator implements XnatInvestigatordataI { + public Investigator() { + // + } + public Investigator(final XnatInvestigatordataI investigator, final Collection<String> primaryProjects, final Collection<String> investigatorProjects) { _xnatInvestigatordataId = investigator.getXnatInvestigatordataId(); _id = investigator.getId(); @@ -65,7 +68,7 @@ public class Investigator implements XnatInvestigatordataI { @Override public void setTitle(final String title) { - throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + _title = title; } @Override @@ -75,7 +78,7 @@ public class Investigator implements XnatInvestigatordataI { @Override public void setFirstname(final String firstname) { - throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + _firstname = firstname; } @Override @@ -85,7 +88,7 @@ public class Investigator implements XnatInvestigatordataI { @Override public void setLastname(final String lastname) { - throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + _lastname = lastname; } @Override @@ -95,7 +98,7 @@ public class Investigator implements XnatInvestigatordataI { @Override public void setInstitution(final String institution) { - throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + _institution = institution; } @Override @@ -105,7 +108,7 @@ public class Investigator implements XnatInvestigatordataI { @Override public void setDepartment(final String department) { - throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + _department = department; } @Override @@ -115,7 +118,7 @@ public class Investigator implements XnatInvestigatordataI { @Override public void setEmail(final String email) { - throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + _email = email; } @Override @@ -125,7 +128,7 @@ public class Investigator implements XnatInvestigatordataI { @Override public void setPhone(final String phone) { - throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + _phone = phone; } @Override @@ -134,8 +137,8 @@ public class Investigator implements XnatInvestigatordataI { } @Override - public void setId(final String v) { - throw new NotImplementedException("Set methods on this class are not implemented: the class is meant to be read-only and only for reference purposes."); + public void setId(final String id) { + _id = id; } @Override @@ -143,6 +146,11 @@ public class Investigator implements XnatInvestigatordataI { return _xnatInvestigatordataId; } + @SuppressWarnings("unused") + public void setXnatInvestigatordataId(final Integer xnatInvestigatordataId) { + _xnatInvestigatordataId = xnatInvestigatordataId; + } + /** * Gets the list of IDs for projects on which the investigator is the primary investigator. * @@ -199,15 +207,15 @@ public class Investigator implements XnatInvestigatordataI { return Arrays.asList(projectIds); } - private final Integer _xnatInvestigatordataId; - private final String _id; - private final String _title; - private final String _firstname; - private final String _lastname; - private final String _institution; - private final String _department; - private final String _email; - private final String _phone; + private Integer _xnatInvestigatordataId; + private String _id; + private String _title; + private String _firstname; + private String _lastname; + private String _institution; + private String _department; + private String _email; + private String _phone; private final Set<String> _primaryProjects = new HashSet<>(); private final Set<String> _investigatorProjects = new HashSet<>(); } diff --git a/src/main/java/org/nrg/xapi/rest/data/InvestigatorsApi.java b/src/main/java/org/nrg/xapi/rest/data/InvestigatorsApi.java index be3b56eb..8316ff34 100644 --- a/src/main/java/org/nrg/xapi/rest/data/InvestigatorsApi.java +++ b/src/main/java/org/nrg/xapi/rest/data/InvestigatorsApi.java @@ -10,7 +10,6 @@ import org.nrg.xdat.om.XnatInvestigatordata; import org.nrg.xdat.rest.AbstractXapiRestController; import org.nrg.xdat.security.helpers.Roles; import org.nrg.xdat.security.services.RoleHolder; -import org.nrg.xdat.security.services.RoleRepositoryServiceI; import org.nrg.xdat.security.services.UserManagementServiceI; import org.nrg.xft.XFTItem; import org.nrg.xft.event.EventUtils; 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 e2d6628a..118075f8 100644 --- a/src/main/java/org/nrg/xapi/rest/users/UsersApi.java +++ b/src/main/java/org/nrg/xapi/rest/users/UsersApi.java @@ -1,7 +1,9 @@ package org.nrg.xapi.rest.users; import io.swagger.annotations.*; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; import org.nrg.framework.annotations.XapiRestController; import org.nrg.framework.exceptions.NrgServiceError; import org.nrg.framework.exceptions.NrgServiceRuntimeException; @@ -10,11 +12,13 @@ import org.nrg.xapi.rest.NotFoundException; import org.nrg.xdat.XDAT; import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xdat.rest.AbstractXapiRestController; +import org.nrg.xdat.security.PasswordValidatorChain; import org.nrg.xdat.security.UserGroupI; import org.nrg.xdat.security.helpers.Groups; import org.nrg.xdat.security.helpers.Users; import org.nrg.xdat.security.services.RoleHolder; import org.nrg.xdat.security.services.UserManagementServiceI; +import org.nrg.xdat.security.user.exceptions.PasswordComplexityException; import org.nrg.xdat.security.user.exceptions.UserInitException; import org.nrg.xdat.security.user.exceptions.UserNotFoundException; import org.nrg.xdat.services.AliasTokenService; @@ -27,6 +31,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.encoding.ShaPasswordEncoder; +import org.springframework.security.core.session.SessionInformation; +import org.springframework.security.core.session.SessionRegistry; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; import java.util.*; @@ -36,9 +44,11 @@ import java.util.*; @RequestMapping(value = "/users") public class UsersApi extends AbstractXapiRestController { @Autowired - public UsersApi(final SiteConfigPreferences preferences, final UserManagementServiceI userManagementService, final RoleHolder roleHolder) { + public UsersApi(final SiteConfigPreferences preferences, final UserManagementServiceI userManagementService, final RoleHolder roleHolder, final SessionRegistry sessionRegistry, final PasswordValidatorChain passwordValidator) { super(userManagementService, roleHolder); _preferences = preferences; + _sessionRegistry = sessionRegistry; + _passwordValidator = passwordValidator; } @ApiOperation(value = "Get list of users.", notes = "The primary users function returns a list of all users of the XNAT system. This includes just the username and nothing else. You can retrieve a particular user by adding the username to the REST API URL or a list of users with abbreviated user profiles by calling /xapi/users/profiles.", response = String.class, responseContainer = "List") @@ -46,7 +56,7 @@ public class UsersApi extends AbstractXapiRestController { @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "You do not have sufficient permissions to access the list of usernames."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) - @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET) + @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) @ResponseBody public ResponseEntity<List<String>> usersGet() { if (_preferences.getRestrictUserListAccessToAdmins()) { @@ -63,7 +73,7 @@ public class UsersApi extends AbstractXapiRestController { @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "You do not have sufficient permissions to access the list of usernames."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) - @RequestMapping(value = {"profiles"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET) + @RequestMapping(value = "profiles", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) @ResponseBody public ResponseEntity<List<Map<String, String>>> usersProfilesGet() { if (_preferences.getRestrictUserListAccessToAdmins()) { @@ -95,24 +105,89 @@ public class UsersApi extends AbstractXapiRestController { return new ResponseEntity<>(userMaps, HttpStatus.OK); } + @ApiOperation(value = "Get list of active users.", notes = "Returns a map of usernames for users that have at least one currently active session, i.e. logged in or associated with a valid application session. The number of active sessions and a list of the session IDs is associated with each user.", response = Map.class, responseContainer = "Map") + @ApiResponses({@ApiResponse(code = 200, message = "A list of active users."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "You do not have sufficient permissions to access the list of usernames."), + @ApiResponse(code = 500, message = "An unexpected error occurred.")}) + @RequestMapping(value = "active", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) + @ResponseBody + public ResponseEntity<Map<String, Map<String, Object>>> getActiveUsers() { + final HttpStatus status = isPermitted(); + if (status != null) { + return new ResponseEntity<>(status); + } + + final Map<String, Map<String, Object>> activeUsers = new HashMap<>(); + for (final Object principal : _sessionRegistry.getAllPrincipals()) { + final String username; + if (principal instanceof String) { + username = (String) principal; + } else if (principal instanceof UserDetails) { + username = ((UserDetails) principal).getUsername(); + } else { + username = principal.toString(); + } + final Map<String, Object> sessionData = new HashMap<>(); + final List<SessionInformation> sessions = _sessionRegistry.getAllSessions(principal, false); + final ArrayList<String> sessionIds = new ArrayList<>(); + for (final SessionInformation session : sessions) { + sessionIds.add(session.getSessionId()); + } + sessionData.put("sessions", sessionIds); + sessionData.put("count", sessions.size()); + activeUsers.put(username, sessionData); + } + return new ResponseEntity<>(activeUsers, HttpStatus.OK); + } + + @ApiOperation(value = "Get information about active sessions for the indicated user.", notes = "Returns a map containing a list of session IDs usernames for users that have at least one currently active session, i.e. logged in or associated with a valid application session. This also includes the number of active sessions for each user.", response = String.class, responseContainer = "List") + @ApiResponses({@ApiResponse(code = 200, message = "A list of active users."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "You do not have sufficient permissions to access the list of usernames."), + @ApiResponse(code = 404, message = "The indicated user has no active sessions or is not a valid user."), + @ApiResponse(code = 500, message = "An unexpected error occurred.")}) + @RequestMapping(value = "active/{username}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) + @ResponseBody + public ResponseEntity<List<String>> getUserActiveSessions(@ApiParam(value = "ID of the user to fetch", required = true) @PathVariable("username") final String username) { + final HttpStatus status = isPermitted(); + if (status != null) { + return new ResponseEntity<>(status); + } + + for (final Object principal : _sessionRegistry.getAllPrincipals()) { + final Object located = locatePrincipalByUsername(username); + if (located == null) { + continue; + } + final List<SessionInformation> sessions = _sessionRegistry.getAllSessions(principal, false); + final List<String> sessionIds = new ArrayList<>(); + for (final SessionInformation session : sessions) { + sessionIds.add(session.getSessionId()); + } + return new ResponseEntity<>(sessionIds, HttpStatus.OK); + } + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + @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 = "An unexpected error occurred.")}) - @RequestMapping(value = {"{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); + @RequestMapping(value = "{username}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) + public ResponseEntity<User> usersIdGet(@ApiParam(value = "ID of the user to fetch", required = true) @PathVariable("username") final String username) { + HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); } final UserI user; try { - user = getUserManagementService().getUser(id); + user = getUserManagementService().getUser(username); return user == null ? new ResponseEntity<User>(HttpStatus.NOT_FOUND) : new ResponseEntity<>(new User(user), HttpStatus.OK); } catch (UserInitException e) { - _log.error("An error occurred initializing the user " + id, e); + _log.error("An error occurred initializing the user " + username, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserNotFoundException e) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -125,8 +200,8 @@ public class UsersApi extends AbstractXapiRestController { @ApiResponse(code = 403, message = "Not authorized to create or update this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) - @RequestMapping(value = {"{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 { + @RequestMapping(value = "{username}", 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("username") String username, @RequestBody User model) throws NotFoundException, PasswordComplexityException { HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); @@ -166,6 +241,20 @@ public class UsersApi extends AbstractXapiRestController { user.setVerified(model.isVerified()); } + final String password; + if (StringUtils.isNotBlank(model.getPassword())) { + password = model.getPassword(); + if (!_passwordValidator.isValid(password, user)) { + throw new PasswordComplexityException(_passwordValidator.getMessage()); + } + } else { + password = RandomStringUtils.randomAscii(32); + } + final String salt = Users.createNewSalt(); + user.setPassword(new ShaPasswordEncoder(256).encodePassword(password, salt)); + user.setPrimaryPassword_encrypt(true); + user.setSalt(salt); + try { getUserManagementService().save(user, getSessionUser(), false, new EventDetails(EventUtils.CATEGORY.DATA, EventUtils.TYPE.WEB_SERVICE, Event.Modified, "", "")); return new ResponseEntity<>(HttpStatus.OK); @@ -175,26 +264,67 @@ public class UsersApi extends AbstractXapiRestController { return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } + @ApiOperation(value = "Invalidates all active sessions associated with the specified username.", notes = "Returns a list of session IDs that were invalidated.", response = String.class, responseContainer = "List") + @ApiResponses({@ApiResponse(code = 200, message = "User successfully invalidated."), + @ApiResponse(code = 304, message = "Indicated user has no active sessions, so no action was taken."), + @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 = "An unexpected error occurred.")}) + @RequestMapping(value = {"{username}", "active/{username}"}, produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.DELETE) + public ResponseEntity<List<String>> invalidateUser(@ApiParam(value = "The username of the user to invalidate.", required = true) @PathVariable("username") String username) throws NotFoundException { + HttpStatus status = isPermitted(username); + if (status != null) { + return new ResponseEntity<>(status); + } + final UserI user; + try { + user = getUserManagementService().getUser(username); + if (user == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } catch (UserInitException e) { + _log.error("An error occurred initializing the user " + username, e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } catch (UserNotFoundException e) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + Object located = locatePrincipalByUsername(user.getUsername()); + if (located == null) { + return new ResponseEntity<>(HttpStatus.NOT_MODIFIED); + } + final List<SessionInformation> sessions = _sessionRegistry.getAllSessions(located, false); + if (sessions.size() == 0) { + return new ResponseEntity<>(HttpStatus.NOT_MODIFIED); + } + final List<String> sessionIds = new ArrayList<>(); + for (final SessionInformation session : sessions) { + sessionIds.add(session.getSessionId()); + session.expireNow(); + } + return new ResponseEntity<>(sessionIds, HttpStatus.OK); + } + @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 = "An unexpected error occurred.")}) - @RequestMapping(value = {"{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); + @RequestMapping(value = "{username}/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("username") final String username) { + HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = getUserManagementService().getUser(id); + final UserI user = getUserManagementService().getUser(username); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return new ResponseEntity<>(user.isEnabled(), HttpStatus.OK); } catch (UserInitException e) { - _log.error("An error occurred initializing the user " + id, e); + _log.error("An error occurred initializing the user " + username, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserNotFoundException e) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -207,14 +337,14 @@ public class UsersApi extends AbstractXapiRestController { @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) - @RequestMapping(value = {"{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); + @RequestMapping(value = "{username}/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("username") final String username, @ApiParam(value = "The value to set for the enabled status.", required = true) @PathVariable("flag") Boolean flag) { + HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = getUserManagementService().getUser(id); + final UserI user = getUserManagementService().getUser(username); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -227,7 +357,7 @@ public class UsersApi extends AbstractXapiRestController { } return new ResponseEntity<>(false, HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserInitException e) { - _log.error("An error occurred initializing the user " + id, e); + _log.error("An error occurred initializing the user " + username, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserNotFoundException e) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -240,20 +370,20 @@ public class UsersApi extends AbstractXapiRestController { @ApiResponse(code = 403, message = "Not authorized to view this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) - @RequestMapping(value = {"{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); + @RequestMapping(value = "{username}/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("username") final String username) { + HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = getUserManagementService().getUser(id); + final UserI user = getUserManagementService().getUser(username); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return new ResponseEntity<>(user.isVerified(), HttpStatus.OK); } catch (UserInitException e) { - _log.error("An error occurred initializing the user " + id, e); + _log.error("An error occurred initializing the user " + username, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserNotFoundException e) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -266,14 +396,14 @@ public class UsersApi extends AbstractXapiRestController { @ApiResponse(code = 403, message = "Not authorized to verify or un-verify this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) - @RequestMapping(value = {"{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); + @RequestMapping(value = "{username}/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("username") final String username, @ApiParam(value = "The value to set for the verified status.", required = true) @PathVariable("flag") Boolean flag) { + HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = getUserManagementService().getUser(id); + final UserI user = getUserManagementService().getUser(username); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -286,7 +416,7 @@ public class UsersApi extends AbstractXapiRestController { } return new ResponseEntity<>(false, HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserInitException e) { - _log.error("An error occurred initializing the user " + id, e); + _log.error("An error occurred initializing the user " + username, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserNotFoundException e) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -299,13 +429,13 @@ public class UsersApi extends AbstractXapiRestController { @ApiResponse(code = 403, message = "Not authorized to view this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) - @RequestMapping(value = {"{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") final String id) { - HttpStatus status = isPermitted(id); + @RequestMapping(value = "{username}/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("username") final String username) { + HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); } - final Collection<String> roles = getUserRoles(id); + final Collection<String> roles = getUserRoles(username); return roles != null ? new ResponseEntity<>(roles, HttpStatus.OK) : new ResponseEntity<Collection<String>>(HttpStatus.FORBIDDEN); } @@ -315,14 +445,15 @@ public class UsersApi extends AbstractXapiRestController { @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) - @RequestMapping(value = {"{id}/roles/{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); + @RequestMapping(value = "{username}/roles/{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("username") final String username, + @ApiParam(value = "The user's new role.", required = true) @PathVariable("role") final String role) { + HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = getUserManagementService().getUser(id); + final UserI user = getUserManagementService().getUser(username); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -334,7 +465,7 @@ public class UsersApi extends AbstractXapiRestController { } return new ResponseEntity<>(false, HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserInitException e) { - _log.error("An error occurred initializing the user " + id, e); + _log.error("An error occurred initializing the user " + username, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserNotFoundException e) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -347,14 +478,14 @@ public class UsersApi extends AbstractXapiRestController { @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) - @RequestMapping(value = {"{id}/roles/{role}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.DELETE}) - 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); + @RequestMapping(value = "{username}/roles/{role}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.DELETE) + public ResponseEntity<Boolean> usersIdRemoveRole(@ApiParam(value = "ID of the user to delete a role from", required = true) @PathVariable("username") final String username, @ApiParam(value = "The user role to delete.", required = true) @PathVariable("role") String role) { + HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = getUserManagementService().getUser(id); + final UserI user = getUserManagementService().getUser(username); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -366,7 +497,7 @@ public class UsersApi extends AbstractXapiRestController { } return new ResponseEntity<>(false, HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserInitException e) { - _log.error("An error occurred initializing the user " + id, e); + _log.error("An error occurred initializing the user " + username, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserNotFoundException e) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -379,21 +510,21 @@ public class UsersApi extends AbstractXapiRestController { @ApiResponse(code = 403, message = "Not authorized to view this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) - @RequestMapping(value = {"{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); + @RequestMapping(value = "{username}/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("username") final String username) { + HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = getUserManagementService().getUser(id); + final UserI user = getUserManagementService().getUser(username); 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); + _log.error("An error occurred initializing the user " + username, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserNotFoundException e) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -406,14 +537,14 @@ public class UsersApi extends AbstractXapiRestController { @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) - @RequestMapping(value = {"{id}/groups/{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") final String group) { - HttpStatus status = isPermitted(id); + @RequestMapping(value = "{username}/groups/{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("username") final String username, @ApiParam(value = "The user's new group.", required = true) @PathVariable("group") final String group) { + HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = getUserManagementService().getUser(id); + final UserI user = getUserManagementService().getUser(username); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -425,7 +556,7 @@ public class UsersApi extends AbstractXapiRestController { } return new ResponseEntity<>(false, HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserInitException e) { - _log.error("An error occurred initializing the user " + id, e); + _log.error("An error occurred initializing the user " + username, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserNotFoundException e) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -438,14 +569,14 @@ public class UsersApi extends AbstractXapiRestController { @ApiResponse(code = 403, message = "Not authorized to enable or disable this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) - @RequestMapping(value = {"{id}/groups/{group}"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.DELETE}) - public ResponseEntity<Boolean> usersIdRemoveGroup(@ApiParam(value = "ID of the user to remove from group", required = true) @PathVariable("id") final String id, @ApiParam(value = "The group to remove the user from.", required = true) @PathVariable("group") final String group) { - HttpStatus status = isPermitted(id); + @RequestMapping(value = "{username}/groups/{group}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.DELETE) + public ResponseEntity<Boolean> usersIdRemoveGroup(@ApiParam(value = "ID of the user to remove from group", required = true) @PathVariable("username") final String username, @ApiParam(value = "The group to remove the user from.", required = true) @PathVariable("group") final String group) { + HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); } try { - final UserI user = getUserManagementService().getUser(id); + final UserI user = getUserManagementService().getUser(username); if (user == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -457,13 +588,31 @@ public class UsersApi extends AbstractXapiRestController { } return new ResponseEntity<>(false, HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserInitException e) { - _log.error("An error occurred initializing the user " + id, e); + _log.error("An error occurred initializing the user " + username, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } catch (UserNotFoundException e) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } + @Nullable + private Object locatePrincipalByUsername(final String username) { + Object located = null; + for (final Object principal : _sessionRegistry.getAllPrincipals()) { + if (principal instanceof String && username.equals(principal)) { + located = principal; + break; + } else if (principal instanceof UserDetails && username.equals(((UserDetails) principal).getUsername())) { + located = principal; + break; + } else if (username.equals(principal.toString())) { + located = principal; + break; + } + } + return located; + } + @SuppressWarnings("unused") public static class Event { public static String Added = "Added User"; @@ -480,5 +629,7 @@ public class UsersApi extends AbstractXapiRestController { private static final Logger _log = LoggerFactory.getLogger(UsersApi.class); - private final SiteConfigPreferences _preferences; + private final SiteConfigPreferences _preferences; + private final SessionRegistry _sessionRegistry; + private final PasswordValidatorChain _passwordValidator; } -- GitLab