Skip to content

Commit 99699a6

Browse files
authored
[3.2.2 backport] CBG-4377 create unsupported option for sending change in a channel filter on channel filter removal (#7291)
* [3.2.2 backport] CBG-4377 create unsupported option for sending change in a channel filter on channel filter removal * fix lint issue
1 parent 11624d1 commit 99699a6

File tree

3 files changed

+133
-16
lines changed

3 files changed

+133
-16
lines changed

db/blip_handler.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ func (bh *blipHandler) sendChanges(sender *blip.Sender, opts *sendChangesOptions
483483
// If change is a removal and we're running with protocol V3 and change change is not a tombstone
484484
// fall into 3.0 removal handling.
485485
// Changes with change.Revoked=true have already evaluated UserHasDocAccess in changes.go, don't check again.
486-
if change.allRemoved && bh.activeCBMobileSubprotocol >= CBMobileReplicationV3 && !change.Deleted && !change.Revoked {
486+
if change.allRemoved && bh.activeCBMobileSubprotocol >= CBMobileReplicationV3 && !change.Deleted && !change.Revoked && !bh.db.Options.UnsupportedOptions.BlipSendDocsWithChannelRemoval {
487487
// If client doesn't want removals / revocations, don't send change
488488
if !opts.revocations {
489489
continue
@@ -494,7 +494,6 @@ func (bh *blipHandler) sendChanges(sender *blip.Sender, opts *sendChangesOptions
494494
if err == nil && userHasAccessToDoc {
495495
continue
496496
}
497-
498497
// If we can't determine user access due to an error, log error and fall through to send change anyway.
499498
// In the event of an error we should be cautious and send a revocation anyway, even if the user
500499
// may actually have an alternate access method. This is the safer approach security-wise and

db/database.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -232,20 +232,21 @@ type APIEndpoints struct {
232232

233233
// UnsupportedOptions are not supported for external use
234234
type UnsupportedOptions struct {
235-
UserViews *UserViewsOptions `json:"user_views,omitempty"` // Config settings for user views
236-
OidcTestProvider *OidcTestProviderOptions `json:"oidc_test_provider,omitempty"` // Config settings for OIDC Provider
237-
APIEndpoints *APIEndpoints `json:"api_endpoints,omitempty"` // Config settings for API endpoints
238-
WarningThresholds *WarningThresholds `json:"warning_thresholds,omitempty"` // Warning thresholds related to _sync size
239-
DisableCleanSkippedQuery bool `json:"disable_clean_skipped_query,omitempty"` // Clean skipped sequence processing bypasses final check (deprecated: CBG-2672)
240-
OidcTlsSkipVerify bool `json:"oidc_tls_skip_verify,omitempty"` // Config option to enable self-signed certs for OIDC testing.
241-
SgrTlsSkipVerify bool `json:"sgr_tls_skip_verify,omitempty"` // Config option to enable self-signed certs for SG-Replicate testing.
242-
RemoteConfigTlsSkipVerify bool `json:"remote_config_tls_skip_verify,omitempty"` // Config option to enable self signed certificates for external JavaScript load.
243-
GuestReadOnly bool `json:"guest_read_only,omitempty"` // Config option to restrict GUEST document access to read-only
244-
ForceAPIForbiddenErrors bool `json:"force_api_forbidden_errors,omitempty"` // Config option to force the REST API to return forbidden errors
245-
ConnectedClient bool `json:"connected_client,omitempty"` // Enables BLIP connected-client APIs
246-
UseQueryBasedResyncManager bool `json:"use_query_resync_manager,omitempty"` // Config option to use Query based resync manager to perform Resync op
247-
DCPReadBuffer int `json:"dcp_read_buffer,omitempty"` // Enables user to set their own DCP read buffer
248-
KVBufferSize int `json:"kv_buffer,omitempty"` // Enables user to set their own KV pool buffer
235+
UserViews *UserViewsOptions `json:"user_views,omitempty"` // Config settings for user views
236+
OidcTestProvider *OidcTestProviderOptions `json:"oidc_test_provider,omitempty"` // Config settings for OIDC Provider
237+
APIEndpoints *APIEndpoints `json:"api_endpoints,omitempty"` // Config settings for API endpoints
238+
WarningThresholds *WarningThresholds `json:"warning_thresholds,omitempty"` // Warning thresholds related to _sync size
239+
DisableCleanSkippedQuery bool `json:"disable_clean_skipped_query,omitempty"` // Clean skipped sequence processing bypasses final check (deprecated: CBG-2672)
240+
OidcTlsSkipVerify bool `json:"oidc_tls_skip_verify,omitempty"` // Config option to enable self-signed certs for OIDC testing.
241+
SgrTlsSkipVerify bool `json:"sgr_tls_skip_verify,omitempty"` // Config option to enable self-signed certs for SG-Replicate testing.
242+
RemoteConfigTlsSkipVerify bool `json:"remote_config_tls_skip_verify,omitempty"` // Config option to enable self signed certificates for external JavaScript load.
243+
GuestReadOnly bool `json:"guest_read_only,omitempty"` // Config option to restrict GUEST document access to read-only
244+
ForceAPIForbiddenErrors bool `json:"force_api_forbidden_errors,omitempty"` // Config option to force the REST API to return forbidden errors
245+
ConnectedClient bool `json:"connected_client,omitempty"` // Enables BLIP connected-client APIs
246+
UseQueryBasedResyncManager bool `json:"use_query_resync_manager,omitempty"` // Config option to use Query based resync manager to perform Resync op
247+
DCPReadBuffer int `json:"dcp_read_buffer,omitempty"` // Enables user to set their own DCP read buffer
248+
KVBufferSize int `json:"kv_buffer,omitempty"` // Enables user to set their own KV pool buffer
249+
BlipSendDocsWithChannelRemoval bool `json:"blip_send_docs_with_channel_removal,omitempty"` // Enables sending docs with channel removals using channel filters
249250
}
250251

251252
type WarningThresholds struct {

rest/blip_channel_filter_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright 2024-Present Couchbase, Inc.
2+
//
3+
// Use of this software is governed by the Business Source License included
4+
// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified
5+
// in that file, in accordance with the Business Source License, use of this
6+
// software will be governed by the Apache License, Version 2.0, included in
7+
// the file licenses/APL2.txt.
8+
9+
package rest
10+
11+
import (
12+
"fmt"
13+
"net/http"
14+
"testing"
15+
16+
"github.com/couchbase/sync_gateway/channels"
17+
"github.com/couchbase/sync_gateway/db"
18+
"github.com/stretchr/testify/require"
19+
)
20+
21+
func TestChannelFilterRemovalFromChannel(t *testing.T) {
22+
btcRunner := NewBlipTesterClientRunner(t)
23+
btcRunner.Run(func(t *testing.T, _ []string) {
24+
for _, sendDocWithChannelRemoval := range []bool{true, false} {
25+
t.Run(fmt.Sprintf("sendDocWithChannelRemoval=%v", sendDocWithChannelRemoval), func(t *testing.T) {
26+
rt := NewRestTester(t, &RestTesterConfig{
27+
SyncFn: channels.DocChannelsSyncFunction,
28+
PersistentConfig: true,
29+
})
30+
defer rt.Close()
31+
32+
dbConfig := rt.NewDbConfig()
33+
dbConfig.Unsupported = &db.UnsupportedOptions{
34+
BlipSendDocsWithChannelRemoval: sendDocWithChannelRemoval,
35+
}
36+
rt.CreateDatabase("db", dbConfig)
37+
rt.CreateUser("alice", []string{"*"})
38+
rt.CreateUser("bob", []string{"A"})
39+
40+
btc := btcRunner.NewBlipTesterClientOptsWithRT(rt, &BlipTesterClientOpts{
41+
Username: "alice",
42+
Channels: []string{"A"},
43+
SendRevocations: false,
44+
})
45+
defer btc.Close()
46+
47+
client := btcRunner.SingleCollection(btc.id)
48+
const docID = "doc1"
49+
version1 := rt.PutDoc("doc1", `{"channels":["A"]}`)
50+
require.NoError(t, rt.WaitForPendingChanges())
51+
52+
response := rt.SendUserRequest("GET", "/{{.keyspace}}/_changes?since=0&channels=A&include_docs=true", "", "alice")
53+
RequireStatus(t, response, http.StatusOK)
54+
55+
expectedChanges1 := fmt.Sprintf(`
56+
{
57+
"results": [
58+
{"seq":1, "id": "_user/alice", "changes":[]},
59+
{"seq":3, "id": "doc1", "doc": {"_id": "doc1", "_rev":"%s", "channels": ["A"]}, "changes": [{"rev":"%s"}]}
60+
],
61+
"last_seq": "3"
62+
}`, version1.RevID, version1.RevID)
63+
require.JSONEq(t, expectedChanges1, string(response.BodyBytes()))
64+
65+
require.NoError(t, client.StartPullSince(BlipTesterPullOptions{Continuous: false, Since: "0", Channels: "A"}))
66+
67+
btcRunner.WaitForVersion(btc.id, docID, version1)
68+
69+
// remove channel A from doc1
70+
version2 := rt.UpdateDoc(docID, version1, `{"channels":["B"]}`)
71+
markerDocID := "marker"
72+
markerDocVersion := rt.PutDoc(markerDocID, `{"channels":["A"]}`)
73+
require.NoError(t, rt.WaitForPendingChanges())
74+
75+
// alice will see doc1 rev2 with body
76+
response = rt.SendUserRequest("GET", "/{{.keyspace}}/_changes?since=2&channels=A&include_docs=true", "", "alice")
77+
RequireStatus(t, response, http.StatusOK)
78+
79+
aliceExpectedChanges2 := fmt.Sprintf(`
80+
{
81+
"results": [
82+
{"seq":4, "id": "%s", "doc": {"_id": "%s", "_rev":"%s", "channels": ["B"]}, "changes": [{"rev":"%s"}]},
83+
{"seq":5, "id": "%s", "doc": {"_id": "%s", "_rev":"%s", "channels": ["A"]}, "changes": [{"rev":"%s"}]}
84+
],
85+
"last_seq": "5"
86+
}`, docID, docID, version2.RevID, version2.RevID, markerDocID, markerDocID, markerDocVersion.RevID, markerDocVersion.RevID)
87+
require.JSONEq(t, aliceExpectedChanges2, string(response.BodyBytes()))
88+
89+
require.NoError(t, client.StartPullSince(BlipTesterPullOptions{Continuous: false, Since: "0", Channels: "A"}))
90+
91+
if sendDocWithChannelRemoval {
92+
data := btcRunner.WaitForVersion(btc.id, docID, version2)
93+
require.Equal(t, `{"channels":["B"]}`, string(data))
94+
} else {
95+
client.WaitForVersion(markerDocID, markerDocVersion)
96+
doc, ok := client.GetDoc(docID)
97+
require.True(t, ok)
98+
require.Equal(t, `{"channels":["A"]}`, string(doc))
99+
}
100+
101+
// bob will not see doc1
102+
response = rt.SendUserRequest("GET", "/{{.keyspace}}/_changes?since=2&channels=A&include_docs=true", "", "bob")
103+
RequireStatus(t, response, http.StatusOK)
104+
105+
bobExpectedChanges2 := fmt.Sprintf(`
106+
{
107+
"results": [
108+
{"seq":4, "id": "doc1", "removed":["A"], "doc": {"_id": "doc1", "_rev":"%s", "_removed": true}, "changes": [{"rev":"%s"}]},
109+
{"seq":5, "id": "%s", "doc": {"_id": "%s", "_rev":"%s", "channels": ["A"]}, "changes": [{"rev":"%s"}]}
110+
],
111+
"last_seq": "5"
112+
}`, version2.RevID, version2.RevID, markerDocID, markerDocID, markerDocVersion.RevID, markerDocVersion.RevID)
113+
require.JSONEq(t, bobExpectedChanges2, string(response.BodyBytes()))
114+
})
115+
}
116+
})
117+
}

0 commit comments

Comments
 (0)