1

With RESTEasy and Jackson, is it possible to use the @RolesAllowed annotation in my model, in order to avoid certain properties to be serialized in output, depending on the role of the user?

I already found a ton of documentation on how to do this with Jersey, but nothing with RESTEasy.

I'm blocked on this architecture so switching libraries is not an option, and using the custom ObjectMapper as explained here is not an option either, as the model is big enough to make it too time-consuming to mark every single property of a large dataset for correct serialization. Plus, this refers to an older version of the Jackson library and I'm not sure on how to make it work with the new version.

EDIT

Specifically see this blog post to understand what I'm trying to accomplish. Please note that this is Jersey-specific and so far I found no documentation on RESTEasy to accomplish this.

cassiomolin
  • 124,154
  • 35
  • 280
  • 359
Luca
  • 1,116
  • 2
  • 15
  • 24
  • The pointed solution should work with the most recent versions of Jackson and RESTEasy. You mentioned: _the model is big enough to make it too time-consuming to mark every single property of a large dataset for correct serialization_ Okay, so which criteria will you apply to filter the properties? – cassiomolin Jun 13 '17 at 12:55
  • The solution refers to the method `ObjectMapper::getSerializationConfig::setSerializationView` which was long deprecated and gone. I tried with the `withView` method which seemed the same but had no results. The criteria, as said in the question is that the user must have a specific permission to access that field. E.g. user with ADMIN permission will be able to access it, otherwise not. IMHO it makes the code easier to read and distinguish use cases as well. – Luca Jun 13 '17 at 13:16
  • I understand you want to perform role-specific serialization. What I'm asking is: If you don't want to use views (annotating each property of each bean), which criteria will you use to select the properties that will be serialized? One option that comes to my mind is filtering the properties by name (using a `SimpleBeanPropertyFilter`, for example). Then, in the `ContextResolver`, instead of setting a view, you would set a filter. – cassiomolin Jun 13 '17 at 13:30
  • I see what you mean, what I'm looking for specifically is a way to make the `@RolesAllowed` annotation work: this way the getProperty() method can be annotated with correct role and the EJB container will not allow the method to be called, thus removing the property, as described here https://blog.dejavu.sk/2014/02/04/filtering-jax-rs-entities-with-standard-security-annotations/ (note that this applies to Jersey, not RESTEasy). I'm looking for the equivalent solution. I hope I've been clearer, sorry for the misunderstanding – Luca Jun 13 '17 at 13:36
  • I see. So using `@JsonView` in _every single property of a large dataset for correct serialization_ is _too time-consuming_. But using roles annotations in every property is not? – cassiomolin Jun 13 '17 at 13:43
  • Nope, the properties in question are at most half a dozen against the hundreds. Plus, as I said, I like more the style of "everything is default until I wish so" (a.k.a. Convention Over Configuration), because with views you always need to ask yourself where the view comes from, if it has inheritance of some kind, etc. whereas using the roles just plain shows "user has role X, is allowed, otherwise not." Look I'm not trying to start up a war on this, I just wish to know if this is possible or not because it fits my current situation better than the other possible solutions. – Luca Jun 13 '17 at 13:48
  • Have a look at my [answer](https://stackoverflow.com/a/44524316/1426227). It may suit your needs. – cassiomolin Jun 13 '17 at 14:43
  • 1
    I was just implementing it, spot on and precise, thank you :) – Luca Jun 13 '17 at 15:13

1 Answers1

3

If you are not willing to use @JsonView, you could consider @JsonFilter. You first need to extend SimpleBeanPropertyFilter and control the serialization according to the user roles:

public class RoleBasedPropertyFilter extends SimpleBeanPropertyFilter {

    private String allowedRole;

    public RoleBasedPropertyFilter(String allowedRole) {
        this.allowedRole = allowedRole;
    }

    @Override
    public void serializeAsField(Object pojo, JsonGenerator jgen,
                                 SerializerProvider provider, 
                                 PropertyWriter writer) throws Exception {

        PermitAll permitAll = writer.getAnnotation(PermitAll.class);
        if (permitAll != null) {
            serializeAsField(pojo, jgen, provider, writer);
            return;
        }

        DenyAll denyAll = writer.getAnnotation(DenyAll.class);
        if (denyAll != null) {
            writer.serializeAsOmittedField(pojo, jgen, provider);
            return;
        }

        RolesAllowed rolesAllowed = writer.getAnnotation(RolesAllowed.class);
        if (rolesAllowed != null) {
            if (!Arrays.asList(rolesAllowed.value()).contains(allowedRole)) {
                writer.serializeAsOmittedField(pojo, jgen, provider);
                return;
            }
        }

        // If no annotation is provided, the property will be serialized
        writer.serializeAsField(pojo, jgen, provider);
    }
}

To apply the filter to a certain bean, annotate it with @JsonFilter("roleBasedPropertyFilter"):

@JsonFilter("roleBasedPropertyFilter")
public class User {

    private String firstName;
    private String lastName;
    private String email;
    private String password;

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    @RolesAllowed({"ADMIN"})
    public String getEmail() {
        return email;
    }

    @DenyAll
    public String getPassword() {
        return password;
    }

    // Other getters and setters
}

Then register your filter in your the ContextResolver for ObjectMapper:

String currentUserRole = // Get role from the current user

FilterProvider filterProvider = new SimpleFilterProvider()
        .addFilter("roleBasedPropertyFilter", 
                new RoleBasedPropertyFilter(currentUserRole));

ObjectMapper mapper = new ObjectMapper();
mapper.setFilterProvider(filterProvider);

If you want to make your filter "global", that is, to be applied to all beans, you can create a mix-in class and annotate it with @JsonFilter("roleBasedPropertyFilter"):

@JsonFilter("roleBasedPropertyFilter")
public class RoleBasedPropertyFilterMixIn {

}

Then bind the mix-in class to Object:

mapper.addMixIn(Object.class, RoleBasedPropertyFilterMixIn.class);
cassiomolin
  • 124,154
  • 35
  • 280
  • 359