-
Notifications
You must be signed in to change notification settings - Fork 140
CBG-3203: Don't store channel history for non-leaf revisions #6368
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
base: main
Are you sure you want to change the base?
Changes from all commits
16fffbb
10507a7
44b8fac
dc69d8b
15ef848
058ce82
8b23e1e
5ce9e15
f32d938
8114336
1ae9ded
8f70fdd
84ad764
dc8985e
4c8d9ed
12da452
9359d53
29969e4
eaec520
3bfaaea
220c275
52495bc
fd3e91a
eacbe39
25c2fab
1c0e4cd
5698d3e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ package db | |
import ( | ||
"bytes" | ||
"context" | ||
"errors" | ||
"fmt" | ||
"math" | ||
"net/http" | ||
|
@@ -21,7 +22,7 @@ import ( | |
"github.com/couchbase/sync_gateway/auth" | ||
"github.com/couchbase/sync_gateway/base" | ||
"github.com/couchbase/sync_gateway/channels" | ||
"github.com/pkg/errors" | ||
pkgerrors "github.com/pkg/errors" | ||
) | ||
|
||
const ( | ||
|
@@ -529,21 +530,23 @@ func (db *DatabaseCollectionWithUser) Get1xRevAndChannels(ctx context.Context, d | |
} | ||
|
||
// Returns an HTTP 403 error if the User is not allowed to access any of this revision's channels. | ||
func (col *DatabaseCollectionWithUser) authorizeDoc(doc *Document, revid string) error { | ||
func (col *DatabaseCollectionWithUser) authorizeDoc(ctx context.Context, doc *Document, revid string) error { | ||
user := col.user | ||
if doc == nil || user == nil { | ||
return nil // A nil User means access control is disabled | ||
} | ||
if revid == "" { | ||
revid = doc.CurrentRev | ||
} | ||
if rev := doc.History[revid]; rev != nil { | ||
// Authenticate against specific revision: | ||
return col.user.AuthorizeAnyCollectionChannel(col.ScopeName, col.Name, rev.Channels) | ||
} else { | ||
// No such revision; let the caller proceed and return a 404 | ||
|
||
channelsForRev, ok := doc.channelsForRev(revid) | ||
if !ok { | ||
// No such revision | ||
// let the caller proceed and return a 404 | ||
return nil | ||
} else if channelsForRev == nil { | ||
// non-leaf (no channel info) - force 404 (caller would find the rev if it tried to look) | ||
return ErrMissing | ||
} | ||
|
||
return col.user.AuthorizeAnyCollectionChannel(col.ScopeName, col.Name, channelsForRev) | ||
} | ||
|
||
// Gets a revision of a document. If it's obsolete it will be loaded from the database if possible. | ||
|
@@ -681,14 +684,14 @@ func (db *DatabaseCollectionWithUser) getAncestorJSON(ctx context.Context, doc * | |
// instead returns a minimal deletion or removal revision to let them know it's gone. | ||
func (db *DatabaseCollectionWithUser) get1xRevFromDoc(ctx context.Context, doc *Document, revid string, listRevisions bool) (bodyBytes []byte, removed bool, err error) { | ||
var attachments AttachmentsMeta | ||
if err := db.authorizeDoc(doc, revid); err != nil { | ||
if err := db.authorizeDoc(ctx, doc, revid); err != nil { | ||
// 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) | ||
// Update: this applies to non-deletions too, since the client may have lost access to | ||
// the channel and gotten a "removed" entry in the _changes feed. It then needs to | ||
// incorporate that tombstone and for that it needs to see the _revisions property. | ||
if revid == "" || doc.History[revid] == nil { | ||
if revid == "" || doc.History[revid] == nil || errors.Is(err, ErrMissing) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From the comment on line 700, it seems like we want to bypass the security check for tombstones or removals (so that we successfully set bodyBytes to either EmptyDocument or RemovedRedactedDocument below). I'm wondering if the ErrMissing handling is going to break this, in particular for removals. I went looking for a test that covers this path - TestGetRemovedAsUser seems close but I don't think it purges the removal from the rev cache before attempting to retrieve. I think it would be good to extend TestGetRemovedAsUser for this scenario to see whether it changes before/after this PR:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same might be true for tombstones, although non-leaf tombstones are more of a corner case. It would be common for removals to be non-leaf revisions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My interpretation of the comment was that an old rev is neither removed from a channel, nor a tombstone. Is that incorrect? An old revision we don't have channel info for any more seems the same as somebody requesting a pruned revision, or There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking of the usage of IsChannelRemoval in revCacheLoaderForDocument , where we check the channel history to identify removals for revisions that aren't in the rev tree, and return a removal instead of 404 for those. I wasn't clear about the interaction (if any) between that handling and the changes here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed, will follow up and get test coverage for a removal that isn't cached and ensure behaviour doesn't change with this PR. |
||
return nil, false, err | ||
} | ||
if doc.History[revid].Deleted { | ||
|
@@ -1557,7 +1560,7 @@ func (db *DatabaseCollectionWithUser) addAttachments(ctx context.Context, newAtt | |
if errors.Is(err, ErrAttachmentTooLarge) || err.Error() == "document value was too large" { | ||
err = base.HTTPErrorf(http.StatusRequestEntityTooLarge, "Attachment too large") | ||
} else { | ||
err = errors.Wrap(err, "Error adding attachment") | ||
err = pkgerrors.Wrap(err, "Error adding attachment") | ||
} | ||
} | ||
return err | ||
|
@@ -1754,7 +1757,8 @@ func (col *DatabaseCollectionWithUser) documentUpdateFunc(ctx context.Context, d | |
return | ||
} | ||
|
||
if len(channelSet) > 0 { | ||
isWinningRev := doc.CurrentRev == newRevID | ||
if len(channelSet) > 0 && !isWinningRev { | ||
doc.History[newRevID].Channels = channelSet | ||
} | ||
|
||
|
@@ -1781,7 +1785,7 @@ func (col *DatabaseCollectionWithUser) documentUpdateFunc(ctx context.Context, d | |
return | ||
} | ||
} | ||
_, err = doc.updateChannels(ctx, channelSet) | ||
_, err = doc.updateChannels(ctx, isWinningRev, channelSet) | ||
if err != nil { | ||
return | ||
} | ||
|
@@ -2010,7 +2014,11 @@ func (db *DatabaseCollectionWithUser) updateAndReturnDoc(ctx context.Context, do | |
return nil, "", err | ||
} | ||
|
||
revChannels := doc.History[newRevID].Channels | ||
revChannels, ok := doc.channelsForRev(newRevID) | ||
if !ok { | ||
// Should be unreachable, as we've already checked History[newRevID] above ... | ||
return nil, "", base.RedactErrorf("unable to determine channels for %s/%s", base.UD(docid), newRevID) | ||
} | ||
documentRevision := DocumentRevision{ | ||
DocID: docid, | ||
RevID: newRevID, | ||
|
Uh oh!
There was an error while loading. Please reload this page.