Skip to content

Commit

Permalink
Allow client to GET deletion revs even if it doesn't have channel access
Browse files Browse the repository at this point in the history
Otherwise the client replicator can't process a deletion that appears in
the _changes feed, because it wants to fetch the body of the revision,
but a deleted rev won't be in any channels.
Fixes #59.
  • Loading branch information
snej committed Jun 18, 2013
1 parent 961dd4e commit 8531cc9
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 1 deletion.
8 changes: 7 additions & 1 deletion src/github.com/couchbaselabs/sync_gateway/db/crud.go
Expand Up @@ -142,8 +142,14 @@ func (db *Database) getRevisionJSON(doc *document, revid string) ([]byte, error)

// Returns the body of a revision given a document struct
func (db *Database) getRevFromDoc(doc *document, revid string, listRevisions bool) (Body, error) {
// FIX: This only authorizes vs the current revision, not the one the client asked for!
if err := AuthorizeAnyDocChannels(db.user, doc.Channels); err != nil {
// FIX: This only authorizes vs the current revision, not the one the client asked for!
// As a special case, you don't need channel access to see a deletion revision,
// otherwise the client's replicator can't process the deletion (since deletions
// usually aren't on any channels at all!) But don't show the full body. (See #59)
if revid != "" && doc.History[revid].Deleted {
return Body{"_id": doc.ID, "_rev": revid, "_deleted": true}, nil
}
return nil, err
}
if revid == "" {
Expand Down
59 changes: 59 additions & 0 deletions src/github.com/couchbaselabs/sync_gateway/rest/rest_test.go
Expand Up @@ -538,3 +538,62 @@ func TestRoleAccessChanges(t *testing.T) {
assert.Equals(t, len(changes.Results), 1)
assert.Equals(t, changes.Results[0].ID, "g1")
}

func TestDocDeletionFromChannel(t *testing.T) {
// See https://github.com/couchbase/couchbase-lite-ios/issues/59
//base.LogKeys["CRUD"] = true

rt := restTester{syncFn: `function(doc) {channel(doc.channel)}`}
a := rt.serverContext().databases["db"].auth

// Create user:
alice, _ := a.NewUser("alice", "letmein", channels.SetOf("zero"))
a.Save(alice)

// Create a doc Alice can see:
response := rt.send(request("PUT", "/db/alpha", `{"channel":"zero"}`))

// Check the _changes feed:
var changes struct {
Results []db.ChangeEntry
}
response = rt.send(requestByUser("GET", "/db/_changes", "", "alice"))
log.Printf("_changes looks like: %s", response.Body.Bytes())
json.Unmarshal(response.Body.Bytes(), &changes)
assert.Equals(t, len(changes.Results), 1)
since := changes.Results[0].Seq
assert.Equals(t, since, "zero:1")

assert.Equals(t, changes.Results[0].ID, "alpha")
rev1 := changes.Results[0].Changes[0]["rev"]

// Delete the document:
assertStatus(t, rt.send(request("DELETE", "/db/alpha?rev="+rev1, "")), 200)

// Get the updates from the _changes feed:
response = rt.send(requestByUser("GET", "/db/_changes?since="+since, "", "alice"))
log.Printf("_changes looks like: %s", response.Body.Bytes())
changes.Results = nil
json.Unmarshal(response.Body.Bytes(), &changes)
assert.Equals(t, len(changes.Results), 1)

assert.Equals(t, changes.Results[0].ID, "alpha")
assert.Equals(t, changes.Results[0].Deleted, true)
assert.DeepEquals(t, changes.Results[0].Removed, base.SetOf("zero"))
rev2 := changes.Results[0].Changes[0]["rev"]

// Now get the deleted revision:
response = rt.send(requestByUser("GET", "/db/alpha?rev="+rev2, "", "alice"))
assert.Equals(t, response.Code, 200)
log.Printf("Deletion looks like: %s", response.Body.Bytes())
var docBody db.Body
json.Unmarshal(response.Body.Bytes(), &docBody)
assert.DeepEquals(t, docBody, db.Body{"_id": "alpha", "_rev": rev2, "_deleted": true})

// Access without deletion revID shouldn't be allowed since doc is not in Alice's channels:
response = rt.send(requestByUser("GET", "/db/alpha", "", "alice"))
assert.Equals(t, response.Code, 403)

response = rt.send(requestByUser("GET", "/db/alpha?rev=bogus", "", "alice"))
assert.Equals(t, response.Code, 403)
}

0 comments on commit 8531cc9

Please sign in to comment.