BUG: Recreating deleted document can break replication when VDU function is active
- Dominant language
- Erlang
- Stars
- 7k
- Forks
- 1.1k
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 9
Description
It is possible to recreate (i mean: to update a deleted document with new content which hasn't the flag _deleted set to true) a document without need to specify revision field (_rev). However this behaviour has been changed somehow in CouchDB 2+.
In CouchDB 1.X, one can recreate document with the correct revision. It is also possible to recreate document without the revision and in this case, the CouchDB 1.X uses the document's recent revision. The rest of the operation is similar to updating existing document, including how the VDU (validate_doc_update) is called.
Consider following declaration (formatted to achieve better reading)
```
"validate_doc_update": function(newDoc, oldDoc, userCtx) {
log(["validation oldDoc, newDoc, userCtx", oldDoc, newDoc, userCtx]);
function unchanged(field) {
if (oldDoc && (field in oldDoc) && toJSON(oldDoc[field]) != toJSON(newDoc[field]))
throw({forbidden : "Field can't be changed: " + field});
};
unchanged("owner");
}"
```
In normal operation, `oldDoc` contains old document, `newDoc` contains new document. If the document is being created, the function is called with `oldDoc == null`. For all other updates the function is called with `oldDoc` equal to the body of the document being updated.
In `CouchDB 1+` this is also applied for deleted documents, when the document is recreated, regardless on whether it is recreated with _rev or without the oldDoc contains the body of the deleted document.
In CouchDB 2+ this is no longer true. When the document is being recreated, oldDoc is always null. **This behaviour can break replication** in situation, when the replication is not continuous and when the document is deleted and recreated between each replication round.
## Expected Behavior
If the document is recreated, the oldDoc of VDU should contain the body of previously deleted document
## Current Behavior
If the document is recreated, the oldDoc of VDU is set to null
## Possible Solution
The recreating of the document is mistakenly handled as a creation of the new document. It should be handled as an update.
## Steps to Reproduce (for bugs)
1. Prepare a design document with VDU defined above
2. Create two databases (db1, db2) and put that design document in the both of them
3. In the first database (db1), create document with a field "owner". For example
```
{
"_id": "bug",
"owner": "jack"
}
```
4. ensure, that owner cannot be changed.
5. also ensure, that document cannot be deleted using DELETE (VDU blocks correctly)
6. replicate db1 to db2
7. in the first database (db1), delete the document "bug" by adding "_deleted":true to the document
8. recreate document "bug" with other owner (this will work, because VDU is called with null)
```
{
"_id": "bug",
"owner": "marie"
}
```
9. replicate db1 to db2
10. Result: document is not replicated. There is also error message sitting in the LOG file
```
Replicator: couldn't write document `bug`, revision `4-e0cc418137015d862ef83eb6d714b21c`, to target database `http://localhost:5984/db2/`. Error: `forbidden`, reason: `Field can't be changed: owner`.
```
This bug is dangerous in a one way. There is no way to detect, that the document is being recreated so the creator can create a document which suddenly cannot be replicated. There is also no way how to prevent such situation, because the VDU cannot distinguish between creation and recreation
creation:
```
Log :: ["validation oldDoc, newDoc, userCtx",null,{"_id":"loc.test","owner":"jack"},{"db":"db1","name":"admin","roles":["_admin"]}]
```
recreation:
```
Log :: ["validation oldDoc, newDoc, userCtx",null,{"_id":"loc.test","owner":"marie"},{"db":"db1","name":"admin","roles":["_admin"]}]
```
## Context
The main goal of the checking the owner in the VDU is to prevent user to steal a document owned by other user.
## Your Environment
* Version used: 2.1.1
* Browser Name and version: not relevant
* Operating System and version (desktop or mobile): Ubuntu 16.04
* Link to your project: not public
Contributor guide
Research direction
Start with the validate_doc_update behavior described in the reproduction, then run the db1/db2 replication sequence involving deletion and recreation. Trace how the VDU receives oldDoc during recreation and replication. Done means recreated documents provide the previously deleted document body to oldDoc and replicate without the owner validation inconsistency.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- erlang, javascript
- Domain
- databases, distributed-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100