I have these read-only fields which needs to be secure, like passwords. Say we have a user object:
public class User {
@NotEmpty
@Size(max = 100)
private String name;
@NotEmpty
private String username;
@NotEmpty
@Email
private String email;
private String password;
@JsonIgnore
public String getPassword() {
return password;
}
@JsonProperty
public void setPassword(String password) {
this.password = password;
}
}
So, this works nicely; as in I can get/post/put whatever I want, but I will never receive the password back. But I also want to make sure that when it's posted for the first time, password should not be empty. If I do
@NotEmpty
private string password;
Then my PUT (edit) requests will fail with validation error if I don't want to change the password of this user.
There are two solutions I can think of:
1- Inherit User class, create a special class only for POST which can have the @NotEmpty annotation over the getter.
public static class Create extends User {
@NotEmpty
@Override
public String getPassword() {
return password;
}
}
This should work generally but not for my codebase as it's already utilizing inheritance greatly on CRUD resources. I'd need to break and duplicate a lot of things for this approach.
2- Handle validation on the resource class:
public class UserResource {
@POST
public User createUser(User user) {
if(user.getPassword().isEmpty()) {
throw new ConstraintValidation....();
}
}
}
Does the job, but not so pretty. Especially since I have 5-10 of these.
Any other alternatives?
Apart from jersey injection and a custom validator, I found a cooler and easier and smoother solution I think that might interest you, Natan:
DW supports validation groups instead. With these, you can decide what to annotate based on your method, without the validation knowing about the method. You can read more about that here:
http://www.dropwizard.io/0.9.0/docs/manual/validation.html
(fwiw I am using the RC release in my example).
So let's get started:
@Path("/hello/world")
@Produces(MediaType.APPLICATION_JSON)
public class HelloWorldResource {
@POST
@Path("/v1")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response test(@Valid @Validated(V1Check.class) User user) {
// checks only username
System.out.println(user.getName());
System.out.println(user.getPassword());
return Response.ok().build();
}
@POST
@Path("/v2")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response test2(@Valid @Validated(V2Check.class) User user) {
// checks both
System.out.println(user.getName());
System.out.println(user.getPassword());
return Response.ok().build();
}
}
This is the resource class. See how I annotated both methods with @Validated. This tells DW what to validate exactly.
Here's my user class:
public class User {
@JsonProperty("name")
private String name;
@JsonProperty("password")
private String password;
@NotEmpty(message="other message", groups= {V2Check.class} )
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@NotEmpty(message="asd asd asd", groups= {V1Check.class, V2Check.class } )
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public interface V1Check {};
public interface V2Check {};
}
I embedded the interfaces in that class as well. Password is now only V2 checked. So for your POST method, you'll want to add that to the Validated annotation, while your get method can remain V1 checked and ignores the password.
And for good measure, the starter for my test:
public class Starter extends Application<Configuration> {
@Override
public void run(Configuration configuration, Environment environment) throws Exception {
environment.jersey().register(HelloWorldResource.class);
}
public static void main(String[] args) throws Exception {
new Starter().run("server", "/Users/artur/dev/repo/dw-test/src/main/resources/configuration.yaml");
}
}
This is the DW way to do this, however you should be able to add jersey injection into your custom validators as well. It just seems unnecessary to write a custom validator JUST because you need to not check your passwords.
Here's my curls. The user_json2:
{
"name" : "artur"
}
V1, not checking the password:
arturk:tmp artur$ curl -XPOST "localhost:9085/hello/world/v1/" --header "Content-Type: application/json" -d @user_json2 -v
* Trying ::1...
* Connected to localhost (::1) port 9085 (#0)
> POST /hello/world/v1/ HTTP/1.1
> Host: localhost:9085
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 19
>
* upload completely sent off: 19 out of 19 bytes
< HTTP/1.1 200 OK
< Date: Fri, 27 May 2016 09:59:22 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact
V2, checking the password:
arturk:tmp artur$ curl -XPOST "localhost:9085/hello/world/v2/" --header "Content-Type: application/json" -d @user_json2 -v
* Trying ::1...
* Connected to localhost (::1) port 9085 (#0)
> POST /hello/world/v2/ HTTP/1.1
> Host: localhost:9085
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 19
>
* upload completely sent off: 19 out of 19 bytes
< HTTP/1.1 400 Bad Request
< Date: Fri, 27 May 2016 10:07:41 GMT
< Content-Type: text/html;charset=iso-8859-1
< Cache-Control: must-revalidate,no-cache,no-store
< Content-Length: 251
<
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 400 Bad Request</title>
</head>
<body><h2>HTTP ERROR 400</h2>
<p>Problem accessing /hello/world/v2/. Reason:
<pre> Bad Request</pre></p>
</body>
</html>
* Connection #0 to host localhost left intact
I hope that helps!
Cheers,
Artur