How to prevent some HTTP methods from being exported from my MongoRepository? - spring

I'm using spring-data-rest and I have a MongoRepository like this:
#RepositoryRestResource
interface MyEntityRepository extends MongoRepository<MyEntity, String> {
}
I would like to allow the GET methods but disable PUT, POST, PATCH and DELETE (read only web service).
According to http://docs.spring.io/spring-data/rest/docs/2.2.2.RELEASE/reference/html/#repository-resources.collection-resource I should be able to do that like this:
#RepositoryRestResource
interface MyEntityRepository extends MongoRepository<MyEntity, String> {
#Override
#RestResource(exported = false)
public MyEntity save(MyEntity s);
#Override
#RestResource(exported = false)
public void delete(String id);
#Override
#RestResource(exported = false)
public void delete(MyEntity t);
}
It doesn't seem to work as I can still do PUT, POST, PATCH and DELETE requests.

Thanks to Oliver, here are the methods to override:
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends MongoRepository<Person, String> {
// Prevents GET /people/:id
#Override
#RestResource(exported = false)
public Person findOne(String id);
// Prevents GET /people
#Override
#RestResource(exported = false)
public Page<Person> findAll(Pageable pageable);
// Prevents POST /people and PATCH /people/:id
#Override
#RestResource(exported = false)
public Person save(Person s);
// Prevents DELETE /people/:id
#Override
#RestResource(exported = false)
public void delete(Person t);
}

This is late reply, but if you need to prevent the global http method for a entity, try it.
#Configuration
public class DataRestConfig implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.getExposureConfiguration()
.forDomainType(Person.class)
.withItemExposure(((metdata, httpMethods) -> httpMethods.disable(HttpMethod.PUT, HttpMethod.POST, ... )))
.withCollectionExposure((metdata, httpMethods) -> httpMethods.disable(HttpMethod.PUT, HttpMethod.POST, ...));
}
}

Why not just use like this?
#Configuration
public class SpringDataRestConfiguration implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration restConfig) {
restConfig.disableDefaultExposure();
}
}

Related

In Spring Data Rest, How to prevent DELETE HTTP methods from being exported from my JpaRepository?

I'm using spring-data-rest and I have a JpaRepository like this:
#RepositoryRestResource(path = "projects")
public interface ProjectsRepository extends JpaRepository<MetricsProjects, Integer> {...}
My repository interface:
#RepositoryRestResource(path = "projects")
public interface ProjectsRepository extends JpaRepository<MetricsProjects, Integer> {
List<MetricsProjects> findByProjectName(String projectName);
#Override
#RestResource(exported = false)
public void deleteById(Integer id);
#Override
#RestResource(exported = false)
public void delete(MetricsProjects entity);
#Override
#RestResource(exported = false)
public void deleteAll(Iterable<? extends MetricsProjects> entities);
#Override
#RestResource(exported = false)
public void deleteAll();
#Override
#RestResource(exported = false)
public void deleteInBatch(Iterable<MetricsProjects> entities);
#Override
#RestResource(exported = false)
public void deleteAllInBatch();
}
I've also added disableDefaultExposure() , as suggested somewhere.
My Configuration file:
#Configuration
public class SpringDataRestConfiguration implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration restConfig) {
restConfig.disableDefaultExposure();
}
}
But I still see the DELETE methods exposed from my Swagger-UI, how do I prevent this?
Create a controller method for the DELETE endpoint and return 405 Method Not Allowed.
Modify the configureRepositoryRestConfiguration to
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration restConfig) {
restConfig.getExposureConfiguration()
.forDomainType(put the class type here)
.withItemExposure((metdata, httpMethods) -> httpMethods.disable(HttpMethod.DELETE))
.withCollectionExposure((metdata, httpMethods) -> httpMethods.disable(HttpMethod.DELETE));
}
Replace put the class type here with the className.class, for example if the className is MetricsProjects, put MetricsProjects.class there.
Here is additional info.

Spring Boot & Data Repositories - fragment implementation x does not implement x

I upgraded from Spring Boot v.1.5.8 to v.2.1.5. When I try to start the application I get the following error:
IllegalArgumentException: Fragment implementation .OtmUniParentRepository2019GeneratedImpl$$EnhancerBySpringCGLIB$$cdf9e294 does not implement x.OtmUniParentRepository2019Generated!
Why I can't start it anymore?
The files:
OtoUniChildRepository2019
#RepositoryRestResource(collectionResourceRel = "OtoUniChild2019", path = "OtoUniChild2019")
public interface OtoUniChildRepository2019 extends OtoUniChildRepository2019Generated {
}
#Transactional
class OtoUniChildRepository2019Impl extends HeartcoreRepositoryImpl<OtoUniChild> {
#PostConstruct
private void setIni() {
super.setIni(OtoUniChild.TABLENAME, OtoUniChild.getColumnName(), OtoUniChild.class, "AGRIDB2019");
}
}
OtoUniChildRepository2019Generated
public interface OtoUniChildRepository2019Generated extends HeartcoreRepository<OtoUniChild> {
OtoUniChild findByIdAndOtoUniParentIsNotNull(#Param("id") String id);
OtoUniChild findByOtoUniParentId(#Param("id") String id);
}
#Transactional
class OtoUniChildRepository2019GeneratedImpl extends HeartcoreRepositoryImpl<OtoUniChild> {
#PostConstruct
private void setIni() {
super.setIni(OtoUniChild.TABLENAME, OtoUniChild.getColumnName(), OtoUniChild.class, "AGRIDB2019");
}
}
HeartcoreRepository
#NoRepositoryBean
public interface HeartcoreRepository<T extends Heartcore> extends RevisionRepository<T, String, Integer>, PagingAndSortingRepository<T, String>, HeartcoreCustomRepository<T> {
#Override
T findOne(String id);
boolean existsById(String id);
#Override
Collection<T> findAll();
List<T> findAllByKanton(#Param("kanton") String kanton);
}
HeartcoreCustomRepository
public interface HeartcoreCustomRepository<T extends Heartcore> {
List<T> findCustom(String sqlQuery);
List<T> findCustom(String select, String where);
Class<T> getType();
T findOne(String id);
Collection<T> findAll();
String getSequence(String sequenceName);
}
HeartcoreCustomRepositoryImpl
#Transactional
public class HeartcoreRepositoryImpl<T extends Heartcore> implements HeartcoreCustomRepository<T> {
#PersistenceContext
protected EntityManager entityManager;
// irrelevant code
public void setIni(String tablename, List<String> columns, Class<T> type, String schema) {
this.tablename = tablename;
this.columns = columns;
this.type = type;
this.schema = schema;
MultitenantDataSource multitenantDataSource = (MultitenantDataSource) entityManager.getEntityManagerFactory().getProperties().get("hibernate.connection.datasource");
DataSource dataSource = (DataSource) multitenantDataSource.determineTargetDataSource();
try {
this.dbDriver = dataSource.getConnection().getMetaData().getDriverName();
}
catch (SQLException e) {
e.printStackTrace();
}
}
// irrelevant code
With 1.5.8 it works fine and I couldn't find infos about breaking changes.
EDIT: Is something wrong with this inheritance structure of the repositories? I tried some different approaches but none worked. Is there an other way to implement some basic functionality for repositories?

How to Restrict access to particular page to only 1 logged in user?

I am making a simple Social Media Website using Java Spring Boot. Now I want to add a profile edit page, where a logged in user can edit/update his profile data but other logged in users should not have access to it.
For example, there are two people John and Tom, John should be able to see only his profile edit page and Tom should see only his Profile edit page Only after login.
How to achieve this using Spring Security or by any other way ?
First of all you need to write BeanAccessor like following:
#Component
public class BeanAccessor implements ApplicationContextAware {
private static ApplicationContext context;
public static ObjectMapper getObjectMapper() {
return getBean(ObjectMapper.class);
}
public static <T> T getBean(Class<T> beanClass, Object... args) {
return context.getBean(beanClass, args);
}
private static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
}
then we need to write new class for method security like:
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
private Object filterObject;
private Object returnObject;
private Object target;
public CustomMethodSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
CustomMethodSecurityExpressionRoot setTarget(Object target) {
this.target = target;
return this;
}
#Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
#Override
public Object getFilterObject() {
return filterObject;
}
#Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
#Override
public Object getReturnObject() {
return returnObject;
}
#Override
public Object getThis() {
return target;
}
}
finally we need custom method security expressinon handler:
#Component
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
#Autowired
private CustomPermissionEvaluator customPermissionEvaluator;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
final CustomMethodSecurityExpressionRoot root = BeanAccessor.getBean(CustomMethodSecurityExpressionRoot.class, authentication);
root.setPermissionEvaluator(customPermissionEvaluator);
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(getRoleHierarchy());
root.setTarget(invocation.getThis());
return root;
}
}
now on your controller method yo can define #PreAuthorize("isProfileOwner(#id)") annotations your user profile show page method looks like :
#PreAuthorize("isProfileOwner(#id)")
#GetMapping("{id}")
public String show(#PathVariable("id") Long id, Model model) {
//omitted
}
everything okey but we need to write isProfileOwner() method to our CustomMethodSecurityExpressionRoot class like:
public boolean isProfileOwner(Long id) {
//add logic here and you are ready
}
also you can check this post

Return IDs in JSON response from Spring Data REST

I've got an entity
#Entity
#Table(name = "books")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "id", unique = true, nullable = false)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I initialize it like this
#PostConstruct
public void init() {
List<String> newFiles = this.listFiles();
newFiles.forEach(filename -> {
Book book = new Book();
book.setName(filename);
dbRepository.save(book);
});
}
If I set the result of save to an instance of Book, I can get the id and it is not null—so id is created fine.
I defined a repository
#RepositoryRestResource
public interface IBooksRepository extends CrudRepository<Book, Long> {
}
which I'd like to use to get and set data into the books table in the database.
When I try to access my repository rest using curl localhost:8080/books, I get this response
{
"_embedded":{
"books":[
{
"name":"simple-file.txt",
"_links":{
"self":{
"href":"http://localhost:8080/books/1"
},
"book":{
"href":"http://localhost:8080/books/1"
}
}
}
]
},
"_links":{
"self":{
"href":"http://localhost:8080/books"
},
"profile":{
"href":"http://localhost:8080/profile/books"
}
}
}
The books element returns name only. How can I make it return id too, on the same level as name?
Spring Data Rest hides the ID by default, in order to have it in the JSON you have to manually configure that for your entity. Depending on your spring version you can either provide your own configuration (old):
#Configuration
public class ExposeEntityIdRestConfiguration extends RepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Book.class);
}
}
...or register a RepositoryRestConfigurer (current):
#Component
public class ExposeEntityIdRestMvcConfiguration extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Book.class);
}
}
See the Spring Data Rest documentation for more details.
The accepted answer overrides a deprecated method. Here's the updated version:
#Component
public class RestConfig implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.exposeIdsFor(Book.class);
}
}
An alternative approach is to implement RepositoryRestConfigurer in your #SpringBootApplication annotated class:
#SpringBootApplication
public class MyApplication implements RepositoryRestConfigurer {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.exposeIdsFor(Book.class);
}
}
There is now a static method RepositoryRestConfigurer.withConfig that does the same thing as above. See javadoc:
Convenience method to easily create simple {#link RepositoryRestConfigurer} instances that solely want to tweak the {#link RepositoryRestConfiguration}.
I found the usage in one of their integration tests
So the following approach would be more up to date as of now:
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer()
{
return RepositoryRestConfigurer.withConfig(config -> {
config.exposeIdsFor(Book.class);
});
}
#Component
public class RestConfig implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Book.class);
//config.exposeIdsFor(Library.class);
}
}
This is a solution which works for all entities
#Autowired
private EntityManager entityManager;
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return RepositoryRestConfigurer.withConfig(config -> config.exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(Type::getJavaType).toArray(Class[]::new)));
}
This is a good way to go.
#Projection(name = "customBook", types = { Book.class })
public interface CustomBook {
#Value("#{target.id}")
long getId();
}
credit: https://www.baeldung.com/spring-data-rest-projections-excerpts

QueryDslMongoRepository for different collections

I am trying to implement a QueryDslMongoRepository for a model "Document"
#QueryEntity
#Document(collection="currentDocuments")
public class DocumentImpl extends TranslatableObjectImpl implements Document
In our current implementation a to be deleted document moves von "currentDocuments" into "deletedDocuments" collection.
I cant find a solution to create a repository like this
public interface DocumentRepository extends MongoRepository<DocumentImpl, String> ,QueryDslPredicateExecutor<DocumentImpl> {}
with a dynamic collection name.
My goal is to have the advantages of queryDsl in one Repository for different collections and to be able to move models from one collection into another like
public move(DocumentImpl entity, String sourceCollection, String targetCollection){
repository.delete(entity,sourceCollection);
repository.save(entity,targetCollection);
}
or something like
public List<Document> findAllDocumentsWithAttachments(String collectionName){
return repository.findAll(QDocumentImpl.documentImpl.attachments.isNotEmpty(), collectionName);
}
Any suggestions?
I implemented this feature by creating an own FactoryBean extending MongoRepositoryFactoryBean.
According to the Answer of this -> Question <- I implemeted following solution.
Entity
#QueryEntity
public class Document extends AbstractObject {
}
Custom QuerydslMongoRepository
public interface CustomQuerydslMongoRepository<T extends AbstractObject,ID extends Serializable> extends MongoRepository<T, ID> ,QueryDslPredicateExecutor<T>{
List<T> findAll(Predicate predicate, String collectionName);
T save(T entity, String collectionName);
...
}
Custom QuerydslMongoRepository Implementation
public class CustomQuerydslMongoRepositoryImpl<T extends AbstractObject,ID extends Serializable> extends QueryDslMongoRepository<T,ID> implements CustomQuerydslMongoRepository<T,ID> {
//All instance variables are available in super, but they are private
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> pathBuilder;
private final MongoOperations mongoOperations;
public CustomQuerydslMongoRepositoryImpl(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations) {
this(entityInformation, mongoOperations,DEFAULT_ENTITY_PATH_RESOLVER);
}
public CustomQuerydslMongoRepositoryImpl(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations, EntityPathResolver resolver) {
super(entityInformation, mongoOperations, resolver);
this.path=resolver.createPath(entityInformation.getJavaType());
this.pathBuilder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.mongoOperations=mongoOperations;
}
#Override
public List<T> findAll(Predicate predicate, String collectionName) {
MongodbQuery<T> query = createQueryFor(predicate,collectionName);
return query.list();
}
#Override
public T save(T entity,String collectionName){
Assert.notNull(entity, "Entity must not be null!");
mongoOperations.save(entity, collectionName);
return entity;
}
private MongodbQuery<T> createQueryFor(Predicate predicate,String collectionName) {
Class<T> domainType = getEntityInformation().getJavaType();
MongodbQuery<T> query = new SpringDataMongodbQuery<T>(getMongoOperations(), domainType,collectionName);
return query.where(predicate);
}
}
Custom Repository Factory
public class CustomQueryDslMongodbRepositoryFactoryBean<R extends QueryDslMongoRepository<T, I>, T, I extends Serializable> extends MongoRepositoryFactoryBean<R, T, I> {
#Override
protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
return new CustomQueryDslMongodbRepositoryFactory<T,I>(operations);
}
public static class CustomQueryDslMongodbRepositoryFactory<T, I extends Serializable> extends MongoRepositoryFactory {
private MongoOperations operations;
public CustomQueryDslMongodbRepositoryFactory(MongoOperations mongoOperations) {
super(mongoOperations);
this.operations = mongoOperations;
}
#SuppressWarnings({ "rawtypes", "unchecked" })
protected Object getTargetRepository(RepositoryMetadata metadata) {
return new CustomQuerydslMongoRepositoryImpl(getEntityInformation(metadata.getDomainType()), operations);
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return CustomQuerydslMongoRepository.class;
}
}
}
Entity Repository
public interface DocumentRepository extends CustomQuerydslMongoRepository<Document, String>{
}
Usage in Service
#Autowired
DocumentRepository repository;
public List<Document> getAllDocuments(Predicate predicate){
return repository.findAll(predicate,"myCustomCollection");
}

Resources