I'm just implementing a basic CRUD service where a user can be created in the database with their password matching a certain regex and being encoded using BCryptPasswordEncoder.
My tests are failing due to a ConstraintViolationException on the password saying that it does not satisfy the regex requirement:
javax.validation.ConstraintViolationException: Validation failed for classes [com.hoaxify.hoaxify.user.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must match "^(?=.*[a-z])(?=.*\d)(?=.*[A-Z]).{8,50}$"', propertyPath=password, rootBeanClass=class com.hoaxify.hoaxify.user.User, messageTemplate='{javax.validation.constraints.Pattern.message}'}
It wasn't getting caught in my #ExceptionHandler since it was throwing a ConstraintViolationException and not a MethodArgumentNotValidException. I debugged and found that, while it was trying to match to the given regex, the value for the password itself was showing as:
$2a$10$pmRUViwj3Ey4alK0eqT1Dulz4BpGSlSReHyBR28K6bIE4.LZ7nYWG
while the password being passed in was:
P4ssword
So it appears the validation is being run on the encrypted password and not the raw password. I thought the validation should occur on the object received in the createUser method - before any other manipulation occurred.
Any help on why this is happening and how to fix would be greatly appreciated.
Note:
Validation works for all other fields
UserController
#RestController
#RequestMapping("{my/path}")
class UserController {
#Autowired
lateinit var userService: UserService
#PostMapping
fun createUser(#Valid #RequestBody user: User): GenericResponse {
userService.save(user)
return GenericResponse("Saved user")
}
#ExceptionHandler(MethodArgumentNotValidException::class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
fun handleValidationException(exception: MethodArgumentNotValidException, request: HttpServletRequest): ApiError {
val error = ApiError(400, "Validation error", request.servletPath)
val bindingResult = exception.bindingResult
bindingResult.fieldErrors.forEach {
error.validationErrors[it.field] = it.defaultMessage ?: "invalid"
}
return error
}
}
User
#Entity
class User(
#Id
#GeneratedValue
val id: Long,
#field:NotBlank
#field:Size(min = 4, max = 50)
var username: String,
#field:NotBlank
#field:Size(min = 4, max = 50)
var displayName: String,
#field:NotBlank
#field:Pattern(regexp = """^(?=.*[a-z])(?=.*\d)(?=.*[A-Z]).{8,50}$""")
var password: String
)
UserService
#Service
class UserService(
private val userRepository: UserRepository,
private val passwordEncoder: BCryptPasswordEncoder = BCryptPasswordEncoder()
) {
fun save(user: User): User {
user.password = passwordEncoder.encode(user.password)
return userRepository.save(user)
}
}
UserControllerTest
(relevant test)
#Test
fun postUser_whenUserIsValid_receiveOk() {
val user = User(
0,
"test-user",
"test-display",
"P4ssword"
)
val response: ResponseEntity<Any> = testRestTemplate.postForEntity(API_USERS_BASE, user, Any::class.java)
assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
}
The problem is that you use the same entity in the controller as in the service. So in the controller, it works as you expect. But in the service, you update the unencrypted password with the encrypted one and save that to the database. When you save to the database, the validation annotations are also checked, triggering the ConstraintViolationException.
The best option is to create a separate object for the controller. For example, create a CreateUserRequest class which is similar to the User entity, but only contains the fields that the controller needs. You can add your validation annotations there. Then in the service, convert the CreateUserRequest instance to a User entity. On the user class, remove the #Pattern validation since you don't want to validate the encrypted password.
Related
I need to check whether user is a member of specific group for security reasons.
However, I'm getting LazyInitializationException all the time and can't find a solution.
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: User.groups, could not initialize proxy - no Session
I've tried to use #Transactional annotation everywhere it is possible, that doesn't help. It works fine with Eager initialization for sure, but I would prefer not to use it.
Controller:
#MessageMapping("/group/chat")
#PreAuthorize("#decider.checkIfGroupMember(#user, #chatMessageRequest.getGroupId())")
public ChatMessageRequest processMessage(
#Payload ChatMessageRequest chatMessageRequest,
#AuthenticationPrincipal User user) {
return chatMessageService.processMessage(chatMessageRequest, user);
}
Decider class:
#Component
public class Decider {
#Transactional
public boolean checkIfGroupMember(Object principal, Integer groupId) {
User user = (User) principal;
Set<Group> groups = user.getGroups();
for (Group group : groups) {
if(group.getId().equals(groupId)) {
return true;
}
}
return false;
}
}
That's what is important about user:
#Data
#Builder
#Accessors(chain = true)
#Table(name = "users")
public class User extends BasicEntity {
#Column(nullable = false)
private String name;
#Column(nullable = false, unique = true)
private String email;
#Column
private String password;
#ManyToMany(mappedBy = "users", fetch = FetchType.LAZY)
#JsonIgnore
private Set<Group> groups = new HashSet<>();
}
Somebody could help me? Thanks in advance!
UPD: Transactional works fine in other parts of application, so I don't think this is configuration issue or it might be. Also, even initializing directly with Hibernate.initialize() inside decider method still fails.
I guess, this happening because #PreAuthorize doesn't access the method directly, so Spring is not able to create proxy. However, I still don't know how to fix it and even not sure if I am right here.
You need to open transaction before authentication. You can do that by specifying the order on both EnableTransactionManagement and EnableGlobalMethodSecurity. By default, they both have lowest precedence.
#EnableTransactionManagement(order = 0)
#EnableGlobalMethodSecurity(prePostEnabled = true, order = 1)
Credit goes to this answer.
Use fetch = FetchType.EAGER instead of FetchType.LAZY.
I'm trying to implement a simple caching of a logged in user in a repository which is linked to a room database.
Here is the code of the repository
#Singleton
class UserRepository #Inject constructor(
private val webservice: Webservice,
private val userDao: UserDao
) {
var user: User? = null //simple cache of a currently logged in user
private set
init {
user = null
}
fun getUser(userId: Int): LiveData<User> {
if (user != null)
return MutableLiveData<User>(user)
val data = userDao.load(userId)
//the value is always null because the load function is done async
user = data.value
return data
}
}
And here is the UserDao
#Dao
interface UserDao {
#Query("select * from user where id = :userId")
fun load(userId: Int): LiveData<User>
}
User data is feched by a ViewModel like so
class HomeViewModel #Inject constructor(
repository: UserRepository
) : ViewModel() {
val userId: Int = MainActivity.uid
val user: LiveData<User> = repository.getUser(userId)
}
The data is fetched from the database and displayed correctly but the loaded user is never asigned to the cache because the loading is done async. Is it possible to add some listener to the load function?
I'm quite new to kotlin and android in general so if there are some better solutions for this kind of simple cache please advise.
this is one of my REST controller,
#RestController
#RequestMapping("/users/Ache")
public class Users {
#GetMapping
public User getUser() {
User user = new User();
return user;
}
}
As response, Spring boot will translate my Object to JSON,
this is response:
{
"username": "Ache",
"password": "eee",
"token": "W0wpuLAUQCwIH1r2ab85gWdJOiy2cp",
"email": null,
"birthday": null,
"createDatetime": "2019-03-15T01:39:11.000+0000",
"updateDatetime": null,
"phoneNumber": null
}
I want to remove password and token fields, How can I do?
I know two hard ways:
create a new hash map
and add some necessary fields, but it too complex
set those two fields to null
but it still leaves two null valued fields, it is too ugly.
Any better solution?
Spring leverages Jackson library for JSON marshalling by default. The easiest solution that comes to mind is making use of Jackson's #JsonIgnore but that would ignore the property on both serialization and de-serialization. So the right approach would be annotating the field with #JsonProperty(access = Access.WRITE_ONLY).
For instance, inside a hypothetical User class:
#JsonProperty(access = Access.WRITE_ONLY)
private String password;
#JsonProperty(access = Access.WRITE_ONLY)
private String token;
An alternative would be using #JsonIgnore only on the getter:
#JsonIgnore
public String getPassword() {
return this.password;
}
You can also create another class, for instance UserResponse with all the fields except password and token, and make it your return type. Of course it involves creating an object and populating it, but you leave your User class clean without Jackson annotations, and de-couples your model from your representation.
Keep the getter and setter but add the WRITE_ONLY JsonProperty. This way password validations will work when you use the entity as the request body.
#NotBlank
#JsonProperty(access = Access.WRITE_ONLY)
private String password;
I'm making and simple application in Kotlin using Spring but I'm having a problem with the validation.
I have this entity class:
#Entity
#Table(name = "category")
data class Category(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long?,
#field:NotNull #field:NotEmpty val name: String)
And my controller function like this:
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
fun create(#Valid #RequestBody category: Category): ResponseEntity<Category>
create have some code, but it is irrelevant for the question, my problem is with the request body validation. If I send a category with an empty name field, it is thrown a MethodArgumentNotValidException exception, but if I send null to the field name, the exception thrown HttpMessageNotReadableException instead. Does anyone knows if it is possible to make passing null to a field marked with #NotNull to also throw MethodArgumentNotValidException in Kotlin.
So your problem is you specify the name field as not nullable, by default jackson module for kotlin will check it and throw HttpMessageNotReadableException which cause by MissingKotlinParameterException during json mapping process. If you mark name filed as nullable json mapping will passed and get to the spring validation phase with #Valid then we will get MethodArgumentNotValidException
#Entity
#Table(name = "category")
data class Category(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long?,
#field:NotNull #field:NotEmpty val name: String?)
You can handle this issue by providing HttpMessageNotReadableException handler
and then checking if the underlying cause is MissingKotlinParameterException.
After that, you can provide custom validation error. I'm using zalando-problem, so syntax is a bit different from vanilla spring, but you get the idea:
#ExceptionHandler
override fun handleMessageNotReadableException(
exception: HttpMessageNotReadableException,
request: NativeWebRequest
): ResponseEntity<Problem> {
// workaround
val cause = exception.cause
if (cause is MissingKotlinParameterException) {
val violations = setOf(createMissingKotlinParameterViolation(cause))
return newConstraintViolationProblem(exception, violations, request)
}
return create(Status.BAD_REQUEST, UnableToReadInputMessageProblem(), request)
}
private fun createMissingKotlinParameterViolation(cause: MissingKotlinParameterException): Violation {
val name = cause.path.fold("") { jsonPath, ref ->
val suffix = when {
ref.index > -1 -> "[${ref.index}]"
else -> ".${ref.fieldName}"
}
(jsonPath + suffix).removePrefix(".")
}
return Violation(name, "must not be null")
}
This way you get get nice output with proper constraint error.
You may try to declare #ExceptionHandler for MissingKotlinParameterException directly (though I've tried, but it didn't some reason), but I can't guarantee it'll work.
Code samples for path parsing are taken from here
I have a database with one table named person:
id | first_name | last_name | date_of_birth
----|------------|-----------|---------------
1 | Tin | Tin | 2000-10-10
There's a JPA entity named Person that maps to this table:
#Entity
#XmlRootElement(name = "person")
#XmlAccessorType(NONE)
public class Person {
#Id
#GeneratedValue
private Long id;
#XmlAttribute(name = "id")
private Long externalId;
#XmlAttribute(name = "first-name")
private String firstName;
#XmlAttribute(name = "last-name")
private String lastName;
#XmlAttribute(name = "dob")
private String dateOfBirth;
// setters and getters
}
The entity is also annotated with JAXB annotations to allow XML payload in
HTTP requests to be mapped to instances of the entity.
I want to implement an endpoint for retrieving and updating an entity with a given id.
According to this answer to a similar question,
all I need to do is to implement the handler method as follows:
#RestController
#RequestMapping(
path = "/persons",
consumes = APPLICATION_XML_VALUE,
produces = APPLICATION_XML_VALUE
)
public class PersonController {
private final PersonRepository personRepository;
#Autowired
public PersonController(final PersonRepository personRepository) {
this.personRepository = personRepository;
}
#PutMapping(value = "/{person}")
public Person savePerson(#ModelAttribute Person person) {
return personRepository.save(person);
}
}
However this is not working as expected as can be verified by the following failing test case:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
public class PersonControllerTest {
#Autowired
private TestRestTemplate restTemplate;
private HttpHeaders headers;
#Before
public void before() {
headers = new HttpHeaders();
headers.setContentType(APPLICATION_XML);
}
// Test fails
#Test
#DirtiesContext
public void testSavePerson() {
final HttpEntity<Object> request = new HttpEntity<>("<person first-name=\"Tin Tin\" last-name=\"Herge\" dob=\"1907-05-22\"></person>", headers);
final ResponseEntity<Person> response = restTemplate.exchange("/persons/1", PUT, request, Person.class, "1");
assertThat(response.getStatusCode(), equalTo(OK));
final Person body = response.getBody();
assertThat(body.getFirstName(), equalTo("Tin Tin")); // Fails
assertThat(body.getLastName(), equalTo("Herge"));
assertThat(body.getDateOfBirth(), equalTo("1907-05-22"));
}
}
The first assertion fails with:
java.lang.AssertionError:
Expected: "Tin Tin"
but: was "Tin"
Expected :Tin Tin
Actual :Tin
In other words:
No server-side exceptions occur (status code is 200)
Spring successfully loads the Person instance with id=1
But its properties do not get updated
Any ideas what am I missing here?
Note 1
The solution provided here is not working.
Note 2
Full working code that demonstrates the problem is provided
here.
More Details
Expected behavior:
Load the Person instance with id=1
Populate the properties of the loaded person entity with the XML payload using Jaxb2RootElementHttpMessageConverter or MappingJackson2XmlHttpMessageConverter
Hand it to the controller's action handler as its person argument
Actual behavior:
The Person instance with id=1 is loaded
The instance's properties are not updated to match the XML in the request payload
Properties of the person instance handed to the controller's action handler method are not updated
this '#PutMapping(value = "/{person}")' brings some magic, because {person} in your case is just '1', but it happens to load it from database and put to ModelAttribute in controller. Whatever you change in test ( it can be even empty) spring will load person from database ( effectively ignoring your input ), you can stop with debugger at the very first line of controller to verify it.
You can work with it this way:
#PutMapping(value = "/{id}")
public Person savePerson(#RequestBody Person person, #PathVariable("id") Long id ) {
Person found = personRepository.findOne(id);
//merge 'found' from database with send person, or just send it with id
//Person merged..
return personRepository.save(merged);
}
wrong mapping in controller
to update entity you need to get it in persisted (managed) state first, then copy desired state on it.
consider introducing DTO for your bussiness objects, as, later, responding with persisted state entities could cause troubles (e.g. undesired lazy collections fetching or entities relations serialization to XML, JSON could cause stackoverflow due to infinite method calls)
Below is simple case of fixing your test:
#PutMapping(value = "/{id}")
public Person savePerson(#PathVariable Long id, #RequestBody Person person) {
Person persisted = personRepository.findOne(id);
if (persisted != null) {
persisted.setFirstName(person.getFirstName());
persisted.setLastName(person.getLastName());
persisted.setDateOfBirth(person.getDateOfBirth());
return persisted;
} else {
return personRepository.save(person);
}
}
Update
#PutMapping(value = "/{person}")
public Person savePerson(#ModelAttribute Person person, #RequestBody Person req) {
person.setFirstName(req.getFirstName());
person.setLastName(req.getLastName());
person.setDateOfBirth(req.getDateOfBirth());
return person;
}
The issue is that when you call personRepository.save(person) your person entity does not have the primary key field(id) and so the database ends up having two records with the new records primary key being generated by the db. The fix will be to create a setter for your id field and use it to set the entity's id before saving it:
#PutMapping(value = "/{id}")
public Person savePerson(#RequestBody Person person, #PathVariable("id") Long id) {
person.setId(id);
return personRepository.save(person);
}
Also, like has been suggested by #freakman you should use #RequestBody to capture the raw json/xml and transform it to a domain model. Also, if you don't want to create a setter for your primary key field, another option may be to support an update operation based on any other unique field (like externalId) and call that instead.
For updating any entity the load and save must be in same Transaction,else it will create new one on save() call,or will throw duplicate primary key constraint violation Exception.
To update any we need to put entity ,load()/find() and save() in same transaction, or write JPQL UPDATE query in #Repository class,and annotate that method with #Modifying .
#Modifying annotation will not fire additional select query to load entity object to update it,rather presumes that there must be a record in DB with input pk,which needs to update.