-
Notifications
You must be signed in to change notification settings - Fork 560
Allow multiple repositories per entity (only one should be exported) [DATAREST-923] #1286
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Oliver Drotbohm commented Generally speaking SD REST expects one repository per domain type. You can disambiguate the situation by making one |
Tim Meißner commented Sure, so basically I am implementing a User Account Settings page - a user should be able to edit his own entity, so I use method security to disallow saving user entities with a different username than the principal. On the other hand there might be an admin user which can trigger service methods over a custom controller that might change other user entities than himself. If i use the same repository the access control will prevent the admin from doing that, so it's good to have two in this case. A complicated security expression / service call might not be enough in this situation, because i could also need to change other users as a normal user by calling other business related services.
|
Daniel Wegener commented Imo not a minor - once you provide more than one repository for an entity type, the behaviour of the application changes randomly, depending on which repository is found in the bean factory first (and this seem to change from compile to compile run). Took me a day to figure out why this is happening (I'd second a comment on Stackoverflow, it should at least yield a warning on the log). My use case: |
Oliver Drotbohm commented Daniel Wegener — Does the workaround with |
Daniel Wegener commented Hi Oliver. The workaround does not work in this case since they are distinct repositories:
and
From what I understand: Since none of them is a subtype of the other, there is no ambiguity that could be resolved with |
Oliver Drotbohm commented I see. I guess we need |
benkuly commented Is there any workaround? |
Johannes Hiemer commented Having exactly the same issue and the same requirements:
Is there any update, when this will be solved? |
Istvan Ratkai commented Exactly the same issue here. One repository for public usage, one for internal usage without security + some extra methods |
Johannes Hiemer commented I would pick up again on this issue, as I don't see why it is prioritised with "minor". To secure an application is substantially important to have mechanism of not maintaining the same repositories two times for different purposes - one of applications with external access to the data via REST and one for the internal access. So my question: if this is not possible on the repository implementation level, is there any useful workaround on the SpelEvaluationContext? |
Will Faithfull commented I also think this is the simplest solution to secure repositories - I would also agree that it should be more than "minor". Relying on |
Will Faithfull commented Inspired by the suggestion by Oliver above, I have arrived at an acceptably solid hack for the time being. Rather than using the This means I can define, for example @RepositoryRestResource(exported = false)
public interface InternalUserRepository extends BaseRepository<User, UUID> {
...
}
@RepositoryRestResource(path = "users")
@PreAuthorize("hasRole('ADMINISTRATOR')")
public interface UserRepository extends BaseRepository<User, UUID> {
...
} etc. If I want to allow a signup by an anonymous principal, I can handle that insertion through a custom route, with my |
Johannes Hiemer commented
|
Oliver Drotbohm commented I guess |
Will Faithfull commented
|
Nguyen commented I decided not to use spring data rest until this issue is fixed. The priority should not just minor |
Marc Friedman commented I just upvoted this issue after spending a day debugging through the bowels of Spring Data Rest trying to determine why my unit tests were randomly passing and failing. I agree that the priority needs to be raised as there is currently no warning and the behavior is unintuitive - if a repository is marked exported=false in an My use case is slightly different. I have a microservice which is managing entities that support soft delete (entities are marked as deleted but remain in the table). My internal repository needs extends CrudRepository and works with all entities. I was attempting to define a REST repository for use by other microservices which exported methods annotated with |
Max Mumford commented I also wasted a very frustrating day on this with unit tests randomly failing. My use case is similar to that of the other guys here - UserRepository needs to be secured when accessed via REST (eg to stop users from loading a complete list of other users in the system), but accessible without security restrictions internally (ie to search for another user by their email address). If it wasn't for Will's workaround I'd without a doubt have to drop Spring Data, and my use case is by no means unusual. Definitely warrants a higher priority than minor imo :/ |
Marc Friedman commented I was able to use As SpringBootRepositoryRestConfigurer is not publlic, I ended up duplicating it into my own RepositoryRestConfigurer and that finally worked. |
Max Mumford commented Just found another issue with having multiple repositories - secured repository methods are sometimes called during the deserialization of a json post request to a related entity, ie:
I'm fairly new to Spring so not really sure how to even go about fixing this... I've tried adding |
Will Faithfull commented
Do you mean you are making a request to |
Max Mumford commented Hi The problem occurs when I do the following:
Hope that clarifies things? |
Piotr Żmudziński commented Isn't it major, rather than minor ticket? |
Burkhard Graves commented Multiple repositories for entities are also problematic if request parameters or path variables are converted to instances of repository managed domain classes (done by |
Andrii Neverov commented +1 on Piotr's use-case Having composite repositories which would do a security check and delegate work to the non-secure version while maintaining the same interface is a very basic and essential requirement for enterprise apps. The issue has been open for more than a year. Any chance this would be prioritized and looked at any time soon? Also would be nice to put a notice into the reference so that people don't spend countless hours debugging |
Norbert Somlai commented I'm trying to work this around by merging my repositories, but I need to be able to handle saving records from POST requests ( |
Dario Seidl commented I was also facing this issue with exactly the same use-case as the OP. Having two repositories, one exported and secured with method security expressions and another non-exported one for internal usage looked like the perfect solution. I hope this will become possible in the future.
What we ended up doing, was to create a custom base repository implementation, extended from SimpleJpaRepository, as explained here: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.customize-base-repository with an implementation that provides aliases for the common CRUD methods, like this: @NoRepositoryBean
public interface InternalRepository<T, ID extends Serializable> extends Repository<T, ID> {
@Transactional(readOnly = true)
List<T> internalFindAll();
@Transactional(readOnly = false)
<S extends T> S internalSave(S entity);
// ...
}
public class InternalRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements InternalRepository<T, ID> {
public InternalRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
}
@Override
@Transactional(readOnly = true)
public List<T> internalFindAll() {
return super.findAll();
}
@Override
@Transactional(readOnly = false)
public <S extends T> S internalSave(S entity) {
return super.save(entity);
}
// ...
} Then all repositories implement a common repository that implements the InternalRepository interface as well as CrudRepository (or whatever you need to export), and overrides the exported methods with method security expressions. This way, we can use the internalFind... and internalSave... methods in our services, and have the default CrudRepository find... and save... exported and secured. This works well, but I still think having the repositories separated would be cleaner and more convenient.
|
Norbert Somlai commented
|
Yves Galante commented Simple question... Why Spring Data does not simply ignore repositories annoted at class level with exposed false ? Actually Spring data keep a referance on that generate issues. That will allow to have one unsecue with few change on Spring data ... |
Istvan Ratkai commented I created an extension for Spring Boot Data JPA which resolves this issue by adding the following methods automatically: <S extends T> S saveWithoutPermissionCheck(S entity);
void deleteWithoutPermissionCheck(T entity);
T findOneWithoutPermissionCheck(ID id); Actually, these methods are only a side-effects of the tons of other useful features I created for securing Data Rest endpoints. |
Istvan Ratkai commented I've found an other solution too.
The root of the problem is that you want an external and an internal method with the same functionality. You can handle the externalization via The real problem is that how can you create the same query twice? (Because queries are generated from the method name, and of course you cannot define two methods with the same name) The solution is quite simple: You can add any text between the 'find' and the 'By' keyword in the method name, because that part will be ignored when the actual SQL query is generated from the method name: findByFirstnameAndLastname and findInternalByFirstnameAndLastname or findExternalByFirstnameAndLastname or findAllThoseBastardsWithSameNamesByFirstnameAndLastname will have the exact same functionality. It's up to you how do you annotate those methods. (You can use The only problem is findAll, because the trick doesn't work here. But you can still create a similar query with the following name: findAllByIdNotNull Id cannot be null, so it will also return the whole table. (I also assume that 'id is not null' will be optimized in the DB level anyway so it won't even affect the performance.) Oh, I only tested it for the latest Data Jpa version for Spring Boot 2, but maybe it also works in the older versions.
|
Yves Galante commented aisik The main issue isn't that this Sping data rest not support this feature. It is that issue is not raise by Spring data rest and generate a random result. Sprind data rest must detect this conflic and log an error or fatal.
Secondly the issue occur also when one of two repositories is mark as not exposed. Repositories not expose must be ignored by sprind data rest |
Oliver Drotbohm commented The reason the exposure flag doesn't make it into the selection is that I've just filed and fixed DATACMNS-1448 that puts the Would you mind giving the snapshots a spin? It's currently available in Moore snapshots. I might consider a backport to Lovelace and Kay if that helps |
Bartosz Kielczewski commented
Work-around would be to override @Configuration
class HackRepositoryRestMvcConfiguration(
private val context: ApplicationContext,
@Qualifier("mvcConversionService") conversionService: ObjectFactory<ConversionService>
) : RepositoryRestMvcConfiguration(context, conversionService) {
override fun repositories() =
when (context) {
is ConfigurableApplicationContext -> Repositories(context.beanFactory)
else -> Repositories(context)
}
}
Work-around would be to create |
Nilesh Padwal commented After applying both the workarounds mentioned above, this is still not working. Spring is still publishing the repository at random. Following piece still returns two names in the for loop. One which has org.springframework.data.repository.support.Repositories class method private void populateRepositoryFactoryInformation(ListableBeanFactory factory) {
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, RepositoryFactoryInformation.class, false, false)) {
cacheRepositoryFactory(name);
}
} Any workarounds till this is fixed? |
buckett commented Ok, I've just lost a good few hours to this one, I've similar uses to others (one internal repo (no spring security) and one user facing repo (with spring security)). If nothing else it would be helpful to throw an error when this is detected so that it's clear what's going wrong |
cyril-gambis commented Hi, I would also need this same feature, for this exact same scenario. Thanks |
Oliver Drotbohm commented Would you guys mind trying the Moore snapshots or release candidate? I see we have DATACMNS-1448 fixed for quite a while already, we just forgot to mark it as resolved. That fix should make |
Bartosz Kielczewski commented I'm currently on Spring Boot 2.2.0.M4, which uses RC1 of data-commons. The project is using spring-data-mongodb, spring-data-rest, though if you follow the link to SO thread, So looking at the commit you've made for DATACMNS-1448 it doesn't work because in cacheFirstOrPrimary() both:
Would providing you a minimal project help you debug? |
Chris McGuire commented Just tried |
Chris McGuire commented My apologies... I spoke too soon. |
Jan Zeppenfeld commented Hey guys, because I stumbled upon the same issue in our current project, I found this thread and analyzed what's going on under the hood and tried to figure out how it can be fixed. Actually, two independent problems already mentioned in the comments prevent this feature from working. The Now the 'primary' state reaches the
This is the case because
The implemented Test So, there are four possible approaches to fix this issue from my point of view... from the cleanest and most invasive to the least invasive one:
Although I would prefer the cleaner solutions (1+2), I guess the fourth approach embracing backward compatibility would be the preferred one. So here is a pull request |
Hi @odrotbohm, I think, that this one can be closed, because
Best regards |
Tim Meißner opened DATAREST-923 and commented
My case is that i have an Entity "User" and two Repositories:
@RepositoryRestResource
(exported = false): meant for internal usage by services etc.@RepositoryRestResource
(exported = true): meant for external usage, restricted access, etc.This exports a user resource in 50% of the times i start up the application. I tried to work around this by annotating them with
@Order
(0/1) for the UserRepository and@Order
(1/0) for the UserRestRepository, but it doesn't work.So I would like to have the possibility to have multiple Repositories for one entity (only one exported but it should not conflict with other normal Repositories that are not exported).
Affects: 2.5.4 (Hopper SR4)
Reference URL: http://stackoverflow.com/questions/36112451/multiple-repositories-for-the-same-entity-in-spring-data-rest
Issue Links:
("depends on")
Referenced from: pull request spring-projects/spring-data-commons#465, and commits spring-projects/spring-data-commons@de7a7ca, spring-projects/spring-data-commons@d8705c1
40 votes, 40 watchers
The text was updated successfully, but these errors were encountered: