From 0a59ae619dd960d50acc0484e090767774b54c79 Mon Sep 17 00:00:00 2001 From: Sam Zhang Date: Tue, 14 Dec 2021 10:52:15 -0500 Subject: [PATCH 01/10] rebase to main with NODE-2899 changes --- src/cmap/auth/mongo_credentials.ts | 8 +- src/cmap/auth/providers.ts | 7 ++ src/connection_string.ts | 9 ++- test/unit/connection_string.test.js | 10 +++ test/unit/srv_option_handling.test.ts | 105 ++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 test/unit/srv_option_handling.test.ts diff --git a/src/cmap/auth/mongo_credentials.ts b/src/cmap/auth/mongo_credentials.ts index b11f6fa4b5..e919013324 100644 --- a/src/cmap/auth/mongo_credentials.ts +++ b/src/cmap/auth/mongo_credentials.ts @@ -2,7 +2,7 @@ import type { Document } from '../../bson'; import { MongoAPIError, MongoMissingCredentialsError } from '../../error'; -import { AuthMechanism } from './providers'; +import { AuthMechanism, $EXTERNAL_AUTH_SOURCE_MECHANISMS } from './providers'; // https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst function getDefaultAuthMechanism(ismaster?: Document): AuthMechanism { @@ -136,11 +136,7 @@ export class MongoCredentials { throw new MongoMissingCredentialsError(`Username required for mechanism '${this.mechanism}'`); } - if ( - this.mechanism === AuthMechanism.MONGODB_GSSAPI || - this.mechanism === AuthMechanism.MONGODB_AWS || - this.mechanism === AuthMechanism.MONGODB_X509 - ) { + if ($EXTERNAL_AUTH_SOURCE_MECHANISMS.has(this.mechanism)) { if (this.source != null && this.source !== '$external') { // TODO(NODE-3485): Replace this with a MongoAuthValidationError throw new MongoAPIError( diff --git a/src/cmap/auth/providers.ts b/src/cmap/auth/providers.ts index 07436baff7..a7ac6ded35 100644 --- a/src/cmap/auth/providers.ts +++ b/src/cmap/auth/providers.ts @@ -10,5 +10,12 @@ export const AuthMechanism = Object.freeze({ MONGODB_X509: 'MONGODB-X509' } as const); +/** @public */ +export const $EXTERNAL_AUTH_SOURCE_MECHANISMS = new Set([ + AuthMechanism.MONGODB_GSSAPI, + AuthMechanism.MONGODB_AWS, + AuthMechanism.MONGODB_X509 +]); + /** @public */ export type AuthMechanism = typeof AuthMechanism[keyof typeof AuthMechanism]; diff --git a/src/connection_string.ts b/src/connection_string.ts index a2f3764364..021c43d257 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -2,10 +2,9 @@ import * as dns from 'dns'; import * as fs from 'fs'; import ConnectionString from 'mongodb-connection-string-url'; import { URLSearchParams } from 'url'; - import type { Document } from './bson'; import { MongoCredentials } from './cmap/auth/mongo_credentials'; -import { AuthMechanism } from './cmap/auth/providers'; +import { AuthMechanism, $EXTERNAL_AUTH_SOURCE_MECHANISMS } from './cmap/auth/providers'; import { Compressor, CompressorName } from './cmap/wire_protocol/compression'; import { Encrypter } from './encrypter'; import { MongoAPIError, MongoInvalidArgumentError, MongoParseError } from './error'; @@ -125,7 +124,11 @@ export function resolveSRVRecord(options: MongoOptions, callback: Callback { + let resolveSrvStub: sinon.SinonStub; + let resolveTxtStub: sinon.SinonStub; + let resolveSRVRecordStub: sinon.SinonStub; + let lookupStub: sinon.SinonStub; + + afterEach(async () => { + if (resolveSrvStub) { + resolveSrvStub.restore(); + resolveSrvStub = undefined; + } + if (resolveTxtStub) { + resolveTxtStub.restore(); + resolveTxtStub = undefined; + } + if (lookupStub) { + lookupStub.restore(); + lookupStub = undefined; + } + if (resolveSRVRecordStub) { + resolveSRVRecordStub.restore(); + resolveSRVRecordStub = undefined; + } + }); + + function makeStub(txtRecord: string) { + const mockAddress = [ + { + name: 'localhost.test.mock.test.build.10gen.cc', + port: 2017, + weight: 0, + priority: 0 + } + ]; + + const mockRecord: string[][] = [[txtRecord]]; + + // first call is for the driver initial connection + // second call will check the poller + resolveSrvStub = sinon.stub(dns, 'resolveSrv').callsFake((address, callback) => { + return process.nextTick(callback, null, mockAddress); + }); + + resolveTxtStub = sinon.stub(dns, 'resolveTxt').callsFake((address, thisIsWhatWeAreTesting) => { + thisIsWhatWeAreTesting(null, mockRecord); + }); + } + + for (const iterator of [ + { + mechanism: AuthMechanism.MONGODB_AWS, + source: '', + userSpecifiedAuthSource: false, + expected: 'succeed' + }, + { + mechanism: AuthMechanism.MONGODB_AWS, + source: 'admin', + userSpecifiedAuthSource: true, + expected: 'succeed' + }, + { + mechanism: null, + source: 'admin', + userSpecifiedAuthSource: false, + expected: 'fail' + } + ]) { + it(`should ${iterator.expected} for ${iterator.mechanism} mechanism and ${ + iterator.userSpecifiedAuthSource ? '' : 'non-' + }user-specified source: ${iterator.source}`, function () { + makeStub('authSource=admin'); + + const options = { + credentials: new MongoCredentials({ + source: '$external', + mechanism: iterator.mechanism, + username: 'username', + password: 'password', + mechanismProperties: {} + }), + srvHost: 'host', + srvServiceName: 'mongodb', + userSpecifiedAuthSource: iterator.userSpecifiedAuthSource + }; + + resolveSRVRecord(options as any, (err, hostAddress) => { + if (iterator.expected === 'succeed') { + expect(options).to.have.nested.property('credentials.source', '$external'); + } else { + expect(options).to.not.have.nested.property('credentials.source', '$external'); + } + }); + + // expect(client).to.have.nested.property('options.credentials.source', '$external'); + }); + } +}); From b7e2a181700d39bd87b94b5f5616d84fda319e65 Mon Sep 17 00:00:00 2001 From: Sam Zhang Date: Mon, 13 Dec 2021 10:50:58 -0500 Subject: [PATCH 02/10] remove comment --- test/unit/connection_string.test.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/unit/connection_string.test.js b/test/unit/connection_string.test.js index ba91d58a3a..0a3d77a329 100644 --- a/test/unit/connection_string.test.js +++ b/test/unit/connection_string.test.js @@ -95,9 +95,6 @@ describe('Connection String', function () { }); it('should provide default authSource when valid AuthMechanism provided', function () { - // const options = parseOptions( - // 'mongodb+srv://jira-sync.pw0q4.mongodb.net/testDB?authMechanism=MONGODB-AWS&retryWrites=true&w=majority' - // ); const options = parseOptions( 'mongodb+srv://jira-sync.pw0q4.mongodb.net/testDB?authMechanism=MONGODB-AWS&retryWrites=true&w=majority' ); From 19c787b6a04b681b95482e1390bcb412b19c8b99 Mon Sep 17 00:00:00 2001 From: Sam Zhang Date: Mon, 13 Dec 2021 17:40:01 -0500 Subject: [PATCH 03/10] iadd unit tests --- src/connection_string.ts | 4 +- test/unit/srv_option_handling.test.ts | 93 ++++++++++++++++----------- 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index 021c43d257..e170a42f7b 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -573,9 +573,7 @@ export const OPTIONS = { let source = options.credentials?.source; if ( mechanism === AuthMechanism.MONGODB_PLAIN || - mechanism === AuthMechanism.MONGODB_GSSAPI || - mechanism === AuthMechanism.MONGODB_AWS || - mechanism === AuthMechanism.MONGODB_X509 + $EXTERNAL_AUTH_SOURCE_MECHANISMS.has(mechanism) ) { // some mechanisms have '$external' as the Auth Source source = '$external'; diff --git a/test/unit/srv_option_handling.test.ts b/test/unit/srv_option_handling.test.ts index 841e1e4a33..a425546e3c 100644 --- a/test/unit/srv_option_handling.test.ts +++ b/test/unit/srv_option_handling.test.ts @@ -1,9 +1,15 @@ import * as dns from 'dns'; import * as sinon from 'sinon'; import { expect } from 'chai'; +import { promisify } from 'util'; import { MongoCredentials } from '../../src/cmap/auth/mongo_credentials'; import { resolveSRVRecord } from '../../src/connection_string'; -import { AuthMechanism } from '../../src/cmap/auth/defaultAuthProviders'; +import { + AuthMechanism, + $EXTERNAL_AUTH_SOURCE_MECHANISMS +} from '../../src/cmap/auth/defaultAuthProviders'; + +const resolveSRVRecordAsync = promisify(resolveSRVRecord); describe('Srv Option Handling', () => { let resolveSrvStub: sinon.SinonStub; @@ -53,53 +59,68 @@ describe('Srv Option Handling', () => { }); } + for (const mechanism of $EXTERNAL_AUTH_SOURCE_MECHANISMS) { + it('should set authSource to $external for ${mechanism} external mechanism:', async function () { + makeStub('authSource=thisShouldNotBeAuthSource'); + const options = { + credentials: new MongoCredentials({ + source: '$external', + mechanism: mechanism, + username: 'username', + password: 'password', + mechanismProperties: {} + }), + srvHost: 'test.mock.test.build.10gen.cc', + srvServiceName: 'mongodb', + userSpecifiedAuthSource: false + }; + + await resolveSRVRecordAsync(options as any); + expect(options).to.have.nested.property('credentials.source', '$external'); + }); + } + + it('should set a default authSource for non-external mechanisms with no user-specified source:', async function () { + makeStub('authSource=thisShouldBeAuthSource'); + + const options = { + credentials: new MongoCredentials({ + source: 'admin', + mechanism: AuthMechanism.MONGODB_SCRAM_SHA256, + username: 'username', + password: 'password', + mechanismProperties: {} + }), + srvHost: 'test.mock.test.build.10gen.cc', + srvServiceName: 'mongodb', + userSpecifiedAuthSource: false + }; + + await resolveSRVRecordAsync(options as any); + expect(options).to.have.nested.property('credentials.source', 'thisShouldBeAuthSource'); + }); + for (const iterator of [ - { - mechanism: AuthMechanism.MONGODB_AWS, - source: '', - userSpecifiedAuthSource: false, - expected: 'succeed' - }, - { - mechanism: AuthMechanism.MONGODB_AWS, - source: 'admin', - userSpecifiedAuthSource: true, - expected: 'succeed' - }, - { - mechanism: null, - source: 'admin', - userSpecifiedAuthSource: false, - expected: 'fail' - } + { mechanism: AuthMechanism.MONGODB_AWS }, + { mechmanism: AuthMechanism.MONGODB_SCRAM_SHA256 } ]) { - it(`should ${iterator.expected} for ${iterator.mechanism} mechanism and ${ - iterator.userSpecifiedAuthSource ? '' : 'non-' - }user-specified source: ${iterator.source}`, function () { - makeStub('authSource=admin'); - + it('should retain credentials for any mechanism with no user-sepcificed source and no source in DNS:', async function () { + makeStub(''); const options = { credentials: new MongoCredentials({ - source: '$external', + source: 'admin', mechanism: iterator.mechanism, username: 'username', password: 'password', mechanismProperties: {} }), - srvHost: 'host', + srvHost: 'test.mock.test.build.10gen.cc', srvServiceName: 'mongodb', - userSpecifiedAuthSource: iterator.userSpecifiedAuthSource + userSpecifiedAuthSource: false }; - resolveSRVRecord(options as any, (err, hostAddress) => { - if (iterator.expected === 'succeed') { - expect(options).to.have.nested.property('credentials.source', '$external'); - } else { - expect(options).to.not.have.nested.property('credentials.source', '$external'); - } - }); - - // expect(client).to.have.nested.property('options.credentials.source', '$external'); + await resolveSRVRecordAsync(options as any); + expect(options).to.have.nested.property('credentials.source', 'admin'); }); } }); From 0e57cfb3c0acd7ef7dc171953d0dd4e9ab960d45 Mon Sep 17 00:00:00 2001 From: Sam Zhang Date: Mon, 13 Dec 2021 17:42:03 -0500 Subject: [PATCH 04/10] cleanup test --- test/unit/srv_option_handling.test.ts | 39 ++++++++++++--------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/test/unit/srv_option_handling.test.ts b/test/unit/srv_option_handling.test.ts index a425546e3c..5fbc4f986f 100644 --- a/test/unit/srv_option_handling.test.ts +++ b/test/unit/srv_option_handling.test.ts @@ -100,27 +100,22 @@ describe('Srv Option Handling', () => { expect(options).to.have.nested.property('credentials.source', 'thisShouldBeAuthSource'); }); - for (const iterator of [ - { mechanism: AuthMechanism.MONGODB_AWS }, - { mechmanism: AuthMechanism.MONGODB_SCRAM_SHA256 } - ]) { - it('should retain credentials for any mechanism with no user-sepcificed source and no source in DNS:', async function () { - makeStub(''); - const options = { - credentials: new MongoCredentials({ - source: 'admin', - mechanism: iterator.mechanism, - username: 'username', - password: 'password', - mechanismProperties: {} - }), - srvHost: 'test.mock.test.build.10gen.cc', - srvServiceName: 'mongodb', - userSpecifiedAuthSource: false - }; + it('should retain credentials for any mechanism with no user-sepcificed source and no source in DNS:', async function () { + makeStub(''); + const options = { + credentials: new MongoCredentials({ + source: 'admin', + mechanism: AuthMechanism.MONGODB_SCRAM_SHA256, + username: 'username', + password: 'password', + mechanismProperties: {} + }), + srvHost: 'test.mock.test.build.10gen.cc', + srvServiceName: 'mongodb', + userSpecifiedAuthSource: false + }; - await resolveSRVRecordAsync(options as any); - expect(options).to.have.nested.property('credentials.source', 'admin'); - }); - } + await resolveSRVRecordAsync(options as any); + expect(options).to.have.nested.property('credentials.source', 'admin'); + }); }); From 9239243fcb9554de0b903dbf244776c081aa98fd Mon Sep 17 00:00:00 2001 From: Sam Zhang Date: Tue, 14 Dec 2021 11:41:25 -0500 Subject: [PATCH 05/10] linting fix --- src/cmap/auth/mongo_credentials.ts | 2 +- src/connection_string.ts | 3 ++- test/unit/srv_option_handling.test.ts | 11 ++++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/cmap/auth/mongo_credentials.ts b/src/cmap/auth/mongo_credentials.ts index e919013324..c5907e014b 100644 --- a/src/cmap/auth/mongo_credentials.ts +++ b/src/cmap/auth/mongo_credentials.ts @@ -2,7 +2,7 @@ import type { Document } from '../../bson'; import { MongoAPIError, MongoMissingCredentialsError } from '../../error'; -import { AuthMechanism, $EXTERNAL_AUTH_SOURCE_MECHANISMS } from './providers'; +import { $EXTERNAL_AUTH_SOURCE_MECHANISMS, AuthMechanism } from './providers'; // https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst function getDefaultAuthMechanism(ismaster?: Document): AuthMechanism { diff --git a/src/connection_string.ts b/src/connection_string.ts index e170a42f7b..3a84bff75b 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -2,9 +2,10 @@ import * as dns from 'dns'; import * as fs from 'fs'; import ConnectionString from 'mongodb-connection-string-url'; import { URLSearchParams } from 'url'; + import type { Document } from './bson'; import { MongoCredentials } from './cmap/auth/mongo_credentials'; -import { AuthMechanism, $EXTERNAL_AUTH_SOURCE_MECHANISMS } from './cmap/auth/providers'; +import { $EXTERNAL_AUTH_SOURCE_MECHANISMS, AuthMechanism } from './cmap/auth/providers'; import { Compressor, CompressorName } from './cmap/wire_protocol/compression'; import { Encrypter } from './encrypter'; import { MongoAPIError, MongoInvalidArgumentError, MongoParseError } from './error'; diff --git a/test/unit/srv_option_handling.test.ts b/test/unit/srv_option_handling.test.ts index 5fbc4f986f..6c4722de40 100644 --- a/test/unit/srv_option_handling.test.ts +++ b/test/unit/srv_option_handling.test.ts @@ -1,13 +1,14 @@ +import { expect } from 'chai'; import * as dns from 'dns'; import * as sinon from 'sinon'; -import { expect } from 'chai'; import { promisify } from 'util'; -import { MongoCredentials } from '../../src/cmap/auth/mongo_credentials'; -import { resolveSRVRecord } from '../../src/connection_string'; + import { - AuthMechanism, - $EXTERNAL_AUTH_SOURCE_MECHANISMS + $EXTERNAL_AUTH_SOURCE_MECHANISMS, + AuthMechanism } from '../../src/cmap/auth/defaultAuthProviders'; +import { MongoCredentials } from '../../src/cmap/auth/mongo_credentials'; +import { resolveSRVRecord } from '../../src/connection_string'; const resolveSRVRecordAsync = promisify(resolveSRVRecord); From e8ed30745b626a06f25d14325d45c4a8a4e10eb7 Mon Sep 17 00:00:00 2001 From: Sam Zhang Date: Tue, 14 Dec 2021 11:43:44 -0500 Subject: [PATCH 06/10] fix unit test import --- src/cmap/auth/providers.ts | 2 +- test/unit/srv_option_handling.test.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/cmap/auth/providers.ts b/src/cmap/auth/providers.ts index a7ac6ded35..9fbb59be5f 100644 --- a/src/cmap/auth/providers.ts +++ b/src/cmap/auth/providers.ts @@ -11,7 +11,7 @@ export const AuthMechanism = Object.freeze({ } as const); /** @public */ -export const $EXTERNAL_AUTH_SOURCE_MECHANISMS = new Set([ +export const $EXTERNAL_AUTH_SOURCE_MECHANISMS = new Set([ AuthMechanism.MONGODB_GSSAPI, AuthMechanism.MONGODB_AWS, AuthMechanism.MONGODB_X509 diff --git a/test/unit/srv_option_handling.test.ts b/test/unit/srv_option_handling.test.ts index 6c4722de40..15c4a82bd2 100644 --- a/test/unit/srv_option_handling.test.ts +++ b/test/unit/srv_option_handling.test.ts @@ -3,11 +3,8 @@ import * as dns from 'dns'; import * as sinon from 'sinon'; import { promisify } from 'util'; -import { - $EXTERNAL_AUTH_SOURCE_MECHANISMS, - AuthMechanism -} from '../../src/cmap/auth/defaultAuthProviders'; import { MongoCredentials } from '../../src/cmap/auth/mongo_credentials'; +import { $EXTERNAL_AUTH_SOURCE_MECHANISMS, AuthMechanism } from '../../src/cmap/auth/providers'; import { resolveSRVRecord } from '../../src/connection_string'; const resolveSRVRecordAsync = promisify(resolveSRVRecord); From 3c014bcc66e4afc706f50f00ca17ee9c5b821eb3 Mon Sep 17 00:00:00 2001 From: Sam Zhang Date: Tue, 14 Dec 2021 15:39:18 -0500 Subject: [PATCH 07/10] refactor(NODE-3675): rename test / reorganize describe blocks --- test/unit/connection_string.test.ts | 103 ++++++++++++++++++++++ test/unit/srv_option_handling.test.ts | 119 -------------------------- 2 files changed, 103 insertions(+), 119 deletions(-) create mode 100644 test/unit/connection_string.test.ts delete mode 100644 test/unit/srv_option_handling.test.ts diff --git a/test/unit/connection_string.test.ts b/test/unit/connection_string.test.ts new file mode 100644 index 0000000000..e618951c03 --- /dev/null +++ b/test/unit/connection_string.test.ts @@ -0,0 +1,103 @@ +import { expect } from 'chai'; +import * as dns from 'dns'; +import * as sinon from 'sinon'; +import { promisify } from 'util'; + +import { MongoCredentials } from '../../src/cmap/auth/mongo_credentials'; +import { $EXTERNAL_AUTH_SOURCE_MECHANISMS, AuthMechanism } from '../../src/cmap/auth/providers'; +import { resolveSRVRecord } from '../../src/connection_string'; + +describe('Connection String', () => { + describe('resolveSRVRecord()', () => { + let resolveSrvStub: sinon.SinonStub; + let resolveTxtStub: sinon.SinonStub; + const resolveSRVRecordAsync = promisify(resolveSRVRecord); + + afterEach(async () => { + sinon.restore(); + }); + + function makeStub(txtRecord: string) { + const mockAddress = [ + { + name: 'localhost.test.mock.test.build.10gen.cc', + port: 2017, + weight: 0, + priority: 0 + } + ]; + + const mockRecord: string[][] = [[txtRecord]]; + + // first call is for the driver initial connection + // second call will check the poller + resolveSrvStub = sinon.stub(dns, 'resolveSrv').callsFake((address, callback) => { + return process.nextTick(callback, null, mockAddress); + }); + + resolveTxtStub = sinon.stub(dns, 'resolveTxt').callsFake((address, thisIsWhatWeAreTesting) => { + thisIsWhatWeAreTesting(null, mockRecord); + }); + } + + for (const mechanism of $EXTERNAL_AUTH_SOURCE_MECHANISMS) { + it(`should set authSource to $external for ${mechanism} external mechanism`, async function () { + makeStub('authSource=thisShouldNotBeAuthSource'); + const options = { + credentials: new MongoCredentials({ + source: '$external', + mechanism: mechanism, + username: 'username', + password: 'password', + mechanismProperties: {} + }), + srvHost: 'test.mock.test.build.10gen.cc', + srvServiceName: 'mongodb', + userSpecifiedAuthSource: false + }; + + await resolveSRVRecordAsync(options as any); + expect(options).to.have.nested.property('credentials.source', '$external'); + }); + } + + it('should set a default authSource for non-external mechanisms with no user-specified source', async function () { + makeStub('authSource=thisShouldBeAuthSource'); + + const options = { + credentials: new MongoCredentials({ + source: 'admin', + mechanism: AuthMechanism.MONGODB_SCRAM_SHA256, + username: 'username', + password: 'password', + mechanismProperties: {} + }), + srvHost: 'test.mock.test.build.10gen.cc', + srvServiceName: 'mongodb', + userSpecifiedAuthSource: false + }; + + await resolveSRVRecordAsync(options as any); + expect(options).to.have.nested.property('credentials.source', 'thisShouldBeAuthSource'); + }); + + it('should retain credentials for any mechanism with no user-sepcificed source and no source in DNS', async function () { + makeStub(''); + const options = { + credentials: new MongoCredentials({ + source: 'admin', + mechanism: AuthMechanism.MONGODB_SCRAM_SHA256, + username: 'username', + password: 'password', + mechanismProperties: {} + }), + srvHost: 'test.mock.test.build.10gen.cc', + srvServiceName: 'mongodb', + userSpecifiedAuthSource: false + }; + + await resolveSRVRecordAsync(options as any); + expect(options).to.have.nested.property('credentials.source', 'admin'); + }); + }); +}); diff --git a/test/unit/srv_option_handling.test.ts b/test/unit/srv_option_handling.test.ts deleted file mode 100644 index 15c4a82bd2..0000000000 --- a/test/unit/srv_option_handling.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { expect } from 'chai'; -import * as dns from 'dns'; -import * as sinon from 'sinon'; -import { promisify } from 'util'; - -import { MongoCredentials } from '../../src/cmap/auth/mongo_credentials'; -import { $EXTERNAL_AUTH_SOURCE_MECHANISMS, AuthMechanism } from '../../src/cmap/auth/providers'; -import { resolveSRVRecord } from '../../src/connection_string'; - -const resolveSRVRecordAsync = promisify(resolveSRVRecord); - -describe('Srv Option Handling', () => { - let resolveSrvStub: sinon.SinonStub; - let resolveTxtStub: sinon.SinonStub; - let resolveSRVRecordStub: sinon.SinonStub; - let lookupStub: sinon.SinonStub; - - afterEach(async () => { - if (resolveSrvStub) { - resolveSrvStub.restore(); - resolveSrvStub = undefined; - } - if (resolveTxtStub) { - resolveTxtStub.restore(); - resolveTxtStub = undefined; - } - if (lookupStub) { - lookupStub.restore(); - lookupStub = undefined; - } - if (resolveSRVRecordStub) { - resolveSRVRecordStub.restore(); - resolveSRVRecordStub = undefined; - } - }); - - function makeStub(txtRecord: string) { - const mockAddress = [ - { - name: 'localhost.test.mock.test.build.10gen.cc', - port: 2017, - weight: 0, - priority: 0 - } - ]; - - const mockRecord: string[][] = [[txtRecord]]; - - // first call is for the driver initial connection - // second call will check the poller - resolveSrvStub = sinon.stub(dns, 'resolveSrv').callsFake((address, callback) => { - return process.nextTick(callback, null, mockAddress); - }); - - resolveTxtStub = sinon.stub(dns, 'resolveTxt').callsFake((address, thisIsWhatWeAreTesting) => { - thisIsWhatWeAreTesting(null, mockRecord); - }); - } - - for (const mechanism of $EXTERNAL_AUTH_SOURCE_MECHANISMS) { - it('should set authSource to $external for ${mechanism} external mechanism:', async function () { - makeStub('authSource=thisShouldNotBeAuthSource'); - const options = { - credentials: new MongoCredentials({ - source: '$external', - mechanism: mechanism, - username: 'username', - password: 'password', - mechanismProperties: {} - }), - srvHost: 'test.mock.test.build.10gen.cc', - srvServiceName: 'mongodb', - userSpecifiedAuthSource: false - }; - - await resolveSRVRecordAsync(options as any); - expect(options).to.have.nested.property('credentials.source', '$external'); - }); - } - - it('should set a default authSource for non-external mechanisms with no user-specified source:', async function () { - makeStub('authSource=thisShouldBeAuthSource'); - - const options = { - credentials: new MongoCredentials({ - source: 'admin', - mechanism: AuthMechanism.MONGODB_SCRAM_SHA256, - username: 'username', - password: 'password', - mechanismProperties: {} - }), - srvHost: 'test.mock.test.build.10gen.cc', - srvServiceName: 'mongodb', - userSpecifiedAuthSource: false - }; - - await resolveSRVRecordAsync(options as any); - expect(options).to.have.nested.property('credentials.source', 'thisShouldBeAuthSource'); - }); - - it('should retain credentials for any mechanism with no user-sepcificed source and no source in DNS:', async function () { - makeStub(''); - const options = { - credentials: new MongoCredentials({ - source: 'admin', - mechanism: AuthMechanism.MONGODB_SCRAM_SHA256, - username: 'username', - password: 'password', - mechanismProperties: {} - }), - srvHost: 'test.mock.test.build.10gen.cc', - srvServiceName: 'mongodb', - userSpecifiedAuthSource: false - }; - - await resolveSRVRecordAsync(options as any); - expect(options).to.have.nested.property('credentials.source', 'admin'); - }); -}); From 4dee7fca4b74b30cf035c462a3cbd3f3b4c48382 Mon Sep 17 00:00:00 2001 From: Sam Zhang Date: Tue, 14 Dec 2021 17:18:56 -0500 Subject: [PATCH 08/10] port tests to connection_string.ts and add MongoCredentials validation in testing --- src/cmap/auth/providers.ts | 2 +- src/connection_string.ts | 3 +- test/unit/connection_string.test.js | 322 ---------------------- test/unit/connection_string.test.ts | 398 +++++++++++++++++++++++++--- 4 files changed, 366 insertions(+), 359 deletions(-) delete mode 100644 test/unit/connection_string.test.js diff --git a/src/cmap/auth/providers.ts b/src/cmap/auth/providers.ts index 9fbb59be5f..6590a7d54b 100644 --- a/src/cmap/auth/providers.ts +++ b/src/cmap/auth/providers.ts @@ -11,7 +11,7 @@ export const AuthMechanism = Object.freeze({ } as const); /** @public */ -export const $EXTERNAL_AUTH_SOURCE_MECHANISMS = new Set([ +export const $EXTERNAL_AUTH_SOURCE_MECHANISMS = new Set([ AuthMechanism.MONGODB_GSSAPI, AuthMechanism.MONGODB_AWS, AuthMechanism.MONGODB_X509 diff --git a/src/connection_string.ts b/src/connection_string.ts index 3a84bff75b..db1fde5851 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -128,7 +128,8 @@ export function resolveSRVRecord(options: MongoOptions, callback: Callback parseOptions('mongodb://localhost', optionsWithUser)).to.throw(MongoParseError); - }); - - it('should support auth passed with username', function () { - const optionsWithUsername = { - authMechanism: 'SCRAM-SHA-1', - auth: { username: 'testing', password: 'llamas' } - }; - const options = parseOptions('mongodb://localhost', optionsWithUsername); - expect(options.credentials).to.containSubset({ - source: 'admin', - username: 'testing', - password: 'llamas' - }); - }); - - it('should provide a default port if one is not provided', function () { - const options = parseOptions('mongodb://hostname'); - expect(options.hosts[0].socketPath).to.be.undefined; - expect(options.hosts[0].host).to.be.a('string'); - expect(options.hosts[0].port).to.equal(27017); - }); - - it('should parse multiple readPreferenceTags', function () { - const options = parseOptions( - 'mongodb://hostname?readPreferenceTags=bar:foo&readPreferenceTags=baz:bar' - ); - expect(options.readPreference.tags).to.deep.equal([{ bar: 'foo' }, { baz: 'bar' }]); - }); - - it('should parse boolean values', function () { - let options = parseOptions('mongodb://hostname?retryWrites=1'); - expect(options.retryWrites).to.equal(true); - options = parseOptions('mongodb://hostname?retryWrites=false'); - expect(options.retryWrites).to.equal(false); - options = parseOptions('mongodb://hostname?retryWrites=t'); - expect(options.retryWrites).to.equal(true); - }); - - it('should parse compression options', function () { - const options = parseOptions('mongodb://localhost/?compressors=zlib&zlibCompressionLevel=4'); - expect(options).to.have.property('compressors'); - expect(options.compressors).to.include('zlib'); - expect(options.zlibCompressionLevel).to.equal(4); - }); - - it('should parse `readConcernLevel`', function () { - const options = parseOptions('mongodb://localhost/?readConcernLevel=local'); - expect(options).to.have.property('readConcern'); - expect(options.readConcern.level).to.equal('local'); - }); - - it('should parse `authMechanismProperties`', function () { - const options = parseOptions( - 'mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,SERVICE_REALM:blah,CANONICALIZE_HOST_NAME:true&authMechanism=GSSAPI' - ); - expect(options.credentials.mechanismProperties).to.deep.include({ - SERVICE_NAME: 'other', - SERVICE_REALM: 'blah', - CANONICALIZE_HOST_NAME: true - }); - expect(options.credentials.mechanism).to.equal(AuthMechanism.MONGODB_GSSAPI); - }); - - it('should provide default authSource when valid AuthMechanism provided', function () { - const options = parseOptions( - 'mongodb+srv://jira-sync.pw0q4.mongodb.net/testDB?authMechanism=MONGODB-AWS&retryWrites=true&w=majority' - ); - expect(options.credentials.source).to.equal('$external'); - }); - - it('should parse a numeric authSource with variable width', function () { - const options = parseOptions('mongodb://test@localhost/?authSource=0001'); - expect(options.credentials.source).to.equal('0001'); - }); - - it('should not remove dbName from the options if authSource is provided', function () { - const dbName = 'my-db-name'; - const authSource = 'admin'; - const options = parseOptions( - `mongodb://myName:myPassword@localhost:27017/${dbName}?authSource=${authSource}` - ); - - expect(options).has.property('dbName', dbName); - expect(options.credentials).to.have.property('source', authSource); - }); - - it('should parse a replicaSet with a leading number', function () { - const options = parseOptions('mongodb://localhost/?replicaSet=123abc'); - expect(options).to.have.property('replicaSet'); - expect(options.replicaSet).to.equal('123abc'); - }); - - context('when both tls and ssl options are provided', function () { - context('when the options are provided in the URI', function () { - context('when the options are equal', function () { - context('when both options are true', function () { - const options = parseOptions('mongodb://localhost/?tls=true&ssl=true'); - - it('sets the tls option', function () { - expect(options.tls).to.be.true; - }); - - it('does not set the ssl option', function () { - expect(options.ssl).to.be.undefined; - }); - }); - - context('when both options are false', function () { - const options = parseOptions('mongodb://localhost/?tls=false&ssl=false'); - - it('sets the tls option', function () { - expect(options.tls).to.be.false; - }); - - it('does not set the ssl option', function () { - expect(options.ssl).to.be.undefined; - }); - }); - }); - - context('when the options are not equal', function () { - it('raises an error', function () { - expect(() => { - parseOptions('mongodb://localhost/?tls=true&ssl=false'); - }).to.throw(MongoParseError, 'All values of tls/ssl must be the same.'); - }); - }); - }); - - context('when the options are provided in the options', function () { - context('when the options are equal', function () { - context('when both options are true', function () { - const options = parseOptions('mongodb://localhost/', { tls: true, ssl: true }); - - it('sets the tls option', function () { - expect(options.tls).to.be.true; - }); - - it('does not set the ssl option', function () { - expect(options.ssl).to.be.undefined; - }); - }); - - context('when both options are false', function () { - context('when the URI is an SRV URI', function () { - const options = parseOptions('mongodb+srv://localhost/', { tls: false, ssl: false }); - - it('overrides the tls option', function () { - expect(options.tls).to.be.false; - }); - - it('does not set the ssl option', function () { - expect(options.ssl).to.be.undefined; - }); - }); - - context('when the URI is not SRV', function () { - const options = parseOptions('mongodb://localhost/', { tls: false, ssl: false }); - - it('sets the tls option', function () { - expect(options.tls).to.be.false; - }); - - it('does not set the ssl option', function () { - expect(options.ssl).to.be.undefined; - }); - }); - }); - }); - - context('when the options are not equal', function () { - it('raises an error', function () { - expect(() => { - parseOptions('mongodb://localhost/', { tls: true, ssl: false }); - }).to.throw(MongoParseError, 'All values of tls/ssl must be the same.'); - }); - }); - }); - }); - - describe('validation', function () { - it('should validate compressors options', function () { - expect(() => parseOptions('mongodb://localhost/?compressors=bunnies')).to.throw( - MongoInvalidArgumentError, - 'bunnies is not a valid compression mechanism' - ); - }); - - it('should validate authMechanism', function () { - expect(() => parseOptions('mongodb://localhost/?authMechanism=DOGS')).to.throw( - MongoParseError, - 'authMechanism one of MONGODB-AWS,MONGODB-CR,DEFAULT,GSSAPI,PLAIN,SCRAM-SHA-1,SCRAM-SHA-256,MONGODB-X509, got DOGS' - ); - }); - - it('should validate readPreference', function () { - expect(() => parseOptions('mongodb://localhost/?readPreference=llamasPreferred')).to.throw( - MongoDriverError, // not parse Error b/c thrown from ReadPreference construction - 'Invalid read preference mode "llamasPreferred"' - ); - }); - }); - - describe('spec tests', function () { - const suites = loadSpecTests('connection-string').concat(loadSpecTests('auth')); - - for (const suite of suites) { - describe(suite.name, function () { - for (const test of suite.tests) { - it(`${test.description}`, function () { - if (skipTests.includes(test.description)) { - return this.skip(); - } - - const message = `"${test.uri}"`; - - const valid = test.valid; - if (valid) { - const options = parseOptions(test.uri); - expect(options, message).to.be.ok; - - if (test.hosts) { - for (const [index, { host, port }] of test.hosts.entries()) { - expect(options.hosts[index], message).to.satisfy(e => { - return e.host === host || e.socketPath === host; - }); - if (typeof port === 'number') expect(options.hosts[index].port).to.equal(port); - } - } - - if (test.auth && test.auth.db != null) { - expect(options.dbName, message).to.equal(test.auth.db); - } - - if (test.auth && test.auth.username) { - expect(options.credentials, message).to.exist; - - if (test.auth.db != null) { - expect(options.credentials.source, message).to.equal(test.auth.db); - } - - if (test.auth.username != null) { - expect(options.credentials.username, message).to.equal(test.auth.username); - } - - if (test.auth.password != null) { - expect(options.credentials.password, message).to.equal(test.auth.password); - } - } - - if (test.options) { - for (const [optionKey, optionValue] of Object.entries(test.options)) { - switch (optionKey) { - case 'authmechanism': - expect(options.credentials.mechanism, message).to.eq(optionValue); - break; - case 'authmechanismproperties': - expect(options.credentials.mechanismProperties, message).to.deep.eq( - optionValue - ); - break; - case 'replicaset': - expect(options.replicaSet, message).to.equal(optionValue); - break; - case 'w': - expect(options.writeConcern.w).to.equal(optionValue); - break; - default: - throw Error(`This options is not covered by the spec test: ${optionKey}`); - } - } - } - } else { - expect(() => parseOptions(test.uri), message).to.throw(); - } - }); - } - }); - } - }); - - describe('mongodb+srv', function () { - it('should parse a default database', function () { - const options = parseOptions('mongodb+srv://test1.test.build.10gen.cc/somedb'); - expect(options.dbName).to.equal('somedb'); - expect(options.srvHost).to.equal('test1.test.build.10gen.cc'); - }); - }); -}); diff --git a/test/unit/connection_string.test.ts b/test/unit/connection_string.test.ts index e618951c03..3db4c89667 100644 --- a/test/unit/connection_string.test.ts +++ b/test/unit/connection_string.test.ts @@ -5,12 +5,328 @@ import { promisify } from 'util'; import { MongoCredentials } from '../../src/cmap/auth/mongo_credentials'; import { $EXTERNAL_AUTH_SOURCE_MECHANISMS, AuthMechanism } from '../../src/cmap/auth/providers'; -import { resolveSRVRecord } from '../../src/connection_string'; +import { parseOptions, resolveSRVRecord } from '../../src/connection_string'; +import { MongoDriverError, MongoInvalidArgumentError, MongoParseError } from '../../src/error'; +import { MongoOptions } from '../../src/mongo_client'; +import { loadSpecTests } from '../spec'; + +// NOTE: These are cases we could never check for unless we write our own +// url parser. The node parser simply won't let these through, so we +// are safe skipping them. +const skipTests = [ + 'Invalid port (negative number) with hostname', + 'Invalid port (non-numeric string) with hostname', + 'Missing delimiting slash between hosts and options', + + // These tests are only relevant to the native driver which + // cares about specific keys, and validating their values + 'Unrecognized option keys are ignored', + 'Unsupported option values are ignored', + + // We don't actually support `wtimeoutMS` which this test depends upon + 'Deprecated (or unknown) options are ignored if replacement exists' +]; + +describe('Connection String', function () { + it('should not support auth passed with user', function () { + const optionsWithUser = { + authMechanism: 'SCRAM-SHA-1', + auth: { user: 'testing', password: 'llamas' } + }; + + expect(() => parseOptions('mongodb://localhost', optionsWithUser as any)).to.throw( + MongoParseError + ); + }); + + it('should support auth passed with username', function () { + const optionsWithUsername = { + authMechanism: 'SCRAM-SHA-1', + auth: { username: 'testing', password: 'llamas' } + }; + const options = parseOptions('mongodb://localhost', optionsWithUsername as any); + expect(options.credentials).to.containSubset({ + source: 'admin', + username: 'testing', + password: 'llamas' + }); + }); + + it('should provide a default port if one is not provided', function () { + const options = parseOptions('mongodb://hostname'); + expect(options.hosts[0].socketPath).to.be.undefined; + expect(options.hosts[0].host).to.be.a('string'); + expect(options.hosts[0].port).to.equal(27017); + }); + + it('should parse multiple readPreferenceTags', function () { + const options = parseOptions( + 'mongodb://hostname?readPreferenceTags=bar:foo&readPreferenceTags=baz:bar' + ); + expect(options.readPreference.tags).to.deep.equal([{ bar: 'foo' }, { baz: 'bar' }]); + }); + + it('should parse boolean values', function () { + let options = parseOptions('mongodb://hostname?retryWrites=1'); + expect(options.retryWrites).to.equal(true); + options = parseOptions('mongodb://hostname?retryWrites=false'); + expect(options.retryWrites).to.equal(false); + options = parseOptions('mongodb://hostname?retryWrites=t'); + expect(options.retryWrites).to.equal(true); + }); + + it('should parse compression options', function () { + const options = parseOptions('mongodb://localhost/?compressors=zlib&zlibCompressionLevel=4'); + expect(options).to.have.property('compressors'); + expect(options.compressors).to.include('zlib'); + expect(options.zlibCompressionLevel).to.equal(4); + }); + + it('should parse `readConcernLevel`', function () { + const options = parseOptions('mongodb://localhost/?readConcernLevel=local'); + expect(options).to.have.property('readConcern'); + expect(options.readConcern.level).to.equal('local'); + }); + + it('should parse `authMechanismProperties`', function () { + const options = parseOptions( + 'mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,SERVICE_REALM:blah,CANONICALIZE_HOST_NAME:true&authMechanism=GSSAPI' + ); + expect(options.credentials.mechanismProperties).to.deep.include({ + SERVICE_NAME: 'other', + SERVICE_REALM: 'blah', + CANONICALIZE_HOST_NAME: true + }); + expect(options.credentials.mechanism).to.equal(AuthMechanism.MONGODB_GSSAPI); + }); + + it('should provide default authSource when valid AuthMechanism provided', function () { + const options = parseOptions( + 'mongodb+srv://jira-sync.pw0q4.mongodb.net/testDB?authMechanism=MONGODB-AWS&retryWrites=true&w=majority' + ); + expect(options.credentials.source).to.equal('$external'); + }); + + it('should parse a numeric authSource with variable width', function () { + const options = parseOptions('mongodb://test@localhost/?authSource=0001'); + expect(options.credentials.source).to.equal('0001'); + }); + + it('should not remove dbName from the options if authSource is provided', function () { + const dbName = 'my-db-name'; + const authSource = 'admin'; + const options = parseOptions( + `mongodb://myName:myPassword@localhost:27017/${dbName}?authSource=${authSource}` + ); + + expect(options).has.property('dbName', dbName); + expect(options.credentials).to.have.property('source', authSource); + }); + + it('should parse a replicaSet with a leading number', function () { + const options = parseOptions('mongodb://localhost/?replicaSet=123abc'); + expect(options).to.have.property('replicaSet'); + expect(options.replicaSet).to.equal('123abc'); + }); + + context('when both tls and ssl options are provided', function () { + context('when the options are provided in the URI', function () { + context('when the options are equal', function () { + context('when both options are true', function () { + const options = parseOptions('mongodb://localhost/?tls=true&ssl=true'); + + it('sets the tls option', function () { + expect(options.tls).to.be.true; + }); + + it('does not set the ssl option', function () { + expect(options).to.not.have.property('ssl'); + }); + }); + + context('when both options are false', function () { + const options = parseOptions('mongodb://localhost/?tls=false&ssl=false'); + + it('sets the tls option', function () { + expect(options.tls).to.be.false; + }); + + it('does not set the ssl option', function () { + expect(options).to.not.have.property('ssl'); + }); + }); + }); + + context('when the options are not equal', function () { + it('raises an error', function () { + expect(() => { + parseOptions('mongodb://localhost/?tls=true&ssl=false'); + }).to.throw(MongoParseError, 'All values of tls/ssl must be the same.'); + }); + }); + }); + + context('when the options are provided in the options', function () { + context('when the options are equal', function () { + context('when both options are true', function () { + const options = parseOptions('mongodb://localhost/', { tls: true, ssl: true }); + + it('sets the tls option', function () { + expect(options.tls).to.be.true; + }); + + it('does not set the ssl option', function () { + expect(options).to.not.have.property('ssl'); + }); + }); + + context('when both options are false', function () { + context('when the URI is an SRV URI', function () { + const options = parseOptions('mongodb+srv://localhost/', { tls: false, ssl: false }); + + it('overrides the tls option', function () { + expect(options.tls).to.be.false; + }); + + it('does not set the ssl option', function () { + expect(options).to.not.have.property('ssl'); + }); + }); + + context('when the URI is not SRV', function () { + const options = parseOptions('mongodb://localhost/', { tls: false, ssl: false }); + + it('sets the tls option', function () { + expect(options.tls).to.be.false; + }); + + it('does not set the ssl option', function () { + expect(options).to.not.have.property('ssl'); + }); + }); + }); + }); + + context('when the options are not equal', function () { + it('raises an error', function () { + expect(() => { + parseOptions('mongodb://localhost/', { tls: true, ssl: false }); + }).to.throw(MongoParseError, 'All values of tls/ssl must be the same.'); + }); + }); + }); + }); + + describe('validation', function () { + it('should validate compressors options', function () { + expect(() => parseOptions('mongodb://localhost/?compressors=bunnies')).to.throw( + MongoInvalidArgumentError, + 'bunnies is not a valid compression mechanism' + ); + }); + + it('should validate authMechanism', function () { + expect(() => parseOptions('mongodb://localhost/?authMechanism=DOGS')).to.throw( + MongoParseError, + 'authMechanism one of MONGODB-AWS,MONGODB-CR,DEFAULT,GSSAPI,PLAIN,SCRAM-SHA-1,SCRAM-SHA-256,MONGODB-X509, got DOGS' + ); + }); + + it('should validate readPreference', function () { + expect(() => parseOptions('mongodb://localhost/?readPreference=llamasPreferred')).to.throw( + MongoDriverError, // not parse Error b/c thrown from ReadPreference construction + 'Invalid read preference mode "llamasPreferred"' + ); + }); + }); + + describe('spec tests', function () { + const suites = loadSpecTests('connection-string').concat(loadSpecTests('auth')); + + for (const suite of suites) { + describe(suite.name, function () { + for (const test of suite.tests) { + it(`${test.description}`, function () { + if (skipTests.includes(test.description)) { + return this.skip(); + } + + const message = `"${test.uri}"`; + + const valid = test.valid; + if (valid) { + const options = parseOptions(test.uri); + expect(options, message).to.be.ok; + + if (test.hosts) { + for (const [index, { host, port }] of test.hosts.entries()) { + expect(options.hosts[index], message).to.satisfy(e => { + return e.host === host || e.socketPath === host; + }); + if (typeof port === 'number') expect(options.hosts[index].port).to.equal(port); + } + } + + if (test.auth && test.auth.db != null) { + expect(options.dbName, message).to.equal(test.auth.db); + } + + if (test.auth && test.auth.username) { + expect(options.credentials, message).to.exist; + + if (test.auth.db != null) { + expect(options.credentials.source, message).to.equal(test.auth.db); + } + + if (test.auth.username != null) { + expect(options.credentials.username, message).to.equal(test.auth.username); + } + + if (test.auth.password != null) { + expect(options.credentials.password, message).to.equal(test.auth.password); + } + } + + if (test.options) { + for (const [optionKey, optionValue] of Object.entries(test.options)) { + switch (optionKey) { + case 'authmechanism': + expect(options.credentials.mechanism, message).to.eq(optionValue); + break; + case 'authmechanismproperties': + expect(options.credentials.mechanismProperties, message).to.deep.eq( + optionValue + ); + break; + case 'replicaset': + expect(options.replicaSet, message).to.equal(optionValue); + break; + case 'w': + expect(options.writeConcern.w).to.equal(optionValue); + break; + default: + throw Error(`This options is not covered by the spec test: ${optionKey}`); + } + } + } + } else { + expect(() => parseOptions(test.uri), message).to.throw(); + } + }); + } + }); + } + }); + + describe('mongodb+srv', function () { + it('should parse a default database', function () { + const options = parseOptions('mongodb+srv://test1.test.build.10gen.cc/somedb'); + expect(options.dbName).to.equal('somedb'); + expect(options.srvHost).to.equal('test1.test.build.10gen.cc'); + }); + }); -describe('Connection String', () => { describe('resolveSRVRecord()', () => { - let resolveSrvStub: sinon.SinonStub; - let resolveTxtStub: sinon.SinonStub; const resolveSRVRecordAsync = promisify(resolveSRVRecord); afterEach(async () => { @@ -29,34 +345,38 @@ describe('Connection String', () => { const mockRecord: string[][] = [[txtRecord]]; - // first call is for the driver initial connection - // second call will check the poller - resolveSrvStub = sinon.stub(dns, 'resolveSrv').callsFake((address, callback) => { + // first call is for stubbing resolveSrv + // second call is for stubbing resolveTxt + sinon.stub(dns, 'resolveSrv').callsFake((address, callback) => { return process.nextTick(callback, null, mockAddress); }); - resolveTxtStub = sinon.stub(dns, 'resolveTxt').callsFake((address, thisIsWhatWeAreTesting) => { - thisIsWhatWeAreTesting(null, mockRecord); + sinon.stub(dns, 'resolveTxt').callsFake((address, whatWeTest) => { + whatWeTest(null, mockRecord); }); } for (const mechanism of $EXTERNAL_AUTH_SOURCE_MECHANISMS) { it(`should set authSource to $external for ${mechanism} external mechanism`, async function () { makeStub('authSource=thisShouldNotBeAuthSource'); + const credentials = new MongoCredentials({ + source: '$external', + mechanism, + username: 'username', + password: mechanism === AuthMechanism.MONGODB_X509 ? undefined : 'password', + mechanismProperties: {} + }); + credentials.validate(); + const options = { - credentials: new MongoCredentials({ - source: '$external', - mechanism: mechanism, - username: 'username', - password: 'password', - mechanismProperties: {} - }), + credentials, srvHost: 'test.mock.test.build.10gen.cc', srvServiceName: 'mongodb', userSpecifiedAuthSource: false - }; + } as MongoOptions; - await resolveSRVRecordAsync(options as any); + await resolveSRVRecordAsync(options); + expect(options.credentials === credentials).to.be.true; expect(options).to.have.nested.property('credentials.source', '$external'); }); } @@ -64,39 +384,47 @@ describe('Connection String', () => { it('should set a default authSource for non-external mechanisms with no user-specified source', async function () { makeStub('authSource=thisShouldBeAuthSource'); + const credentials = new MongoCredentials({ + source: 'admin', + mechanism: AuthMechanism.MONGODB_SCRAM_SHA256, + username: 'username', + password: 'password', + mechanismProperties: {} + }); + credentials.validate(); + const options = { - credentials: new MongoCredentials({ - source: 'admin', - mechanism: AuthMechanism.MONGODB_SCRAM_SHA256, - username: 'username', - password: 'password', - mechanismProperties: {} - }), + credentials, srvHost: 'test.mock.test.build.10gen.cc', srvServiceName: 'mongodb', userSpecifiedAuthSource: false - }; + } as MongoOptions; - await resolveSRVRecordAsync(options as any); + await resolveSRVRecordAsync(options); + expect(options.credentials !== credentials).to.be.true; expect(options).to.have.nested.property('credentials.source', 'thisShouldBeAuthSource'); }); it('should retain credentials for any mechanism with no user-sepcificed source and no source in DNS', async function () { makeStub(''); + const credentials = new MongoCredentials({ + source: 'admin', + mechanism: AuthMechanism.MONGODB_SCRAM_SHA256, + username: 'username', + password: 'password', + mechanismProperties: {} + }); + credentials.validate(); + const options = { - credentials: new MongoCredentials({ - source: 'admin', - mechanism: AuthMechanism.MONGODB_SCRAM_SHA256, - username: 'username', - password: 'password', - mechanismProperties: {} - }), + credentials, srvHost: 'test.mock.test.build.10gen.cc', srvServiceName: 'mongodb', userSpecifiedAuthSource: false - }; + } as MongoOptions; await resolveSRVRecordAsync(options as any); + expect(options.credentials === credentials).to.be.true; expect(options).to.have.nested.property('credentials.source', 'admin'); }); }); From b0123e73ddcd055c013c503d4945d8f61891a62e Mon Sep 17 00:00:00 2001 From: Sam Zhang Date: Wed, 15 Dec 2021 11:01:04 -0500 Subject: [PATCH 09/10] rename , update test assertions --- src/cmap/auth/mongo_credentials.ts | 4 ++-- src/cmap/auth/providers.ts | 2 +- src/connection_string.ts | 6 +++--- test/unit/connection_string.test.ts | 13 ++++++++----- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/cmap/auth/mongo_credentials.ts b/src/cmap/auth/mongo_credentials.ts index c5907e014b..4e2ab95ea4 100644 --- a/src/cmap/auth/mongo_credentials.ts +++ b/src/cmap/auth/mongo_credentials.ts @@ -2,7 +2,7 @@ import type { Document } from '../../bson'; import { MongoAPIError, MongoMissingCredentialsError } from '../../error'; -import { $EXTERNAL_AUTH_SOURCE_MECHANISMS, AuthMechanism } from './providers'; +import { AUTH_MECHS_AUTH_SRC_EXTERNAL, AuthMechanism } from './providers'; // https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst function getDefaultAuthMechanism(ismaster?: Document): AuthMechanism { @@ -136,7 +136,7 @@ export class MongoCredentials { throw new MongoMissingCredentialsError(`Username required for mechanism '${this.mechanism}'`); } - if ($EXTERNAL_AUTH_SOURCE_MECHANISMS.has(this.mechanism)) { + if (AUTH_MECHS_AUTH_SRC_EXTERNAL.has(this.mechanism)) { if (this.source != null && this.source !== '$external') { // TODO(NODE-3485): Replace this with a MongoAuthValidationError throw new MongoAPIError( diff --git a/src/cmap/auth/providers.ts b/src/cmap/auth/providers.ts index 6590a7d54b..e4527b4081 100644 --- a/src/cmap/auth/providers.ts +++ b/src/cmap/auth/providers.ts @@ -11,7 +11,7 @@ export const AuthMechanism = Object.freeze({ } as const); /** @public */ -export const $EXTERNAL_AUTH_SOURCE_MECHANISMS = new Set([ +export const AUTH_MECHS_AUTH_SRC_EXTERNAL = new Set([ AuthMechanism.MONGODB_GSSAPI, AuthMechanism.MONGODB_AWS, AuthMechanism.MONGODB_X509 diff --git a/src/connection_string.ts b/src/connection_string.ts index db1fde5851..77f412b71c 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -5,7 +5,7 @@ import { URLSearchParams } from 'url'; import type { Document } from './bson'; import { MongoCredentials } from './cmap/auth/mongo_credentials'; -import { $EXTERNAL_AUTH_SOURCE_MECHANISMS, AuthMechanism } from './cmap/auth/providers'; +import { AUTH_MECHS_AUTH_SRC_EXTERNAL, AuthMechanism } from './cmap/auth/providers'; import { Compressor, CompressorName } from './cmap/wire_protocol/compression'; import { Encrypter } from './encrypter'; import { MongoAPIError, MongoInvalidArgumentError, MongoParseError } from './error'; @@ -129,7 +129,7 @@ export function resolveSRVRecord(options: MongoOptions, callback: Callback Date: Wed, 15 Dec 2021 13:48:17 -0500 Subject: [PATCH 10/10] style(NODE-3675): reorg declarations --- src/cmap/auth/providers.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cmap/auth/providers.ts b/src/cmap/auth/providers.ts index e4527b4081..9c7b1f4b82 100644 --- a/src/cmap/auth/providers.ts +++ b/src/cmap/auth/providers.ts @@ -11,11 +11,11 @@ export const AuthMechanism = Object.freeze({ } as const); /** @public */ +export type AuthMechanism = typeof AuthMechanism[keyof typeof AuthMechanism]; + +/** @internal */ export const AUTH_MECHS_AUTH_SRC_EXTERNAL = new Set([ AuthMechanism.MONGODB_GSSAPI, AuthMechanism.MONGODB_AWS, AuthMechanism.MONGODB_X509 ]); - -/** @public */ -export type AuthMechanism = typeof AuthMechanism[keyof typeof AuthMechanism];