question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

SimpleCouchbaseRepository.findById() and CouchbaseTemplate.findById() do not validate the document type

See original GitHub issue

It is possible to save() a document of one type, and retrieve it using another type.

The below example will succeed, where one might expect it to fail.

public class Group {
    @Id
    @GeneratedValue(strategy = GenerationStrategy.UNIQUE)
    String id;
    @Field
    String name;
}

@Repository
public interface GroupRepository extends CouchbaseRepository<Group, String> {
}

public class User {
    @Id
    @GeneratedValue(strategy = GenerationStrategy.UNIQUE)
    String id;
    @Field
    String name;
}

@Repository
public interface UserRepository extends CouchbaseRepository<User, String> {
}

@SpringBootApplication
@EnableCouchbaseRepositories
public class ExampleApplication implements ApplicationRunner {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private GroupRepository groupRepository;

    public static void main(final String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }

    @Override
    public void run(final ApplicationArguments args) throws Exception {
        User user = userRepository.save(new User());
        System.out.println(user.id);
        System.out.println(groupRepository.findById(user.id)
                .get().id);
    }
}

The underlying code in SimpleCouchbaseRepository#findById

calls

return Optional.ofNullable(couchbaseOperations.findById(entityInformation.getJavaType()).one(id.toString()));

CouchbaseTemplate should at least try and validate that the supplied type matches the retrieved document type, and either return null (my personal preference), or throw an exception that the underlying type does not match (still acceptable).

We had implemented a workaround for the previous Spring Data Couchbase 3.x series, that prevented this behaviour as it was flagged by our qa/security team as a security issue.

It’s probably just a good idea to solve it at the source and for everyone?

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:32 (17 by maintainers)

github_iconTop GitHub Comments

1reaction
aaronjwhitesidecommented, Feb 3, 2021

_class is not a security compartment

You’re right it’s not directly related to security, but it’s used to enforce type safety. And bugs with type safety can lead to exposing sensitive information. So indirectly I assert that it IS a security concern.

Java is generally considered a type safe language (there are limits and arguments to be made) but it’s not javascript or ruby.

At the Java level when defining and using a Repository there is an explicit contract on the types that the repository will accept and produce.

@Repository
public interface GroupRepository extends CouchbaseRepository<Group, String> {
}

GroupRepository#save() will only compile if Group types are passed in.

And Repository#findById() will only return instances of Group.

However it seems that although type information is persisted and maintained by save() it is ignored by findById(). Strangely enough if findAll() where to return documents of a different type, this would be considered a bug… after all the explicit type contract was neglected, was it not?

In the context of a Repository findById() is the outlier that does not enforce type safety. Why is it special?

To be thorough I think Repository#deleteById(), #deleteAllById, #existsById also suffers from the same problem.

Security is on buckets.

Yes it is.

I also remember Couchbase Professional Services recommending that for micro services we have them share a bucket. Due to scaling limitations of Couchbase, If I remember correctly it performs really badly with more than 10 buckets. And we have 60 odd microservices at last count.

We do have a seperate security bucket for really sensitive information. So there is that.

But even within a single micro service it can and does in our case, contain multiple document types. Represented as distinct resources via a REST API.

And I agree with you that the micro service probably needs to enforce this, not Couchbase itself. I’m not arguing that it be handled by Couchbase for me. I’m just pointing out that Spring Data Couchbase is intended to be a high level interface to Couchbase which provides specific and different (from the raw SDK) paradigms for accessing it. I think the responsibility lies here.

This would prevent an application from having Employee documents read as either Manager entities or IndividualContributor entities.

I’m not discounting this use case, but I am saying I believe it’s definitely not valid in the context of a Repository.

And I think we probably crossover into talking about polymorphism and how to represent that for Couchbase documents, in terms of ORMs JPA and Hibernate are probably a good base line here.

And to reenforce the argument, if one does not want type safety and wants to duck type documents, then they probably shouldn’t be Spring Data Couchbase? As it’s design makes the document types explicit unlike the underlying Couchbase SDK.

The current behavior is the equivalent to the “as(OtherEntity.class)” projection on the query api.

I assume you mean GetResult#contentAs(OtherEntity.class)?

So CouchbaseTemplate has no specific generic type, and always requires the user to pass in the type they want returned. This makes sense, it’s at a lower level than the SimpleCouchbaseRepository implementation.

Given it does accept any class type in the insertById() method, and persist that class type into a field in the raw document. And there is a natural assumed symmetry between insertById() and findById().

I believe it should also allow one to enforce the type of the underlying documents. Maybe that’s by a configuration flag, or by a new explicit type safe method, typeSafeFindById() or perhaps findById(Some.class).verifyType().one("123").

If a microservice is going to rely _class for security, then it will need to enforce it.

But how?

By the time CouchbaseTemplate#findById() has returned, the type information is gone… lost.

And if your answer is going to be, well you can just add another query method to your repository… as a work around. Right sure, so for all the ById methods in the CouchbaseRepository interface, I’ll re-implement them because the out of the box methods have broken the explicit type contract as defined in the repository class definition…

I’m preemptively saying I think this argument is weak at best.

I believe that CouchbaseTemplate needs to provide this functionality out of the box.

0reactions
aaronjwhitesidecommented, Feb 4, 2021

And your intention was to have _class that was verified against the repository and I gave you that.

True enough.

I said if you want to identify the type from the key only, then you need to put that identification in the key. It’s not really a recommendation - it’s a fact.

I never wanted for applications to have to identify the type from the key 😃

We only originally did it for aiding human troubleshooting, when looking at logs and other output. Our previous system based on MsSql used the same approach for identifiers. And I think when Couchbase Processional services recommended it (some 4 years ago now), we didn’t think too hard about it. Certainly we never intended our applications to verify the prefix in any way.

What I really WANT is for Spring Data Couchbase to not violate the generic interface contract as defined in the repository interfaces, I want that type declaration to be honored for all methods in the interface, not just methods that are backed by N1QL.

I understand the KV store doesn’t make this easy to do. There is a mismatch between Couchbase’s capabilities and what Java tells you the type will be.

You’ve given me a workaround, which is wonderful.

But I think you really need to make others aware of this quirk by documenting it, and perhaps for those who want the same sanity document the same workaround you provided to me.

And if I think about it it seems the scopes and collections feature in the upcoming CB7 is an attempt to right this wrong. I.e. Using key prefixes isn’t ideal, and there’s probably a better way to do it… so try this approach.

The CustomConverter is better than having an annotation or something else baked in the product. Imagine six months from now that you want it to do something different. If it was in the product, you’d have open an issue, get it accepted, then wait for a change and then a release. In the converter, you can change it on a moment’s notice, you can have auditing, allow exceptions for specific classes or services - whatever.

Yes, we value composition over inheritance. Which is what I ultimately think your trying to say here. Not that you’re taking about inheritance per se.

But violating the type contract of a generic interface and us having to implement a Custom Converter to fix it? That’s not really a technology requirement specific to us… it’s applicable to all Java applications.

I remember when Java 5 came out and suddenly, you could apply a generic type to a Collection. And I’m sure we had code to fix that was broken by the idea of being able to assign a single type to a Collection. When we were exploiting it to store many different types. These days you can declare a List and store anything in it, but people rarely do, and it’s even frowned upon in polite society 😉

So having said all that, let me take what you have provided and let me see how I can integrate it and if it is good enough for our purposes.

I would also like if you took this feedback and ensured when it comes time to integrate scopes and collections into the repositories that you can make them honor the declared type. Default a repositories collection to the simple name of the entity?

Read more comments on GitHub >

github_iconTop Results From Across the Web

SimpleCouchbaseRepository.findOne() doesn't validate ...
SimpleCouchbaseRepository.findOne() doesn't validate _class types. ... but not properly initialized - no exceptions are raised.
Read more >
SpringBoot CouchbaseTemplate: findbyId is working But ...
findById (Entity.class).one(id) is working fine, I am able to fetch data · findByQuery(Entity.class).all() returning empty array · findByQuery( ...
Read more >
Spring Data Couchbase - Reference Documentation
If the domain class is annotated with the module-specific type annotation, then it's a valid candidate for the particular Spring Data module.
Read more >
Index (Spring Data Couchbase 2.2.10.RELEASE API)
Returns an array containing the constants of this enum type, in the order they are declared. values() - Static method in enum org.springframework.data.couchbase ......
Read more >
Intro to Spring Data Couchbase
First, we add the following Maven dependency to our pom.xml file: ... Another popular reason to override typeKey() is if you are using...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found