Using Spring Security ACL with Spring Data REST - spring-data-jpa

I am trying to authorize apis exposed by Spring Data REST. So far I am able to do role-based authorization i.e:
#RepositoryRestResource(path = "book")
public interface BookRepository extends JpaRepository<Book, Long> {
#PreAuthorize("hasRole('ROLE_ADMIN')")
<S extends Book> Book save(Book book);
}
Also in the same project i have a service layer with ACL mechanism, which is working.
I am unable to use PostFilter expression with Spring Data REST i.e:
#PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, admin)")
List<Book> findAll();
It would be of great help, if anyone using ACL with Spring Data REST.
Note: I am aware of below open issues:
https://jira.spring.io/browse/DATAREST-236
https://jira.spring.io/browse/SEC-2409

using JpaRepository was shadowing List<Book> findAll() method. Then I used CrudRepository, and PostFilter got applied.
For more details, a sample project is available on GitHub:
https://github.com/charybr/spring-data-rest-acl
ACL-based authorization is working for below entity exposed by Spring Data REST.
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.security.access.method.P;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
#RepositoryRestResource(path = "book")
public interface BookRepository extends CrudRepository<Book, Long> {
#PreAuthorize("hasRole('ROLE_ADMIN') or hasPermission(#book, 'write')")
<S extends Book> Book save(#P("book") Book book);
#Override
#PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, admin)")
Iterable<Book> findAll();
}

Related

Add custom database session variable to Spring Boot JPA queries?

I am trying to set SET SESSION encrypt.key='some_key' to database queries or connection.
Thing is I have following column definition in my model class
#ColumnTransformer(forColumn = "first_name",
read = "pgp_sym_decrypt(first_name, current_setting('encrypt.key'))",
write = "pgp_sym_encrypt(?, current_setting('encrypt.key'))")
#Column(name = "first_name", columnDefinition = "bytea")
private String firstName;
Above works when we set encrypt.key in postgres.conf file directly but out requirement is to have encrypt.key configurable from our spring properties file.
Things I tried.
AttributeConverter annotation with custom Converter class which only works with JPA, and LIKE operations are not supported.
I tried ContextEventListener where I executed SET SESSION query at application startup but that only works for few requests
Next I tried CustomTransactionManager extends JpaTransactionManager where I was doing following
#Override
protected void prepareSynchronization(DefaultTransactionStatus status,TransactionDefinition definition) {
super.prepareSynchronization(status, definition);
if (status.isNewTransaction()) {
final String query = "SET encrypt.key='" + encryptKey + "'";
entityManager.createNativeQuery(query).executeUpdate();
}
log.info("Encrypt Key : {}", entityManager.createNativeQuery("SElECT current_setting('encrypt.key')").getSingleResult());
}
}
Above does not work when I call normal JPA Repository methods and encrypt.key is not set as the CustomTransactionManager class in not called.
Any guidance in right direction would help me a lot
Since I created CustomTransactionManager extends JpaTransactionManager
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionStatus;
import javax.persistence.EntityManager;
#Component
#Slf4j
#Primary
#Qualifier(value = "transactionManager")
public class CustomTransactionManager extends JpaTransactionManager {
#Autowired
private EntityManager entityManager;
#Value("${database.encryption.key}")
private String encryptKey;
#Override
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
super.prepareSynchronization(status, definition);
if (status.isNewTransaction()) {
final String query = "SET SESSION encrypt.key='" + encryptKey + "'";
entityManager.createNativeQuery(query).executeUpdate();
}
}
}
Above was not getting called when I used normal JPA Repository methods.
For example,
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByFirstName(String firstName);
}
Adding #Transactional on Repository class did override framework logic where a shared transaction was getting created behind-the-scenes for all repository beans. This resulted in my CustomTransactionManager to be called even with repository methods.
I initially thought that adding Transactional annotation was overkill but found out that it gets created automatically at framework level as well so manually adding it had no additional footprint on its own but code/query you write inside CustomTransactionManager class will add required request footprint.
So I ended up adding #Transactional annotation on all repository classes whose domain(table) had encrypted columns.
For my use-case, this was the most flexible solution to have column level encryption on Azure postgres datbase service with Spring boot because we can not add custom environment variables there from Azure Portal, and directly adding to postgres.conf file also not possible due it being a SAAS service.

Application layers. Mapping between api models and internal models

I have situation like this
I have controller code
#RestController
public class MyController implements SomeApi {
#Autowired
private final MyService myService ;
public ResponseEntity<AnswerObject> getSomething (RestModelObject obj) {
myService.getSomething(obj);
}
Below Service code:
#Service
public class MyServiceImpl implements MyService {
#Autowired
private final EntityRepository entityRepository;
public AnswerObject getSomething (RestModelObject obj) {
Entity entity = entityRepository.getSomething(obj);
AnswerObject answerObject = map(entity, new AnswerObject());
return answerObject;
}
}
I have here few layers as I can see - rest layer, business layer, persistence layer (let's suppose I have few data sources - DB and elastic, each have some repository bean).
As we can see Business layer (service) aware about entities, which is not really good I think.
So question is what is the best practices for this situation?
Mapping should happen on persistence layer?
Or Is it good idea to create some additional layer adapter which will be responsible for mappings between rest models to internal data models, and inject it to the service bean ?
Appreciate any good mature examples.
I think, it can be done on controller's level, like in example here. Correct me If I am wrong.

Accesing data from different collections of mongodb via springboot

I have been trying to join two different collections of MongoDB within a Spring boot application in order to fetch the data within a single #GetMapping call.
These are my application.properties:
spring.data.mongodb.uri=mongodb://localhost:27017/alpha1
#spring.data.mongodb.uri=mongodb://username:password#host:port/database
server.port = 4000
And this is what my repository looks like:
package com.example.demo.repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.example.demo.model.Person;
#Repository
public interface PersonRepository extends MongoRepository<Person, String>{
public Person findByContId(String firstName);
public Person findByUid(String uid);
}
The problem is I need to make another collection regarding user feeds and its data should be fetched within the same method.
You need to create another repository for the same like and fetch it where ever you need it
#Repository
public interface UserFeedsRepository extends MongoRepository<UserFeeds, String>{
public List<UserFeeds> findAll();
}

Mixing Spring Data Envers and QueryDSL

I'm using a global custom repository in my project which extends QueryDslJpaRepository:
public class CustomPagingAndSortingRepositoryImpl<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID>
implements CustomPagingAndSortingRepository<T, ID> {
And the interface:
public interface CustomPagingAndSortingRepository<T, ID extends Serializable>
extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
And then on my configuration I annotate it with:
#EnableJpaRepositories(repositoryBaseClass = CustomPagingAndSortingRepositoryImpl.class)
All is working fine, but now I was trying to add auditing support to my entities by using spring-data-envers and according to the docs I should use a specific repository factory bean class :
#EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class, repositoryBaseClass = CustomPagingAndSortingRepositoryImpl.class)
Now obviously if I do this things won't work because my repositories will now be created through the EnversRevisionRepositoryFactoryBean class and will no longer be of CustomPagingAndSortingRepositoryImpl type.
How can I support something like this? I'm not seeing how since my custom repository need to extend from QueryDslJpaRepository already.
I think the relevant part for you is this method of EnversRevisionRepositoryFactoryBean:
#Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return EnversRevisionRepositoryImpl.class;
}
Here you really want your CustomPagingAndSortingRepositoryImpl returned. So I would try the following:
extend EnversRevisionRepositoryFactoryBean and overwrite getRepositoryBaseClass to return your CustomPagingAndSortingRepositoryImpl.
Make CustomPagingAndSortingRepositoryImpl extend EnversRevisionRepositoryImpl.

Wrapping Spring Data JPA with ApsectJ

Is it possible?
Currently I am using some aspects for my MVC controllers, what works really fine. I'm wrapping their responses and I have desired effect.
I also want to do this with Spring Data JPA repositories. But since they're generated based on the interface e.g:
public interface SomeRepository<T extends Some, ID extends Serializable> extends
BaseRepository<T, ID>, JpaSpecificationExecutor<T> {
public List<T> findById(Long id)
}
It generates me controller which is ready to use:
http://localhost:8080/findById?id=1234
I also want to wrap this controller. Is it possible?
This should work:
#Component
#Aspect
public class MyAdvice {
#Before("execution(* com.company.jpa.SomeRepository+.findById(..))")
public void intercept() { ... }
}
Basically, we are telling the framework to intercept the call to the findById method on any sub-class of SomeRepository.
Here is a sample application demonstrating this in action.