From 1e1a686d0c2d9295cd44612f21931d133d855b33 Mon Sep 17 00:00:00 2001 From: Gregory Newman-Smith <109068393+gregns1@users.noreply.github.com> Date: Tue, 24 Oct 2023 20:20:34 +0100 Subject: [PATCH] CBG-3559: Inherited channels from roles are not checked when running changes feed filtered to a channel (#6551) (#6552) * CBG-3559: ensure we check inherited channels across assigned roles when filtering the changes feed to availible channels to the user * chnage implementation, add userImpl canSeeCollectionChannelSince * remove unused function --- auth/user_collection_access.go | 10 +++++ rest/changestest/changes_api_test.go | 65 ++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/auth/user_collection_access.go b/auth/user_collection_access.go index 85292375c9..f19dfbc8fa 100644 --- a/auth/user_collection_access.go +++ b/auth/user_collection_access.go @@ -100,3 +100,13 @@ func (user *userImpl) AuthorizeAnyCollectionChannel(scope, collection string, ch return user.UnauthError("You are not allowed to see this") } + +func (user *userImpl) canSeeCollectionChannelSince(scope, collection, channel string) uint64 { + minSeq := user.roleImpl.canSeeCollectionChannelSince(scope, collection, channel) + for _, role := range user.GetRoles() { + if seq := role.canSeeCollectionChannelSince(scope, collection, channel); seq > 0 && (seq < minSeq || minSeq == 0) { + minSeq = seq + } + } + return minSeq +} diff --git a/rest/changestest/changes_api_test.go b/rest/changestest/changes_api_test.go index 4876de4949..215396ad64 100644 --- a/rest/changestest/changes_api_test.go +++ b/rest/changestest/changes_api_test.go @@ -188,6 +188,71 @@ func TestDocDeletionFromChannel(t *testing.T) { assert.Equal(t, 200, response.Code) } +// TestChangesFeedOnInheritedChannelsFromRoles: +// - Create a role with access to channel "A" +// - Create a user with the role just created +// - Put some docs on the database with channel "A" assigned +// - Run changes feed as the user with a channel filter on it to channel A +func TestChangesFeedOnInheritedChannelsFromRoles(t *testing.T) { + base.SetUpTestLogging(t, base.LevelInfo, base.KeyChanges, base.KeyHTTP) + + rt := rest.NewRestTester(t, &rest.RestTesterConfig{SyncFn: channels.DocChannelsSyncFunction}) + defer rt.Close() + collection := rt.GetSingleTestDatabaseCollection() + + // create role with collection channel access set to channel A + resp := rt.SendAdminRequest(http.MethodPut, "/{{.db}}/_role/role", rest.GetRolePayload(t, "", "", collection, []string{"A"})) + rest.RequireStatus(t, resp, http.StatusCreated) + + // create user and assign the role create above to that user + resp = rt.SendAdminRequest(http.MethodPut, "/{{.db}}/_user/alice", `{"name": "alice", "password": "letmein", "admin_roles": ["role"]}`) + rest.RequireStatus(t, resp, http.StatusCreated) + + // Put a some doc on the database with channel A assigned + for i := 0; i < 5; i++ { + resp = rt.SendAdminRequest(http.MethodPut, "/{{.keyspace}}/"+fmt.Sprint(i), `{"source": "rt", "channels":["A"]}`) + rest.RequireStatus(t, resp, http.StatusCreated) + } + + // Start changes feed as the user filtered to channel A, expect 6 changes (5 docs + user doc) + changes, err := rt.WaitForChanges(6, "/{{.keyspace}}/_changes?filter=sync_gateway/bychannel&channels=A", "alice", false) + require.NoError(t, err) + assert.Equal(t, 6, len(changes.Results)) +} + +// TestChangesFeedOnInheritedChannelsFromRolesDefaultCollection: +// - Same concept as TestChangesFeedOnInheritedChannelsFromRoles just with use of default collection instead +// - Create a role with access to channel "A" +// - Create a user with the role just created +// - Put some docs on the database with channel "A" assigned +// - Run changes feed as the user with a channel filter on it to channel A +func TestChangesFeedOnInheritedChannelsFromRolesDefaultCollection(t *testing.T) { + base.SetUpTestLogging(t, base.LevelInfo, base.KeyChanges, base.KeyHTTP) + + rt := rest.NewRestTesterDefaultCollection(t, &rest.RestTesterConfig{SyncFn: channels.DocChannelsSyncFunction}) + defer rt.Close() + collection := rt.GetSingleTestDatabaseCollection() + + // create role with collection channel access set to channel A + resp := rt.SendAdminRequest(http.MethodPut, "/{{.db}}/_role/role", rest.GetRolePayload(t, "", "", collection, []string{"A"})) + rest.RequireStatus(t, resp, http.StatusCreated) + + // create user and assign the role create above to that user + resp = rt.SendAdminRequest(http.MethodPut, "/{{.db}}/_user/alice", `{"name": "alice", "password": "letmein", "admin_roles": ["role"]}`) + rest.RequireStatus(t, resp, http.StatusCreated) + + // Put a some doc on the database with channel A assigned + for i := 0; i < 5; i++ { + resp = rt.SendAdminRequest(http.MethodPut, "/{{.keyspace}}/"+fmt.Sprint(i), `{"source": "rt", "channels":["A"]}`) + rest.RequireStatus(t, resp, http.StatusCreated) + } + + // Start changes feed as the user filtered to channel A, expect 6 changes (5 docs + user doc) + changes, err := rt.WaitForChanges(6, "/{{.keyspace}}/_changes?filter=sync_gateway/bychannel&channels=A", "alice", false) + require.NoError(t, err) + assert.Equal(t, 6, len(changes.Results)) +} + func TestPostChanges(t *testing.T) { base.SetUpTestLogging(t, base.LevelInfo, base.KeyChanges, base.KeyHTTP)