Skip to content
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

Refactor document code, and avoid [un]marshaling doc bodies #6108

Open
wants to merge 87 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
d07305f
Moved document, revision, attachment stuff into new 'document' package
snej Feb 22, 2023
62d8e07
Moved validation.go from db to document
snej Feb 23, 2023
6755026
Moved RevisionCache from db to document
snej Feb 23, 2023
a078e59
Merge branch 'master' into refactor-document
snej Mar 9, 2023
2ba5640
rest has its own Body type, not using db.Body anymore
snej Mar 9, 2023
91039b6
Moved document.ParseRevisions to rest
snej Mar 10, 2023
9f3c599
Extracted document.SyncData into its own source file
snej Mar 10, 2023
cf97cf5
Fixed remaining build errors by casting params to db.Body
snej Mar 10, 2023
63d4064
Implemented base.JSONExtractUnderscored() [EE only so far]
snej Mar 13, 2023
b70ec3f
Making DocumentRevision use JSONExtract fn
snej Mar 14, 2023
1b40494
Merge branch 'master' into refactor-document
snej Mar 14, 2023
073245f
tweak ParseDocumentRevision
snej Mar 14, 2023
3a6fd33
DocumentRevision.BodyBytes is now a method not a field
snej Mar 14, 2023
5eeb9e9
Removed DocumentRevision.MutableBody(), renamed Body()
snej Mar 14, 2023
ff3184e
Removed Body from RevisionCache
snej Mar 14, 2023
48eb314
Removed includeBody parameters from RevisionCache interface
snej Mar 14, 2023
57d4e33
Fixed some spots where BodyBytes didn't get "()" appended
snej Mar 14, 2023
f486410
Fixing test failures in db
snej Mar 14, 2023
eeae2a6
More bug fixes & test fixes
snej Mar 15, 2023
605c3fe
Cleaned up GetRev, getRev, Get1xBody methods
snej Mar 15, 2023
4ec9bfe
Avoid unmarshaling rev in blipHandler.processRev
snej Mar 15, 2023
2ee0774
rest.Body is the same as db.Body again (disabled earlier change)
snej Mar 15, 2023
f112552
Optimize creating DocumentRevision from pre-parsed merged delta
snej Mar 15, 2023
99291b0
Some minor improvements
snej Mar 15, 2023
028e55a
Optimized Document.UnmarshalJSON; a few other fixes
snej Mar 15, 2023
127db6a
Removed several call sites of Document.Body()
snej Mar 16, 2023
1866ccd
Stop using Document.Body in handlePutDocReplicator2
snej Mar 16, 2023
93f6be8
Document no longer caches Body. Renamed Body()->UnmarshalBody()
snej Mar 16, 2023
58bad1c
Merge branch 'master' into refactor-document
snej Mar 16, 2023
ffe0dd5
Cleanup & minor API changes
snej Mar 16, 2023
73e50a1
Backed out giving rest its own Body type
snej Mar 16, 2023
405fc9c
More tweaks & cleanup
snej Mar 16, 2023
ca75d3f
Use DocAttachment struct instead of map[string]any
snej Mar 17, 2023
0500c52
Merge branch 'master' into refactor-document
snej Mar 21, 2023
e85716b
Some attachment-related fixes
snej Mar 21, 2023
f357695
Fixed some issues in attachment_compaction
snej Mar 23, 2023
29e6f3a
Fixed: JSONExtractObject mishandles input `null`
snej Mar 23, 2023
6195b20
Fixed issues importing document with body `null`
snej Mar 23, 2023
ddf6d7e
Fixed some replicator tests
snej Mar 24, 2023
4a93892
Merge branch 'master' into refactor-document
snej Mar 24, 2023
70b005b
Slight change to attachment map types to fix delta compression; ALL T…
snej Mar 27, 2023
0cfe123
added missing license comments
snej Mar 29, 2023
7deef64
add missing mod and run go mod tidy
torcolvin Mar 29, 2023
ed7a4af
Merge branch 'master' into refactor-document
snej Apr 12, 2023
271f56d
Renamed package document --> documents
snej Apr 13, 2023
dfc5965
Cleanup and doc-comments.
snej Apr 13, 2023
cb656e6
Merge branch 'master' into refactor-document
snej Apr 18, 2023
f9dd5e9
CBG-2894: Reject user auth when channel threshold is over 500 (#6214)
gregns1 May 9, 2023
ae024a2
Report the correct error (#6232)
torcolvin May 9, 2023
c776492
CBG-2916 Add database examples with scopes (#6233)
torcolvin May 10, 2023
ddb447f
Update scopes documentation (#6231)
torcolvin May 10, 2023
370a2bd
CBG-2895: Add static replication connection limit (#6226)
gregns1 May 11, 2023
80d5eae
API Spec cleanup (scopes/collections) (#6236)
bbrks May 11, 2023
a31be80
CBG-2928 add blip stats for database (#6229)
torcolvin May 11, 2023
f56fba3
add x-additionalPropertiesName (#6238)
torcolvin May 11, 2023
4b7c051
Merge branch 'master' into refactor-document
snej May 11, 2023
b9a08ed
CBG-2938: Ignore Cbgt EOF feed errors when intentionally stopped (#6235)
bbrks May 12, 2023
1bdd12c
Rename TestStatusAfterReplicationRebalanceFail to avoid 'Fail:' searc…
bbrks May 15, 2023
36a3204
CBG-2853 Allow one-shot replications to wait for DCP to catch up on c…
adamcfraser May 15, 2023
e7ae90c
Fixed timezone issue in DocumentRevision, and small lint fix
snej May 15, 2023
8433ff1
Merge branch 'master' into refactor-document
snej May 15, 2023
7fe3ab1
Timezone test fix
snej May 15, 2023
2eb41b1
Fix race in TestOneShotGrant tests (#6247)
adamcfraser May 15, 2023
c24e303
Use less or equal as a speculative fix (#6246)
torcolvin May 15, 2023
4fe200c
CBG-2944: Ensure proveAttachments works for v2 attachments with a v2 …
bbrks May 16, 2023
3655a09
Make turnOffNoDelay log more info in error case (#6250)
bbrks May 17, 2023
044bf5b
CBG-2973: Fix panic for assigning to nil map inside Mutable1xBody (#6…
gregns1 May 17, 2023
c1cf34e
Fix RedactableError not satisfying the Redactor interface (#6253)
bbrks May 17, 2023
5c7673e
CBG-2998 always set no TLS bootstrap parameter to false for cbgt (#6254)
torcolvin May 18, 2023
c3146d8
CBG-2905 remove cached connections when bucket disappear (#6251)
torcolvin May 18, 2023
cb6aef7
Log around config.FromConnStr to diagnose slow DNS SRV resolution (#6…
bbrks May 19, 2023
0f6660f
Update waiting error message from generic message (#6259)
torcolvin May 23, 2023
75def59
Tweak TestIncrCounter to cover non-equal def and amt values (#6263)
bbrks May 25, 2023
b49a752
Setup manifest for 3.0.8 (#6266)
adamcfraser May 26, 2023
7093dda
CBG-2983 Close cbgt agents on database close (#6265)
adamcfraser May 26, 2023
c17d66c
CBG-3024 Make sure CE import uses checkpoints (#6261)
torcolvin May 26, 2023
b1b017e
CBG-3001 Avoid bucket retrieval error during OnFeedClose (#6269)
adamcfraser May 30, 2023
c9d4196
Merge branch 'master' into refactor-document
snej May 30, 2023
c471697
CBG-2977 allow DELETE on a broken DB config (#6260)
torcolvin May 30, 2023
c7b78bf
Make tests pass with default collections/views (#6267)
torcolvin May 31, 2023
2940cb1
CBG-3043: pick up cbgt fix for panic in import feed (#6270)
gregns1 May 31, 2023
7dff1d1
CBG-3028: fixes for failing CE tests (#6279)
gregns1 Jun 1, 2023
76f92e0
CBG-2857 Remove unambiguous timeouts from triggering cbcollections (#…
torcolvin Jun 1, 2023
b01ed47
CBG-2793: attachment compaction code erroneously sets failOnRollback …
gregns1 Jun 1, 2023
a62cd63
Make test pass if there are buckets that are non test buckets (#6285)
torcolvin Jun 5, 2023
072b06f
Merge branch 'master' into refactor-document
snej Jun 5, 2023
b14a83c
Fixed a broken test
snej Jun 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 86 additions & 5 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ type Authenticator struct {
}

type AuthenticatorOptions struct {
ClientPartitionWindow time.Duration
ChannelsWarningThreshold *uint32
SessionCookieName string
BcryptCost int
LogCtx context.Context
ClientPartitionWindow time.Duration
ChannelsWarningThreshold *uint32
ServerlessChannelThreshold uint32
SessionCookieName string
BcryptCost int
LogCtx context.Context

// Collections defines the set of collections used by the authenticator when rebuilding channels.
// Channels are only recomputed for collections included in this set.
Expand Down Expand Up @@ -196,6 +197,17 @@ func (auth *Authenticator) getPrincipal(docID string, factory func() Principal)
}
changed = true
}
// If the channel threshold has been set we need to check the inherited channels across all scopes and collections against the limit
if auth.ServerlessChannelThreshold != 0 {
channelsLength, err := auth.getInheritedChannelsLength(user)
if err != nil {
return nil, nil, false, err
}
err = auth.checkChannelLimits(channelsLength, user)
if err != nil {
return nil, nil, false, err
}
}
}

if changed {
Expand Down Expand Up @@ -223,13 +235,81 @@ func (auth *Authenticator) getPrincipal(docID string, factory func() Principal)
return princ, nil
}

// inheritedCollectionChannels returns channels for a given scope + collection
func (auth *Authenticator) inheritedCollectionChannels(user User, scope, collection string) (ch.TimedSet, error) {
roles, err := auth.getUserRoles(user)
if err != nil {
return nil, err
}

channels := user.CollectionChannels(scope, collection)
for _, role := range roles {
roleSince := user.RoleNames()[role.Name()]
channels.AddAtSequence(role.CollectionChannels(scope, collection), roleSince.Sequence)
}
return channels, nil
}

// getInheritedChannelsLength returns number of channels a user has access to across all collections
func (auth *Authenticator) getInheritedChannelsLength(user User) (int, error) {
var cumulativeChannels int
for scope, collections := range auth.Collections {
for collection := range collections {
channels, err := auth.inheritedCollectionChannels(user, scope, collection)
if err != nil {
return 0, err
}
cumulativeChannels += len(channels)
}
}
return cumulativeChannels, nil
}

// checkChannelLimits logs a warning when the warning threshold is met and will return an error when the channel limit is met
func (auth *Authenticator) checkChannelLimits(channels int, user User) error {
// Error if ServerlessChannelThreshold is set and is >= than the threshold
if uint32(channels) >= auth.ServerlessChannelThreshold {
base.ErrorfCtx(auth.LogCtx, "User ID: %v channel count: %d exceeds %d for channels per user threshold. Auth will be rejected until rectified",
base.UD(user.Name()), channels, auth.ServerlessChannelThreshold)
return base.ErrMaximumChannelsForUserExceeded
}

// This function is likely to be called once per session when a channel limit is applied, the sync once
// applied here ensures we don't fill logs with warnings about being over warning threshold. We may want
// to revisit this implementation around the warning threshold in future
user.GetWarnChanSync().Do(func() {
if channelsPerUserThreshold := auth.ChannelsWarningThreshold; channelsPerUserThreshold != nil {
if uint32(channels) >= *channelsPerUserThreshold {
base.WarnfCtx(auth.LogCtx, "User ID: %v channel count: %d exceeds %d for channels per user warning threshold",
base.UD(user.Name()), channels, *channelsPerUserThreshold)
}
}
})
return nil
}

// getUserRoles gets all roles a user has been granted
func (auth *Authenticator) getUserRoles(user User) ([]Role, error) {
roles := make([]Role, 0, len(user.RoleNames()))
for name := range user.RoleNames() {
role, err := auth.GetRole(name)
if err != nil {
return nil, err
} else if role != nil {
roles = append(roles, role)
}
}
return roles, nil
}

// Rebuild channels computes the full set of channels for all collections defined for the authenticator.
// For each collection in Authenticator.collections:
// - if there is no CollectionAccess on the principal for the collection, rebuilds channels for that collection
// - If CollectionAccess on the principal has been invalidated, rebuilds channels for that collection
func (auth *Authenticator) rebuildChannels(princ Principal) (changed bool, err error) {

changed = false

for scope, collections := range auth.Collections {
for collection, _ := range collections {
// If collection channels are nil, they have been invalidated and must be rebuilt
Expand All @@ -242,6 +322,7 @@ func (auth *Authenticator) rebuildChannels(princ Principal) (changed bool, err e
}
}
}

return changed, nil
}

Expand Down
111 changes: 111 additions & 0 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2752,6 +2752,117 @@ func TestObtainChannelsForDeletedRole(t *testing.T) {
}
}

func TestServerlessChannelLimitsRoles(t *testing.T) {
testCases := []struct {
Name string
Collection bool
}{
{
Name: "Single role",
},
{
Name: "Muliple roles",
},
}
for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
testBucket := base.GetTestBucket(t)
defer testBucket.Close()
dataStore := testBucket.GetSingleDataStore()
var role2 Role

opts := DefaultAuthenticatorOptions()
opts.ServerlessChannelThreshold = 5
opts.Collections = map[string]map[string]struct{}{
"scope1": {"collection1": struct{}{}, "collection2": struct{}{}},
}
auth := NewAuthenticator(dataStore, nil, opts)
user1, err := auth.NewUser("user1", "pass", ch.BaseSetOf(t, "ABC"))
require.NoError(t, err)
err = auth.Save(user1)
require.NoError(t, err)
_, err = auth.AuthenticateUser("user1", "pass")
require.NoError(t, err)

role1, err := auth.NewRole("role1", nil)
require.NoError(t, err)
if testCase.Name == "Single role" {
user1.SetExplicitRoles(ch.TimedSet{"role1": ch.NewVbSimpleSequence(1)}, 1)
require.NoError(t, auth.Save(user1))
_, err = auth.AuthenticateUser("user1", "pass")
require.NoError(t, err)

role1.SetCollectionExplicitChannels("scope1", "collection1", ch.AtSequence(ch.BaseSetOf(t, "ABC", "DEF", "GHI", "JKL"), 1), 1)
require.NoError(t, auth.Save(role1))
} else {
role2, err = auth.NewRole("role2", nil)
require.NoError(t, err)
user1.SetExplicitRoles(ch.TimedSet{"role1": ch.NewVbSimpleSequence(1), "role2": ch.NewVbSimpleSequence(1)}, 1)
require.NoError(t, auth.Save(user1))
role1.SetCollectionExplicitChannels("scope1", "collection1", ch.AtSequence(ch.BaseSetOf(t, "ABC", "DEF", "GHI", "JKL"), 1), 1)
role2.SetCollectionExplicitChannels("scope1", "collection2", ch.AtSequence(ch.BaseSetOf(t, "MNO", "PQR"), 1), 1)
require.NoError(t, auth.Save(role1))
require.NoError(t, auth.Save(role2))
}
_, err = auth.AuthenticateUser("user1", "pass")
require.Error(t, err)
})
}
}

func TestServerlessChannelLimits(t *testing.T) {

testCases := []struct {
Name string
Collection bool
}{
{
Name: "Collection not enabled",
Collection: false,
},
{
Name: "Collection is enabled",
Collection: true,
},
}
for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
testBucket := base.GetTestBucket(t)
defer testBucket.Close()
dataStore := testBucket.GetSingleDataStore()

opts := DefaultAuthenticatorOptions()
opts.ServerlessChannelThreshold = 5
if testCase.Collection {
opts.Collections = map[string]map[string]struct{}{
"scope1": {"collection1": struct{}{}, "collection2": struct{}{}},
}
}
auth := NewAuthenticator(dataStore, nil, opts)
user1, err := auth.NewUser("user1", "pass", ch.BaseSetOf(t, "ABC"))
require.NoError(t, err)
err = auth.Save(user1)
require.NoError(t, err)
_, err = auth.AuthenticateUser("user1", "pass")
require.NoError(t, err)

if !testCase.Collection {
user1.SetCollectionExplicitChannels("_default", "_default", ch.AtSequence(ch.BaseSetOf(t, "ABC", "DEF", "GHI", "JKL", "MNO", "PQR"), 1), 1)
err = auth.Save(user1)
require.NoError(t, err)
} else {
user1.SetCollectionExplicitChannels("scope1", "collection1", ch.AtSequence(ch.BaseSetOf(t, "ABC", "DEF", "GHI", "JKL"), 1), 1)
user1.SetCollectionExplicitChannels("scope1", "collection2", ch.AtSequence(ch.BaseSetOf(t, "MNO", "PQR"), 1), 1)
err = auth.Save(user1)
require.NoError(t, err)
}
_, err = auth.AuthenticateUser("user1", "pass")
require.Error(t, err)
assert.Contains(t, err.Error(), base.ErrMaximumChannelsForUserExceeded.Error())
})
}
}

func TestInvalidateRoles(t *testing.T) {
testBucket := base.GetTestBucket(t)
defer testBucket.Close()
Expand Down
3 changes: 3 additions & 0 deletions auth/principal.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package auth

import (
"sync"
"time"

"github.com/couchbase/sync_gateway/base"
Expand Down Expand Up @@ -125,6 +126,8 @@ type User interface {

InitializeRoles()

GetWarnChanSync() *sync.Once

revokedChannels(since uint64, lowSeq uint64, triggeredBy uint64) RevokedChannels

// Obtains the period over which the user had access to the given channel. Either directly or via a role.
Expand Down
4 changes: 4 additions & 0 deletions auth/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ func (user *userImpl) SetEmail(email string) error {
return nil
}

func (user *userImpl) GetWarnChanSync() *sync.Once {
return &user.warnChanThresholdOnce
}

func (user *userImpl) RoleNames() ch.TimedSet {
if user.RoleInvalSeq != 0 {
return nil
Expand Down
Loading