Restrict access to user's data in Spring MongoRepository - spring

I use Spring Data Rest with Spring Data Mongo.
I have a rather simple REST API which looks similar to this:
public class User {
String id;
String email;
String password;
List<String> roles;
}
public class UserData {
String data;
User user;
}
#PreAuthorize("hasRole('ROLE_USER')")
public interface QueryTemplateRepository extends
MongoRepository<UserData, String> {
}
What I want now is that users can only access their data and if they create/edit data it will be linked to their account.
Do I have to get rid of the MongoRepository and write everything myself? Is there some kind of interceptor or filter where I can do this?
I will want to create more REST APIs that are restricted to the user's data, so it would be great if there was some generic solution to this problem.

You can use features of AbstractMongoEventListener, it has convinient methods for your needs:
void onAfterConvert(DBObject dbo, E source)
void onAfterSave(E source, DBObject dbo)
void onBeforeSave(E source, DBObject dbo)
void onBeforeConvert(E source)
void onAfterLoad(DBObject dbo)
void onApplicationEvent(MongoMappingEvent event)

I think this is a concern you could deal with in your service layer through aspects as a generic approach. AbstractMongoEventListener is a good example of an applied aspect technique.
Because you are dealing with spring-data-rest, no service layer is available unless you wrap it and expose the repository via a Controller.
There is nothing in REST standard regarding allowing modification/deletion of entities only by the creator of it.

Related

Spring REST: Show certain data of a User only if requester is a friend

Scenario:
A community webapp where people can form communities about certain topics with a Spring REST backend.
Today I was wondering how one would implement a setting akin "Who can see your email adress".
When a User enters a community and a REST call to e.g. /api/community/1/users is being made, how would it be possible to stop the CrudRepository from serializing a field such as email of User B if the user A making the API call to the backend is not a friend / does not fulfill certain criteria of user B's settings, such as only showing emails to approved contacts. The resulting JSON should contain a list of users with some having a email field and some not.
While searching I was not able to find anything that matches my question. Following are some things I have discovered but don't feel like they are of much help.
Annotating Controller methods / Repository methods with #PreAuthorize, passing in the Principal.
Why I think this might not help: This seems to be a good solution if I want to block someone based on their ID from viewing a ressource completely. Such as Users only being able to see their own data but not others because the Principal ID does not match the requested ressource's id.
Using JsonFilter as described here: https://www.baeldung.com/jackson-serialize-field-custom-criteria
With this approach I don't see a way of checking WHO is making a request for e.g. my email.
This approach seems to fit well for a scenario such as having a boolean flag set to show email or not, for all cases and any requesters.
Creating a new domain object such as "Friend" extending "User", which is only there for overwriting the #JsonIgnore property of User. While a normal User would not have their Email field serialized due to #JsonIgnore, a friend would set #JsonIgnore(false) over email.
I dont like this approach because I feel like it must somehow be possible to implement this functionality without creating new classes only to overwrite Jackson annotations.
Sorry if there isn't any code to show. So far I have only been creating simple entities and mostly theorycrafting how it would be possible to accomplish the above when I saw that the repository exposes everything. I'm usually more home at the Frontend side of things but I want to learn backend with Spring as well, for private as well as professional reasons. I hope the question isn't too basic.
Thank you in advance.
You can use #JsonView from Jackson for it.
First, create a DTO with the fields you want to return and annotate them with #JsonView:
public class UserDto {
#JsonView(NoFriend.class)
private String name;
#JsonView(Friend.class);
private String email;
public static class NoFriend {}
public static class Friend extends NoFriend {}
}
The NoFriend and Friend inner classes are just markers to define what fields should be returned in what case.
Now in your controller, instead of returning a UserDto, you wrap the UserDto in a MappingJacksonValue:
public class UserController {
#GetMapping("/api/community/1/users")
public List<MappingJacksonValue> getUsers(#AuthenticationPrincipal Principal principal) {
List<User> users = service.getUsers();
return users.stream()
.map( user -> {
MappingJacksonValue value = new MappingJacksonValue(UserDto.fromUser(user));
value.setSerializationView(getView(principal, user));
})
.collectors(toList());
}
private Class getView(Principal princapl, User user) {
// return UserDto.Friend.class or UserDto.NoFriend.class, depending the relation of the authentication principal with the user
}
Probably, not the simplest way to implement it. But maybe it will help you to decompose a problem and find an appropriate solution.
I assume that you just want to clear fields on API level, but still gonna fill it in your Objects.
Let's define a model with some security metadata on it:
class UserDTO {
Long id;
String name;
#AllowOnly("hasRole('FRIEND')") // SPeL/any your custom defined language, or simpler:
//#AllowOnly(Role.FRIEND)
String email;
}
Then define a controller class
#RestController
class SomeController {
#GetMapping("/api/community/{id}/users")
public List<UserDTO> getUsers() {
return List.of(
new UserDTO(1, "Bob", "email-you#gonna.see"),
new UserDTO(2, "Jack", "email-you-NOT#gonna.see"))
}
}
So what i propose is to create an aspect, which is gonna clear fields based on your permission model.
#AfterReturning("within(#org.springframework.web.bind.annotation.RestController *)
&& execution(* *(..))", returning="objectToClear")
public void applyFieldPermissions(Object objectToClear) {
// Here i would parse annotations on object fields
// and if found #AllowOnly, check your role to a user.
// and clean up field, if necessary
}
Logic of the aspect is totally dependent on your cases, but for this simple example, need only to implement some method to check your role for specific object
boolean hasRoleOn(UserDto dto, Role role, Authentication currentUser)

OData (Olingo) "inhibit" endpoint

My question is about what is best way to inhibit an endpoint that is automatically provided by Olingo?
I am playing with a simple app based on Spring boot and using Apache Olingo.On short, this is my servlet registration:
#Configuration
public class CxfServletUtil{
#Bean
public ServletRegistrationBean getODataServletRegistrationBean() {
ServletRegistrationBean odataServletRegistrationBean = new ServletRegistrationBean(new CXFNonSpringJaxrsServlet(), "/user.svc/*");
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("javax.ws.rs.Application", "org.apache.olingo.odata2.core.rest.app.ODataApplication");
initParameters.put("org.apache.olingo.odata2.service.factory", "com.olingotest.core.CustomODataJPAServiceFactory");
odataServletRegistrationBean.setInitParameters(initParameters);
return odataServletRegistrationBean;
} ...
where my ODataJPAServiceFactory is
#Component
public class CustomODataJPAServiceFactory extends ODataJPAServiceFactory implements ApplicationContextAware {
private static ApplicationContext context;
private static final String PERSISTENCE_UNIT_NAME = "myPersistenceUnit";
private static final String ENTITY_MANAGER_FACTORY_ID = "entityManagerFactory";
#Override
public ODataJPAContext initializeODataJPAContext()
throws ODataJPARuntimeException {
ODataJPAContext oDataJPAContext = this.getODataJPAContext();
try {
EntityManagerFactory emf = (EntityManagerFactory) context.getBean(ENTITY_MANAGER_FACTORY_ID);
oDataJPAContext.setEntityManagerFactory(emf);
oDataJPAContext.setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
return oDataJPAContext;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
...
My entity is quite simple ...
#Entity
public class User {
#Id
private String id;
#Basic
private String firstName;
#Basic
private String lastName;
....
Olingo is doing its job perfectly and it helps me with the generation of all the endpoints around CRUD operations for my entity.
My question is : how can I "inhibit" some of them? Let's say for example that I don't want to enable the delete my entity.
I could try to use a Filter - but this seems a bit harsh. Are there any other, better ways to solve my problem?
Thanks for the help.
As you have said, you could use a filter, but then you are really coupled with the URI schema used by Olingo. Also, things will become complicated when you have multiple, related entity sets (because you could navigate from one to the other, making the URIs more complex).
There are two things that you can do, depending on what you want to achieve:
If you want to have a fined grained control on what operations are allowed or not, you can create a wrapper for the ODataSingleProcesor and throw ODataExceptions where you want to disallow an operation. You can either always throw exceptions (i.e. completely disabling an operation type) or you can use the URI info parameters to obtain the target entity set and decide if you should throw an exception or call the standard single processor. I have used this approach to create a read-only OData service here (basically, I just created a ODAtaSingleProcessor which delegates some calls to the standard one + overridden a method in the service factory to wrap the standard single processor in my wrapper).
If you want to completely un-expose / ignore a given entity or some properties, then you can use a JPA-EDM mapping model end exclude the desired components. You can find an example of such a mapping here: github. The mapping model is just an XML file which maps the JPA entities / properties to EDM entity type / properties. In order for olingo to pick it up, you can pass the name of the file to the setJPAEdmMappingModel method of the ODataJPAContext in your initialize method.

Spring Constraint Validation Context - Database Request Caching

I've written a custom Validation Annotation and a ConstraintValidator implementation, which uses a Spring Service (and executes a Database Query):
public class MyValidator implements ConstraintValidator<MyValidationAnnotation, String> {
private final MyService service;
public MyValidator(MyService service) {
this.service = service;
}
#Override
public void initialize(MyValidationAnnotation constraintAnnotation) {}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return service.exists(value);
}
}
It's used like this:
public class MyEntity {
#Valid
List<Foo> list;
}
public class Foo {
#MyValidationAnnotation
String id;
}
This works quite nice, but service.exists(value) is getting called for every item within the list, which is correct, but could/should be optimized.
Question:
When validating an instance of MyEntity, I'd like to cache the results of the service.exists(value) calls.
I don't want to use a static HashMap<String, Boolean>, because this would cache the results for the entire application lifetime.
Is it possible to access some kind of Constraint Validation Context, which only exists while this particular validation is running, so I can put there the cached results?
Or do you have some other solution?
Thanks in advance!
You can use Spring's cache support. There might be other parts in the application which needs caching and this can be reused. And the setup is very simple too. And it will keep your code neat and readable.
You can cache your service calls. You need to put annotation on your service methods and a little bit of configuration.
And for cache provider you can use Ehcache. You have many options like setting ttl and max number of elements that can be cached and eviction policy etc etc if needed.
Or you can implement your own cache provider if your needs are simple. And if it is web request, In this cache you may find ThreadLocal to be useful. you can do all caching for this running thread using threadlocal. When the request is processed you can clear the threadlocal cache.

Return custom-typed object from JpaRepository

I have the following repository:
public interface UserRepository extends BaseDAO<User> {
Collection<User> findByEmail(#Param("email") String email);
#Query("select new com.data.CustomUser(upper(substring(u.lastName, 1, 1)) as initial, count(*)) from User u join u.chats c where c.business=:business group by upper(substring(u.lastName, 1, 1)) order by initial")
List<CustomUser> getContactsIndex(#Param("email") String email);
}
which is exposed with Spring Data REST. The User object is a managed entity, while CustomUser not and as you can see, it's build on-fly by using custom query.
Once I want to call that function, it fails with Persistent entity must not be a null! exception. Is there any way to implement this behavior?
P.S. Expose CustomUser with separate repository is impossible because it is not a managed entity.
One challenge with using Spring Data Rest is when you hit an edge case and you don't know whether you've hit a bug or whether you're just outside the scope of what the library is intended for. In this case I think you are at the edge of what SDR will easily do for you, and it's time to implement your own controller.
Spring Data Rest is looking for an Entity - in your case a User - as the return type for ALL methods in the repository to expose under /entities/search, and breaks when it doesn't find that entity type. The User it wants to serialize isn't there, hence the "Persistent entity must not be null".
The way around this is to write a simple #Controller that has a #RequestMapping for the exact same url exposed by the repository method. This will override the SDR generated implementation for that url, and from that you can return whatever you want.
Your implementation might look something like this:
#Controller
public class CustomUserController {
private final UserRepository repository;
#Inject
public CustomUserController(UserRepository repo) {
repository = repo;
}
#RequestMapping(value = "/users/search/getContactsIndex", method = GET, produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody List<CustomUser> getContactsIndex(#RequestParam String email) {
return repository.getContactsIndex(email);
}
}
Be aware that there is a "recommended" way to override functionality this way. There is an open issue to document the best way to do this.

How does delete operation work with Rest in Spring Data

Currently we have exposed our methods like this
#RestController
#RequestMapping("/app/person")
public class PersonResource {
#Timed
public void delete(#PathVariable Long id) {
log.debug("REST request to delete Person: {}", id);
personRepository.delete(id);
}
}
The operations of this method, in terms of input and output, are very clear to the user developer.
This article http://spring.io/guides/gs/accessing-data-rest/ shows how to expose JPARepositories directly obviating the need of a service layer.
#RepositoryRestResource(collectionResourceRel="people", path="people")
public interface PersonRepository extends JpaRepository<PersonEntity, Long> {
}
It is not obvious to me how I can make a "delete operation" available with PathVariable Long id.
There is an excellent article on this topic. https://github.com/spring-projects/spring-data-rest/wiki/Configuring-the-REST-URL-path
But it actually shows how to supress export of a delete operation.
As documented here, Spring Data REST will expose item resources for the repository you declare. Thus, all you need to do is discover the URI of the resource to delete and issue a DELETE request to it.

Resources