-
Notifications
You must be signed in to change notification settings - Fork 138
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
CBG-4025 Invalidate user roles after resync #6942
Changes from 1 commit
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 |
---|---|---|
|
@@ -441,11 +441,11 @@ func (auth *Authenticator) UpdateSequenceNumber(p Principal, seq uint64) error { | |
} | ||
|
||
func (auth *Authenticator) InvalidateDefaultChannels(name string, isUser bool, invalSeq uint64) error { | ||
return auth.InvalidateChannels(name, isUser, base.DefaultScope, base.DefaultCollection, invalSeq) | ||
return auth.InvalidateChannels(name, isUser, base.ScopeAndCollectionNames{base.DefaultScopeAndCollectionName()}, invalSeq) | ||
} | ||
|
||
// Invalidates the channel list of a user/role by setting the ChannelInvalSeq to a non-zero value | ||
func (auth *Authenticator) InvalidateChannels(name string, isUser bool, scope string, collection string, invalSeq uint64) error { | ||
func (auth *Authenticator) InvalidateChannels(name string, isUser bool, collections base.ScopeAndCollectionNames, invalSeq uint64) error { | ||
var princ Principal | ||
var docID string | ||
|
||
|
@@ -465,17 +465,23 @@ func (auth *Authenticator) InvalidateChannels(name string, isUser bool, scope st | |
|
||
base.InfofCtx(auth.LogCtx, base.KeyAccess, "Invalidate access of %q", base.UD(name)) | ||
|
||
subdocPath := "channel_inval_seq" | ||
if scope != base.DefaultScope || collection != base.DefaultCollection { | ||
subdocPath = "collection_access." + scope + "." + collection + "." + subdocPath | ||
} | ||
// Attempt to use subdoc if we're only modifying a single collection | ||
if len(collections) == 1 { | ||
scope := collections[0].ScopeName() | ||
collection := collections[0].CollectionName() | ||
|
||
if subdocStore, ok := base.AsSubdocStore(auth.datastore); ok { | ||
err := subdocStore.SubdocInsert(auth.LogCtx, docID, subdocPath, 0, invalSeq) | ||
if err != nil && err != base.ErrAlreadyExists && err != base.ErrPathExists && err != base.ErrPathNotFound && !base.IsDocNotFoundError(err) { | ||
return err | ||
subdocPath := "channel_inval_seq" | ||
if scope != base.DefaultScope || collection != base.DefaultCollection { | ||
subdocPath = "collection_access." + scope + "." + collection + "." + subdocPath | ||
} | ||
|
||
if subdocStore, ok := base.AsSubdocStore(auth.datastore); ok { | ||
err := subdocStore.SubdocInsert(auth.LogCtx, docID, subdocPath, 0, invalSeq) | ||
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.
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. AsSubdocStore is also doing a check on ds.IsSupported(sgbucket.BucketStoreFeatureSubdocOperations) (which I believe is still relevant for walrus support for backports to 3.1 that support walrus). I think I'd prefer to leave this code as it was in this PR since we expect a backport. |
||
if err != nil && err != base.ErrAlreadyExists && err != base.ErrPathExists && err != base.ErrPathNotFound && !base.IsDocNotFoundError(err) { | ||
return err | ||
} | ||
return nil | ||
} | ||
return nil | ||
} | ||
|
||
_, err := auth.datastore.Update(docID, 0, func(current []byte) (updated []byte, expiry *uint32, delete bool, err error) { | ||
|
@@ -489,14 +495,21 @@ func (auth *Authenticator) InvalidateChannels(name string, isUser bool, scope st | |
return nil, nil, false, err | ||
} | ||
|
||
if princ.CollectionChannels(scope, collection) == nil { | ||
return nil, nil, false, base.ErrUpdateCancel | ||
changed := false | ||
for _, collectionName := range collections { | ||
scope := collectionName.ScopeName() | ||
collection := collectionName.CollectionName() | ||
if princ.CollectionChannels(scope, collection) != nil { | ||
princ.setCollectionChannelInvalSeq(scope, collection, invalSeq) | ||
changed = true | ||
} | ||
} | ||
|
||
princ.setCollectionChannelInvalSeq(scope, collection, invalSeq) | ||
if !changed { | ||
return nil, nil, false, base.ErrUpdateCancel | ||
} | ||
|
||
updated, err = base.JSONMarshal(princ) | ||
|
||
return updated, nil, false, err | ||
}) | ||
|
||
|
@@ -550,6 +563,56 @@ func (auth *Authenticator) InvalidateRoles(username string, invalSeq uint64) err | |
return err | ||
} | ||
|
||
// Invalidates the computed roles and channels of a user by setting the ChannelInvalSeq to a non-zero value for all specified collections. | ||
func (auth *Authenticator) InvalidateRolesAndChannels(username string, collections base.ScopeAndCollectionNames, invalSeq uint64) error { | ||
|
||
docID := auth.DocIDForUser(username) | ||
base.InfofCtx(auth.LogCtx, base.KeyAccess, "Invalidate computed role and channel access of %q for collections %v", base.UD(username), collections) | ||
|
||
_, err := auth.datastore.Update(docID, 0, func(current []byte) (updated []byte, expiry *uint32, delete bool, err error) { | ||
// If user/role doesn't exist cancel update | ||
if current == nil { | ||
return nil, nil, false, base.ErrUpdateCancel | ||
} | ||
|
||
var user userImpl | ||
err = base.JSONUnmarshal(current, &user) | ||
if err != nil { | ||
return nil, nil, false, base.ErrUpdateCancel | ||
} | ||
|
||
changed := false | ||
// Invalidate channel access per collection | ||
for _, collection := range collections { | ||
scope := collection.ScopeName() | ||
collection := collection.CollectionName() | ||
if user.CollectionChannels(scope, collection) != nil { | ||
user.setCollectionChannelInvalSeq(scope, collection, invalSeq) | ||
changed = true | ||
} | ||
} | ||
|
||
// Invalidate role access | ||
if user.RoleNames() != nil { | ||
user.SetRoleInvalSeq(invalSeq) | ||
changed = true | ||
} | ||
|
||
if !changed { | ||
return nil, nil, false, base.ErrUpdateCancel | ||
} | ||
|
||
updated, err = base.JSONMarshal(&user) | ||
return updated, nil, false, err | ||
}) | ||
|
||
if err == base.ErrUpdateCancel { | ||
return nil | ||
} | ||
|
||
return err | ||
} | ||
|
||
// Updates user email and writes user doc | ||
func (auth *Authenticator) UpdateUserEmail(u User, email string) error { | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more of an optimization than might be necessary, but can we change the API of SubdocInsert to be map[string]any so we can do multiple subdoc operations at once?
Later we are going to run through the logic of updating the document, though not the write operation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered adding that functionality in this PR, but decided against it due to the increase in scope/risk. Since the more common usage of InvalidateChannels (at write time) is always single collection, I thought it was reasonable to omit.