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

Use V8 instead of Otto for JavaScript #5980

Open
wants to merge 147 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 141 commits
Commits
Show all changes
147 commits
Select commit Hold shift + click to select a range
5dfc11b
Configurable limits on function/graphQL count, size, etc.
snej Sep 26, 2022
68a66e5
(fix REST tests after FunctionsConfig struct change)
snej Oct 14, 2022
74f73a3
Change JS user.save function params to (body, docID?)
snej Oct 7, 2022
273c710
JS delete fn takes either docID or body as param
snej Oct 7, 2022
0cc7d0e
Functions: Ensure only SELECT queries can be used
snej Oct 10, 2022
b094458
Use conventional order of GraphQL resolver parameters
snej Oct 10, 2022
65bb5a8
Updated function 'allow' pattern syntax
snej Oct 13, 2022
504cd0e
Implemented new mutability rules for functions/GraphQL
snej Oct 14, 2022
3548956
GraphQL top-level resolvers should not be allow-by-default
snej Oct 17, 2022
dfcedb9
WIP: V8
snej Oct 11, 2022
94590af
WIP
snej Oct 13, 2022
b6597f4
Added TS fn+graphql code as db/functions/engine
snej Oct 18, 2022
19e71b6
Can call a function with V8!
snej Oct 18, 2022
59de802
Reimplementing functions package using V8
snej Oct 21, 2022
4e3cd5b
WIP Many unit tests passing
snej Oct 21, 2022
adcaf3d
OMG all db/functions unit tests pass
snej Oct 25, 2022
97f85f6
Cleanup: comments and making evaluator non-public
snej Oct 26, 2022
b080edd
Made V8-based code thread-safe
snej Oct 26, 2022
30aae2d
Added Apollo subgraph support. Fixed typename resolvers.
snej Oct 26, 2022
490a324
TypeScript cleanup and improved README
snej Nov 15, 2022
ceed13c
Refactored V8 code out of `functions`package
snej Nov 17, 2022
0415be0
Moved fns/GraphQL objects to Database
snej Nov 18, 2022
2daa450
Refactored js API
snej Nov 21, 2022
9e88cac
more refactoring
snej Nov 21, 2022
9859c63
Reimplemented channels.ChannelMapper using js package & V8
snej Nov 23, 2022
1b364c9
WIP: Nearly all SG unit tests pass
snej Nov 24, 2022
2aa5273
Limit number of simultaneous VMs a VMPool will create
snej Nov 28, 2022
20ac4dc
Fixed: JS recursion-depth limit false positives
snej Nov 29, 2022
f646fee
Implemented timeouts for V8-based functions
snej Nov 29, 2022
05ba095
js.VMPool: stop using sync.Pool
snej Nov 29, 2022
998e051
Fixed one ChannelMapper bug, and all SG tests pass!
snej Nov 29, 2022
b7e3de3
Converted import filter fns from Otto to V8
snej Nov 30, 2022
5d9a976
js package API changes & docs.
snej Dec 2, 2022
efcad04
More js cleanup. Restored rest.TestDocumentNumbers
snej Dec 2, 2022
fa82cb1
Converted db Events API to use V8
snej Dec 3, 2022
7933053
Removed last vestiges of otto
snej Dec 3, 2022
16b7ad3
Merge branch 'master' into feature/v8+subgraph
snej Dec 5, 2022
b8ea97f
Updated db.functions to use the default collection.
snej Dec 6, 2022
f9f97e7
More unit tests for `js` package
snej Dec 6, 2022
2eddd69
Merge branch 'master' into feature/v8+subgraph
snej Dec 7, 2022
c4c4bc6
Converted the ChannelMapper callbacks to JS fns
snej Dec 8, 2022
6c729bd
Prevent sync fn from calling JS `eval()` or `Function()`
snej Dec 9, 2022
9dc9b3d
Functions/GraphQL: Collections support
snej Dec 9, 2022
e0ce752
Fixed two rest unit tests that broke with V8
snej Dec 9, 2022
97ab8ab
Fixed import-filter function setup
snej Dec 12, 2022
ef6c4be
Fixed TestFunctionsConfigMVCC
snej Dec 15, 2022
aaf1679
Fixed TestRevocationWithUserXattrs
snej Dec 15, 2022
21397ae
build on feature
torcolvin Dec 19, 2022
070bf7b
no dash
torcolvin Dec 19, 2022
fa31840
Fixed: GraphQL couldn't read schema from file (oops)
snej Dec 20, 2022
be4e646
Improved doc-comments; some cleanup
snej Jan 3, 2023
3265285
Improved VMPool
snej Jan 3, 2023
0b1b0a2
Optimizations & improvements for my latest v8go changes
snej Jan 10, 2023
308bb57
Fixed VM panic & VMPool inaccurate logging logging
snej Jan 16, 2023
2854e0b
Use new v8go.Array in evaluator
snej Jan 16, 2023
cb37ea5
js: Added string based benchmark
snej Jan 16, 2023
a1d268d
Experimental: Avoid marshaling body calling sync fn
snej Dec 8, 2022
828655b
Make config JS func validation use V8 (it was still using Otto!)
snej Jan 17, 2023
37a8648
Avoid more JSON marshaling calling sync fn
snej Jan 17, 2023
a286e7c
oops, fixed mistake in JS fn validation
snej Jan 18, 2023
a323055
Merge branch 'master' into feature/v8+subgraph
snej Jan 19, 2023
2d465cf
Merge branch 'master' into feature/v8+subgraph
snej Jan 20, 2023
47566e6
js package can now support engines other than V8
snej Jan 24, 2023
f8da7bf
js package supports both V8 and Otto!
snej Jan 25, 2023
411a652
channels package works with both Otto & V8
snej Jan 26, 2023
93bfa98
Import and conflict-resolver fns work with Otto & V8
snej Jan 27, 2023
eff0e1f
JS-based functionality now tested with both VM types
snej Jan 30, 2023
c26af94
Refactored js a bit, turning VMType into Engine
snej Jan 30, 2023
7483393
Merge branch 'master' into feature/v8+subgraph
snej Jan 30, 2023
80cfe11
functions package improvements & fixes
snej Feb 2, 2023
f772841
Got rest/functionsapitest tests working
snej Feb 2, 2023
06deebd
Removed an obsolete temporary comment leftover from the prev commit
snej Feb 2, 2023
cf5b4c8
Small cleanup/commenting of js.Runner interface
snej Feb 6, 2023
e22145b
Make JS engine selectable via database config
snej Feb 6, 2023
335db67
More work on configurable JS engine
snej Feb 7, 2023
d8a271d
Force use of V8 when testing GraphQL/Functions
snej Feb 8, 2023
2333c81
Changed expected result of a Functions test of an edge case
snej Feb 8, 2023
905e19e
Fixed a nasty concurrency bug in functions pkg
snej Feb 10, 2023
31bf1ff
Changed default JS engine to V8
snej Feb 10, 2023
7a39c8d
Merge branch 'master' into feature/v8+subgraph
snej Feb 10, 2023
c38c814
Changed v8go package ID in imports
snej Feb 11, 2023
535e1aa
Updated go.mod and go.sum
snej Feb 11, 2023
3eb3399
Fixed CI lint errors
snej Feb 11, 2023
58d6f51
Made test rest.TestFunctionsConcurrently less failure-prone
snej Feb 11, 2023
7a1d7a9
Some more test tweaks to avoid CI failure
snej Feb 13, 2023
3d993fa
Some source cleanup for PR
snej Feb 13, 2023
cfe52af
Cleaned up how doc body is passed to sync fn
snej Feb 14, 2023
3bab1f9
Fixed TestImportInternalPropertiesHandling/_purged
snej Feb 15, 2023
e834a84
Added V8 heap and stack size limits (bumped v8go to 1.7.3)
snej Feb 16, 2023
aab76e2
Merge branch 'master' into feature/v8+subgraph
snej Feb 17, 2023
54c722f
Merge branch 'master' into feature/v8+subgraph
snej Feb 17, 2023
8e1bfe4
Removed some unnecessary code I added in indextest_test.go
snej Feb 17, 2023
2c9b18a
Updated go.sum
snej Feb 18, 2023
0499145
Added license header to files
snej Feb 18, 2023
005f0fd
Prevent js.TestPoolsConcurrently from failing in CI
snej Feb 18, 2023
e2c0b60
More JS concurrent-test timeout fixes
snej Feb 20, 2023
8b6c8d5
Prepend license comment to db/functions' main.js
snej Feb 21, 2023
720b08b
Github CI: Ignore js/underscore.js files in addlicense check
snej Feb 21, 2023
422279c
Removed now-unused Document.GetDeepMutableBody()
snej Mar 1, 2023
1ce44ef
Merge branch 'master' into feature/v8+subgraph
snej Apr 19, 2023
1244349
Feature/v8 build tag (#6201)
torcolvin Apr 24, 2023
4986b44
Allow v8 build in jenkins (#6224)
torcolvin May 1, 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
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
6c30b46
Merge branch 'master' into feature/v8+subgraph
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
6302dc5
Updated v8go: fixed a type mismatch on Windows
snej 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
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
153a365
Comment out windows v8 tests
torcolvin Jun 2, 2023
05282f6
Merge branch 'master' into feature/v8+subgraph
snej Jun 2, 2023
1de4377
make tags pass without v8
torcolvin Jun 2, 2023
d9a7b84
Add v8 tests to jenkins (#6288)
torcolvin Jun 5, 2023
10a44bf
Moved the 'js' module to the sg-bucket repo
snej Jun 5, 2023
c60ed61
Changes from Tor's review comments
snej Jun 7, 2023
0891f2e
Deep-copy doc bodies passed to Otto conflict handlers
snej Jun 7, 2023
4c6436b
Updated sg-bucket version in go.mod
snej Jun 20, 2023
b4b458a
Adapt to Context-related changes in `js` module
snej Jun 21, 2023
8249dc7
Updated functions/evaluator.go due to changes in JS module API
snej Jun 21, 2023
b759efd
v8go package moved to couchbasedeps
snej Jul 11, 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
37 changes: 36 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ on:
- 'release/*'
- 'CBG*'
- 'ci-*'
- 'feature*'
pull_request:
branches:
- 'master'
- 'release/*'
- 'feature*'

jobs:
build:
Expand All @@ -40,7 +42,7 @@ jobs:
go-version: 1.20.3
- run: go install github.com/google/addlicense@latest
- uses: actions/checkout@v3
- run: addlicense -check -f licenses/addlicense.tmpl .
- run: addlicense -check -f licenses/addlicense.tmpl -ignore 'js/underscore*.js' .

golangci:
name: lint
Expand Down Expand Up @@ -86,6 +88,39 @@ jobs:
uses: guyarb/golang-test-annotations@v0.6.0
with:
test-results: test.json
test-v8:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
name: macos
# v8 windows does not work yet CBG-2741
#- os: windows-latest
#name: windows
- os: ubuntu-latest
name: ubuntu
env:
GOPRIVATE: github.com/couchbaselabs
MallocNanoZone: 0
name: test v8 (${{ matrix.name }})
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.20.3
- uses: actions/checkout@v3
- name: Build
run: go build -tags cb_sg_v8 -v "./..."
- name: Run Tests
run: go test -tags cb_sg_v8 -timeout=30m -count=1 -json -v "./..." | tee test.json | jq -s -jr 'sort_by(.Package,.Time) | .[].Output | select (. != null )'
shell: bash
- name: Annotate Failures
if: always()
uses: guyarb/golang-test-annotations@v0.6.0
with:
test-results: test.json


test-race:
runs-on: ubuntu-latest
Expand Down
56 changes: 56 additions & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pipeline {
BRANCH = "${BRANCH_NAME}"
COVERALLS_TOKEN = credentials('SG_COVERALLS_TOKEN')
EE_BUILD_TAG = "cb_sg_enterprise"
V8_BUILD_TAG = "cb_sg_v8"
SGW_REPO = "github.com/couchbase/sync_gateway"
GH_ACCESS_TOKEN_CREDENTIAL = "github_cb-robot-sg_access_token"
GO111MODULE = "on"
Expand Down Expand Up @@ -80,11 +81,21 @@ pipeline {
sh "GOOS=linux go build -o sync_gateway_ce-linux -v ${SGW_REPO}"
}
}
stage('CE Linux V8') {
steps {
sh "GOOS=linux go build -tags ${V8_BUILD_TAG} -o sync_gateway_ce-linux -v ${SGW_REPO}"
}
}
stage('EE Linux') {
steps {
sh "GOOS=linux go build -o sync_gateway_ee-linux -tags ${EE_BUILD_TAG} -v ${SGW_REPO}"
}
}
stage('EE Linux V8') {
steps {
sh "GOOS=linux go build -o sync_gateway_ee-linux -tags ${EE_BUILD_TAG},${V8_BUILD_TAG} -v ${SGW_REPO}"
}
}
stage('CE macOS') {
// TODO: Remove skip
when { expression { return false } }
Expand Down Expand Up @@ -291,6 +302,51 @@ pipeline {
}
}
}
stage('EE V8') {
steps {
withEnv(["PATH+GO=${env.GOTOOLS}/bin"]) {
githubNotify(credentialsId: "${GH_ACCESS_TOKEN_CREDENTIAL}", context: 'sgw-pipeline-ee-v8-unit-tests', description: 'EE V8 Unit Tests Running', status: 'PENDING')

// Build EE coverprofiles
sh "2>&1 go test -timeout=20m -tags ${EE_BUILD_TAG},${V8_BUILD_TAG} -coverpkg=./... -coverprofile=cover_ee_v8.out -race -count=1 -v ./... > verbose_ee_v8.out.raw || true"

sh 'go tool cover -func=cover_ee_v8.out | awk \'END{print "Total SG EE V8 Coverage: " $3}\''

sh 'mkdir -p reports'

// strip non-printable characters from the raw verbose test output
sh 'LC_CTYPE=C tr -dc [:print:][:space:] < verbose_ee_v8.out.raw > verbose_ee_v8.out'

// Generate Cobertura XML report that can be parsed by the Jenkins Cobertura Plugin
sh 'gocov convert cover_ee_v8.out | gocov-xml > reports/coverage-ee_v8.xml'

// Grab test fail/total counts so we can print them later
sh "grep '\\-\\-\\- PASS: ' verbose_ee_v8.out | wc -l | awk '{printf \$1}' > test-ee-v8-pass.count"
sh "grep '\\-\\-\\- FAIL: ' verbose_ee_v8.out | wc -l | awk '{printf \$1}' > test-ee-v8-fail.count"
sh "grep '\\-\\-\\- SKIP: ' verbose_ee_v8.out | wc -l | awk '{printf \$1}' > test-ee-v8-skip.count"
sh "grep '=== RUN' verbose_ee_v8.out | wc -l | awk '{printf \$1}' > test-ee-v8-total.count"
script {
env.TEST_EE_PASS = readFile 'test-ee-v8-pass.count'
env.TEST_EE_FAIL = readFile 'test-ee-v8-fail.count'
env.TEST_EE_SKIP = readFile 'test-ee-v8-skip.count'
env.TEST_EE_TOTAL = readFile 'test-ee-v8-total.count'
}

// Generate junit-formatted test report
script {
try {
sh 'go2xunit -fail -suite-name-prefix="EE-V8-" -input verbose_ee_v8.out -output reports/test-ee-v8.xml'
githubNotify(credentialsId: "${GH_ACCESS_TOKEN_CREDENTIAL}", context: 'sgw-pipeline-ee-v8-unit-tests', description: env.TEST_EE_V8_PASS+'/'+env.TEST_EE_V8_TOTAL+' passed ('+env.TEST_EE_V8_SKIP+' skipped)', status: 'SUCCESS')
} catch (Exception e) {
githubNotify(credentialsId: "${GH_ACCESS_TOKEN_CREDENTIAL}", context: 'sgw-pipeline-ee-unit-tests', description: env.TEST_EE_V8_FAIL+'/'+env.TEST_EE_V8_TOTAL+' failed ('+env.TEST_EE_V8_SKIP+' skipped)', status: 'FAILURE')
// archive verbose test logs in the event of a test failure
archiveArtifacts artifacts: 'verbose_ee_v8.out', fingerprint: false
unstable("At least one EE V8 unit test failed")
}
}
}
}
}
}
}

Expand Down
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
Loading