diff --git a/build.gradle b/build.gradle index 173b0b422c1257117b94fb76064efb69cd798ccc..f8b63ac9c17c649e12994d46ef30fa4191d0cc98 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,6 @@ def vCargo = '1.4.18' def vSlf4j = '1.7.21' def vLog4j = '1.2.17' def vJunit = '4.12' -// def vSaxon = '9.7.0-7' def vSaxon = '9.1.0.8' def vGroovy = '2.4.6' def vJython = '2.7.0' @@ -50,6 +49,7 @@ apply plugin: 'groovy' apply plugin: 'java' apply plugin: 'war' apply plugin: 'maven' +apply plugin: 'jacoco' apply plugin: 'maven-publish' apply plugin: 'com.bmuschko.tomcat' apply plugin: 'com.bmuschko.cargo' @@ -167,6 +167,17 @@ war { 'Implementation-Version': version } } +jacoco { + toolVersion = "0.7.6.201602180812" +} + +jacocoTestReport { + reports { + xml.enabled false + csv.enabled false + html.destination "${buildDir}/jacocoHtml" + } +} task sourceJar(type: Jar, dependsOn: classes) { from sourceSets.main.allSource diff --git a/src/main/java/org/nrg/xapi/rest/ApiException.java b/src/main/java/org/nrg/xapi/exceptions/ApiException.java similarity index 87% rename from src/main/java/org/nrg/xapi/rest/ApiException.java rename to src/main/java/org/nrg/xapi/exceptions/ApiException.java index f6e34b409067d47062917836cef519f9905cc9e9..2664e319cfc2611054a479bbbe6638907563b5c4 100644 --- a/src/main/java/org/nrg/xapi/rest/ApiException.java +++ b/src/main/java/org/nrg/xapi/exceptions/ApiException.java @@ -1,4 +1,4 @@ -package org.nrg.xapi.rest; +package org.nrg.xapi.exceptions; public class ApiException extends Exception { public ApiException(int code, String msg) { diff --git a/src/main/java/org/nrg/xapi/exceptions/DataFormatException.java b/src/main/java/org/nrg/xapi/exceptions/DataFormatException.java new file mode 100644 index 0000000000000000000000000000000000000000..8af6ada60de5a0e9ffe9fa87aaa7876371080194 --- /dev/null +++ b/src/main/java/org/nrg/xapi/exceptions/DataFormatException.java @@ -0,0 +1,91 @@ +package org.nrg.xapi.exceptions; + +import com.google.common.base.Joiner; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class DataFormatException extends ApiException { + private final List<String> _missing = new ArrayList<>(); + private final List<String> _unknown = new ArrayList<>(); + private final Map<String, String> _invalid = new HashMap<>(); + + public DataFormatException() { + this("There was an error with the submitted data"); + } + + public DataFormatException(final String message) { + super(HttpStatus.BAD_REQUEST.value(), message); + } + + public List<String> getMissingFields() { + return _missing; + } + + public void setMissing(final List<String> missing) { + _missing.clear(); + _missing.addAll(missing); + } + + public void addMissing(final String missing) { + _missing.add(missing); + } + + public List<String> getUnknownFields() { + return _unknown; + } + + public void setUnknown(final List<String> unknown) { + _unknown.clear(); + _unknown.addAll(unknown); + } + + public void addUnknown(final String unknown) { + _unknown.add(unknown); + } + + public Map<String, String> getInvalidFields() { + return _invalid; + } + + public void setInvalid(final Map<String, String> invalid) { + _invalid.clear(); + _invalid.putAll(invalid); + } + + public void addInvalid(final String invalid) { + _invalid.put(invalid, "Invalid " + invalid + " format"); + } + + public void addInvalid(final String invalid, final String message) { + _invalid.put(invalid, message); + } + + public boolean hasDataFormatErrors() { + return !_missing.isEmpty() && !_unknown.isEmpty() && !_invalid.isEmpty(); + } + + @Override + public String getMessage() { + final StringBuilder buffer = new StringBuilder(super.getMessage()); + buffer.append("\n"); + if (_missing.size() > 0) { + buffer.append(" * Missing fields: ").append(Joiner.on(", ").join(_missing)).append("\n"); + } + if (_unknown.size() > 0) { + buffer.append(" * Unknown fields: ").append(Joiner.on(", ").join(_unknown)).append("\n"); + } + if (_invalid.size() > 0) { + buffer.append(" * Invalid fields:\n"); + for (final String invalid : _invalid.keySet()) { + buffer.append(" - ").append(invalid).append(": ").append(_invalid.get(invalid)).append("\n"); + } + } + return buffer.toString(); + } +} diff --git a/src/main/java/org/nrg/xapi/exceptions/NotFoundException.java b/src/main/java/org/nrg/xapi/exceptions/NotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..4315274b6d53163a4bb41d932a80cc873e1f1f46 --- /dev/null +++ b/src/main/java/org/nrg/xapi/exceptions/NotFoundException.java @@ -0,0 +1,11 @@ +package org.nrg.xapi.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) +public class NotFoundException extends ApiException { + public NotFoundException(String msg) { + super(HttpStatus.NOT_FOUND.value(), msg); + } +} diff --git a/src/main/java/org/nrg/xapi/exceptions/ResourceAlreadyExistsException.java b/src/main/java/org/nrg/xapi/exceptions/ResourceAlreadyExistsException.java new file mode 100644 index 0000000000000000000000000000000000000000..fd39f298a5f4a35f335fa9707487fd534b4bdc93 --- /dev/null +++ b/src/main/java/org/nrg/xapi/exceptions/ResourceAlreadyExistsException.java @@ -0,0 +1,11 @@ +package org.nrg.xapi.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.CONFLICT) +public class ResourceAlreadyExistsException extends ApiException { + public ResourceAlreadyExistsException(final String type, final String name) { + super(HttpStatus.CONFLICT.value(), "The resource of type " + type + " with the name " + name + " already exists."); + } +} 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 b8b7574e10009c1a6c512fa5433318b328596e8f..9e0f751ed11fd756d51a3a913cac1523e6f46cb2 100644 --- a/src/main/java/org/nrg/xapi/model/users/User.java +++ b/src/main/java/org/nrg/xapi/model/users/User.java @@ -1,6 +1,7 @@ package org.nrg.xapi.model.users; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import org.nrg.xdat.entities.UserAuthI; @@ -106,7 +107,7 @@ public class User { * Whether the user is a site administrator. **/ @ApiModelProperty(value = "Whether the user is a site administrator.") - public boolean isAdmin() { + public Boolean isAdmin() { return _isAdmin; } @@ -118,7 +119,7 @@ public class User { * Whether the user is enabled. **/ @ApiModelProperty(value = "Whether the user is enabled.") - public boolean isEnabled() { + public Boolean isEnabled() { return _isEnabled; } @@ -130,7 +131,7 @@ public class User { * Whether the user is verified. **/ @ApiModelProperty(value = "Whether the user is verified.") - public boolean isVerified() { + public Boolean isVerified() { return _isVerified; } @@ -142,7 +143,7 @@ public class User { * The user's encrypted password. **/ @ApiModelProperty(value = "The user's encrypted password.") - @JsonIgnore + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) public String getPassword() { return _password; } @@ -155,7 +156,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 + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) public String getSalt() { return _salt; } @@ -222,7 +223,7 @@ public class User { private String _salt; private Date _lastModified; private UserAuthI _authorization; - private boolean _isAdmin; - private boolean _isEnabled; - private boolean _isVerified; + private Boolean _isAdmin; + private Boolean _isEnabled; + private Boolean _isVerified; } diff --git a/src/main/java/org/nrg/xapi/rest/NotFoundException.java b/src/main/java/org/nrg/xapi/rest/NotFoundException.java deleted file mode 100644 index b42ca7024df3855805ae2ad47162d845ce074132..0000000000000000000000000000000000000000 --- a/src/main/java/org/nrg/xapi/rest/NotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.nrg.xapi.rest; - -public class NotFoundException extends ApiException { - public NotFoundException(int code, String msg) { - super(code, msg); - } -} diff --git a/src/main/java/org/nrg/xapi/rest/XapiRestControllerAdvice.java b/src/main/java/org/nrg/xapi/rest/XapiRestControllerAdvice.java index 07a3712cb27ee04f502ce0c8d5f5316eb420532d..5775a149fa8326d3a55079359115960d9dbc69aa 100644 --- a/src/main/java/org/nrg/xapi/rest/XapiRestControllerAdvice.java +++ b/src/main/java/org/nrg/xapi/rest/XapiRestControllerAdvice.java @@ -4,6 +4,8 @@ import org.nrg.dcm.exceptions.EnabledDICOMReceiverWithDuplicatePortException; import org.nrg.framework.exceptions.NrgServiceError; import org.nrg.framework.exceptions.NrgServiceException; import org.nrg.framework.exceptions.NrgServiceRuntimeException; +import org.nrg.xapi.exceptions.DataFormatException; +import org.nrg.xapi.exceptions.ResourceAlreadyExistsException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotationUtils; @@ -26,6 +28,16 @@ public class XapiRestControllerAdvice { return handleException(request, exception.getMessage()); } + @ExceptionHandler(DataFormatException.class) + public ModelAndView handleDataFormatException(final HttpServletRequest request, final DataFormatException exception) { + return handleException(request, exception.getMessage(), exception); + } + + @ExceptionHandler(ResourceAlreadyExistsException.class) + public ModelAndView handleDataFormatException(final HttpServletRequest request, final ResourceAlreadyExistsException exception) { + return handleException(request, exception.getMessage(), exception); + } + @ExceptionHandler(NrgServiceException.class) public ModelAndView handleNrgServiceException(final HttpServletRequest request, final NrgServiceException exception) { return handleException(HttpStatus.CONFLICT, request, "An NRG service error occurred.", exception); diff --git a/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApi.java b/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApi.java index ece0cd4d31679efa9b193628314fdc9ee0453b3c..140d294520db83f57629a000a5b4213bdf13c9a3 100644 --- a/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApi.java +++ b/src/main/java/org/nrg/xapi/rest/dicomscp/DicomSCPApi.java @@ -7,7 +7,7 @@ import org.nrg.dcm.exceptions.EnabledDICOMReceiverWithDuplicatePortException; import org.nrg.dcm.preferences.DicomSCPInstance; import org.nrg.framework.annotations.XapiRestController; import org.nrg.framework.exceptions.NrgServiceException; -import org.nrg.xapi.rest.NotFoundException; +import org.nrg.xapi.exceptions.NotFoundException; import org.nrg.xdat.rest.AbstractXapiRestController; import org.nrg.xdat.security.services.RoleHolder; import org.nrg.xdat.security.services.UserManagementServiceI; diff --git a/src/main/java/org/nrg/xapi/rest/theme/ThemeApi.java b/src/main/java/org/nrg/xapi/rest/theme/ThemeApi.java index b650d8d041644e8fe8c2071e1ddb7462e5f51376..5dd40e81388d8b11798e2da8ff45dd64638ba25f 100644 --- a/src/main/java/org/nrg/xapi/rest/theme/ThemeApi.java +++ b/src/main/java/org/nrg/xapi/rest/theme/ThemeApi.java @@ -14,7 +14,7 @@ package org.nrg.xapi.rest.theme; import io.swagger.annotations.*; import org.apache.commons.io.FileUtils; import org.nrg.framework.annotations.XapiRestController; -import org.nrg.xapi.rest.NotFoundException; +import org.nrg.xapi.exceptions.NotFoundException; import org.nrg.xdat.entities.ThemeConfig; import org.nrg.xdat.rest.AbstractXapiRestController; import org.nrg.xdat.security.services.RoleHolder; 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 118075f8dbe39e8008f632ece0848c1d7936e57e..3e687d89e896ca78dfbfad373da52ea82cc2ca46 100644 --- a/src/main/java/org/nrg/xapi/rest/users/UsersApi.java +++ b/src/main/java/org/nrg/xapi/rest/users/UsersApi.java @@ -7,8 +7,11 @@ import org.jetbrains.annotations.Nullable; import org.nrg.framework.annotations.XapiRestController; import org.nrg.framework.exceptions.NrgServiceError; import org.nrg.framework.exceptions.NrgServiceRuntimeException; +import org.nrg.framework.utilities.Patterns; +import org.nrg.xapi.exceptions.DataFormatException; +import org.nrg.xapi.exceptions.ResourceAlreadyExistsException; import org.nrg.xapi.model.users.User; -import org.nrg.xapi.rest.NotFoundException; +import org.nrg.xapi.exceptions.NotFoundException; import org.nrg.xdat.XDAT; import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xdat.rest.AbstractXapiRestController; @@ -194,70 +197,113 @@ public class UsersApi extends AbstractXapiRestController { } } - @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."), + @ApiOperation(value = "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 = 201, message = "User successfully created."), + @ApiResponse(code = 400, message = "The submitted data was invalid."), @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 = 403, message = "Not authorized to update this user."), + @ApiResponse(code = 500, message = "An unexpected error occurred.")}) + @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST) + public ResponseEntity<User> createUser(@RequestBody final User model) throws NotFoundException, PasswordComplexityException, DataFormatException, UserInitException, ResourceAlreadyExistsException { + final HttpStatus status = isPermitted(); + if (status != null) { + return new ResponseEntity<>(status); + } + + validateUser(model); + + final UserI user = getUserManagementService().createUser(); + + if (user == null) { + throw new NrgServiceRuntimeException(NrgServiceError.Unknown, "Failed to create a user object for user " + model.getUsername()); + } + + user.setLogin(model.getUsername()); + user.setFirstname(model.getFirstName()); + user.setLastname(model.getLastName()); + user.setEmail(model.getEmail()); + if (model.isEnabled() != null) { + user.setEnabled(model.isEnabled()); + } + if (model.isVerified()) { + user.setVerified(model.isVerified()); + } + user.setPassword(model.getPassword()); + user.setAuthorization(model.getAuthorization()); + + fixPassword(user); + + try { + getUserManagementService().save(user, getSessionUser(), false, new EventDetails(EventUtils.CATEGORY.DATA, EventUtils.TYPE.WEB_SERVICE, Event.Added, "Requested by user " + getSessionUser().getUsername(), "Created new user " + user.getUsername() + " through XAPI user management API.")); + return new ResponseEntity<>(new User(user), HttpStatus.CREATED); + } catch (Exception e) { + _log.error("Error occurred modifying user " + user.getLogin()); + } + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ApiOperation(value = "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 updated."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to update this user."), @ApiResponse(code = 404, message = "User not found."), @ApiResponse(code = 500, message = "An unexpected error occurred.")}) @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); + public ResponseEntity<User> updateUser(@ApiParam(value = "The username of the user to create or update.", required = true) @PathVariable("username") final String username, @RequestBody final User model) throws NotFoundException, PasswordComplexityException, UserInitException { + final HttpStatus status = isPermitted(username); if (status != null) { return new ResponseEntity<>(status); } - UserI user; + + final UserI user; try { user = getUserManagementService().getUser(username); - } catch (Exception e) { - //Create new User - user = getUserManagementService().createUser(); - user.setLogin(username); + } catch (UserNotFoundException e) { + throw new NotFoundException("User with username " + username + " was not found."); } + if (user == null) { - throw new NrgServiceRuntimeException(NrgServiceError.Unknown, "Failed to retrieve or create a user object for user " + username); + throw new NrgServiceRuntimeException(NrgServiceError.Unknown, "Failed to retrieve user object for user " + username); } - if ((StringUtils.isNotBlank(model.getFirstName())) && (!StringUtils.equals(model.getFirstName(), user.getFirstname()))) { + + if ((StringUtils.isNotBlank(model.getUsername())) && (!StringUtils.equals(user.getUsername(), model.getUsername()))) { + user.setLogin(model.getUsername()); + } + if ((StringUtils.isNotBlank(model.getFirstName())) && (!StringUtils.equals(user.getFirstname(), model.getFirstName()))) { user.setFirstname(model.getFirstName()); } - if ((StringUtils.isNotBlank(model.getLastName())) && (!StringUtils.equals(model.getLastName(), user.getLastname()))) { + if ((StringUtils.isNotBlank(model.getLastName())) && (!StringUtils.equals(user.getLastname(), model.getLastName()))) { user.setLastname(model.getLastName()); } - if ((StringUtils.isNotBlank(model.getEmail())) && (!StringUtils.equals(model.getEmail(), user.getEmail()))) { + if ((StringUtils.isNotBlank(model.getEmail())) && (!StringUtils.equals(user.getEmail(), model.getEmail()))) { user.setEmail(model.getEmail()); } - if (model.isEnabled() != user.isEnabled()) { + if ((StringUtils.isNotBlank(model.getPassword())) && (!StringUtils.equals(user.getPassword(), model.getPassword()))) { + user.setPassword(model.getPassword()); + fixPassword(user); + } + if (model.getAuthorization() != null && !model.getAuthorization().equals(user.getAuthorization())) { + user.setAuthorization(model.getAuthorization()); + } + if (model.isEnabled() != null) { 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()) { + if (model.isVerified() != null) { 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()); + if (!user.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); } - } 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); + return new ResponseEntity<>(new User(user), HttpStatus.OK); } catch (Exception e) { _log.error("Error occurred modifying user " + user.getLogin()); } @@ -613,6 +659,50 @@ public class UsersApi extends AbstractXapiRestController { return located; } + private void validateUser(final User model) throws DataFormatException, UserInitException, ResourceAlreadyExistsException { + final DataFormatException exception = new DataFormatException(); + + if (StringUtils.isBlank(model.getUsername())) { + exception.addMissing("username"); + } else if (!Patterns.USERNAME.matcher(model.getUsername()).matches()) { + exception.addInvalid("username"); + } + + try { + final UserI user = getUserManagementService().getUser(model.getUsername()); + if (user != null) { + throw new ResourceAlreadyExistsException("user", model.getUsername()); + } + } catch (UserNotFoundException ignored) { + // This is actually what we want. + } + + if (StringUtils.isBlank(model.getEmail())) { + exception.addMissing("email"); + } else if (!Patterns.EMAIL.matcher(model.getEmail()).matches()) { + exception.addInvalid("email"); + } + + if (exception.hasDataFormatErrors()) { + throw exception; + } + } + + private void fixPassword(final UserI user) throws PasswordComplexityException { + final String password = user.getPassword(); + if (StringUtils.isNotBlank(password)) { + if (!_passwordValidator.isValid(password, user)) { + throw new PasswordComplexityException(_passwordValidator.getMessage()); + } + } else { + user.setPassword(RandomStringUtils.randomAscii(32)); + } + final String salt = Users.createNewSalt(); + user.setPassword(new ShaPasswordEncoder(256).encodePassword(password, salt)); + user.setPrimaryPassword_encrypt(true); + user.setSalt(salt); + } + @SuppressWarnings("unused") public static class Event { public static String Added = "Added User"; diff --git a/src/test/java/org/nrg/xapi/model/users/TestUserSerialization.java b/src/test/java/org/nrg/xapi/model/users/TestUserSerialization.java new file mode 100644 index 0000000000000000000000000000000000000000..8ac87ebd505983f97ed9e04cab0a9ac96095256d --- /dev/null +++ b/src/test/java/org/nrg/xapi/model/users/TestUserSerialization.java @@ -0,0 +1,61 @@ +package org.nrg.xapi.model.users; + +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.commons.lang3.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.nrg.framework.services.SerializerService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.io.IOException; + +import static org.junit.Assert.*; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = TestUserSerializationConfig.class) +public class TestUserSerialization { + @Autowired + public void setSerializer(final SerializerService serializer) { + _serializer = serializer; + } + + @Test + public void testHiddenProperties() throws IOException { + final User input = new User(); + input.setUsername("name"); + input.setEmail("foo@bar.com"); + input.setPassword("password"); + input.setSalt("salt"); + input.setAdmin(false); + input.setEnabled(true); + + final String json = _serializer.toJson(input); + assertNotNull(json); + assertTrue(StringUtils.isNotBlank(json)); + + // Here's where we make sure the password and salt aren't serialized. + final JsonNode map = _serializer.deserializeJson(json); + assertNotNull(map); + assertTrue(map.has("username")); + assertTrue(map.has("email")); + assertFalse(map.has("password")); + assertFalse(map.has("salt")); + assertTrue(map.has("admin")); + assertTrue(map.has("enabled")); + assertFalse(map.has("verified")); + + final User output = _serializer.deserializeJson(json, User.class); + assertNotNull(output); + assertTrue(StringUtils.isNotBlank(output.getUsername())); + assertTrue(StringUtils.isNotBlank(output.getEmail())); + assertTrue(StringUtils.isBlank(output.getPassword())); + assertTrue(StringUtils.isBlank(output.getSalt())); + assertFalse(output.isAdmin()); + assertTrue(output.isEnabled()); + assertNull(output.isVerified()); + } + + private SerializerService _serializer; +} diff --git a/src/test/java/org/nrg/xapi/model/users/TestUserSerializationConfig.java b/src/test/java/org/nrg/xapi/model/users/TestUserSerializationConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..a9b209fedc8f908b2ff7393720fd382b5d0c49be --- /dev/null +++ b/src/test/java/org/nrg/xapi/model/users/TestUserSerializationConfig.java @@ -0,0 +1,38 @@ +package org.nrg.xapi.model.users; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module; +import org.nrg.framework.beans.Beans; +import org.nrg.framework.exceptions.NrgServiceException; +import org.nrg.framework.services.SerializerService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +import java.util.Map; + +@Configuration +public class TestUserSerializationConfig { + @Bean + public Jackson2ObjectMapperBuilder objectMapperBuilder() throws NrgServiceException { + return new Jackson2ObjectMapperBuilder() + .serializationInclusion(JsonInclude.Include.NON_NULL) + .failOnEmptyBeans(false) + .mixIns(mixIns()) + .featuresToEnable(JsonParser.Feature.ALLOW_SINGLE_QUOTES, JsonParser.Feature.ALLOW_YAML_COMMENTS) + .featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS, SerializationFeature.WRITE_NULL_MAP_VALUES) + .modulesToInstall(new Hibernate4Module()); + } + + @Bean + public SerializerService serializerService(final Jackson2ObjectMapperBuilder objectMapperBuilder) { + return new SerializerService(objectMapperBuilder); + } + + @Bean + public Map<Class<?>, Class<?>> mixIns() throws NrgServiceException { + return Beans.getMixIns(); + } +} diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties new file mode 100644 index 0000000000000000000000000000000000000000..f5b31a8e6bb75bc54d8881eb8f4db9ac53460aa7 --- /dev/null +++ b/src/test/resources/log4j.properties @@ -0,0 +1,14 @@ +# +# log4j.properties +# XNAT http://www.xnat.org +# Copyright (c) 2016, Washington University School of Medicine +# All Rights Reserved +# +# Released under the Simplified BSD. +# +log4j.rootLogger=DEBUG, test + +log4j.appender.test=org.nrg.log4j.NRGFileAppender +log4j.appender.test.File=build/test-results/xnat-web-test.log +log4j.appender.test.layout=org.apache.log4j.PatternLayout +log4j.appender.test.layout.ConversionPattern=%p %t %c - %m%n