diff --git a/test/authentication/testExternalBrowser.js b/test/authentication/testExternalBrowser.js index 2805147a8..ec5ac7363 100644 --- a/test/authentication/testExternalBrowser.js +++ b/test/authentication/testExternalBrowser.js @@ -3,41 +3,54 @@ const assert = require('assert'); const testUtil = require('../integration/testUtil'); const connParameters = require('./connectionParameters'); const { exec, spawn } = require('child_process'); +const Util = require('../../lib/util'); +const JsonCredentialManager = require('../../lib/authentication/secure_storage/json_credential_manager'); describe('External browser authentication tests', function () { const provideBrowserCredentialsPath = '/externalbrowser/provideBrowserCredentials.js'; const login = connParameters.snowflakeTestBrowserUser; const password = connParameters.snowflakeAuthTestOktaPass; - let connection; + let connection, error; - describe('External browser tests', async () => { - before(async () => { - await cleanBrowserProcesses(); - }); + before(async () => { + await cleanBrowserProcesses(); + }); - afterEach(async () => { - await cleanBrowserProcesses(); - if (connection !== undefined && connection.isUp()) { - await testUtil.destroyConnectionAsync(connection); - } - }); + afterEach(async () => { + await cleanBrowserProcesses(); + await destroyConnection(connection); + error = undefined; + }); + describe('External browser tests', async () => { it('Successful connection', async () => { const connectionOption = { ...connParameters.externalBrowser, clientStoreTemporaryCredential: false }; connection = await snowflake.createConnection(connectionOption); const provideCredentialsPromise = execWithTimeout('node', [provideBrowserCredentialsPath, 'success', login, password], 15000); - await Promise.allSettled([connection.connectAsync(function () {}), provideCredentialsPromise]); + await Promise.allSettled([connection.connectAsync(connectAsyncCallback()), provideCredentialsPromise]); + verifyNoErrorWasThrown(); await verifyConnectionIsUp(connection); }); + it('Mismatched Username', async () => { + const connectionOption = { ...connParameters.externalBrowser, username: 'differentUsername', clientStoreTemporaryCredential: false }; + connection = await snowflake.createConnection(connectionOption); + const provideCredentialsPromise = execWithTimeout('node', [provideBrowserCredentialsPath, 'success', login, password], 15000); + await Promise.allSettled([connection.connectAsync(connectAsyncCallback()), provideCredentialsPromise]); + assert.strictEqual(error?.message, 'The user you were trying to authenticate as differs from the user currently logged in at the IDP.'); + await verifyConnectionIsNotUp(connection, 'Unable to perform operation using terminated connection.'); + }); + it('Wrong credentials', async () => { const login = 'itsnotanaccount.com'; const password = 'fakepassword'; - const connectionOption = { ...connParameters.externalBrowser, browserActionTimeout: 5000, clientStoreTemporaryCredential: false }; + const connectionOption = { ...connParameters.externalBrowser, browserActionTimeout: 10000, clientStoreTemporaryCredential: false }; connection = await snowflake.createConnection(connectionOption); const provideCredentialsPromise = execWithTimeout('node', [provideBrowserCredentialsPath, 'fail', login, password]); - await Promise.allSettled([connection.connectAsync(function () {}), provideCredentialsPromise]); + const results = await Promise.allSettled([connection.connectAsync(connectAsyncCallback()), provideCredentialsPromise]); + assert.strictEqual(results[1].status, 'fulfilled'); + assert.strictEqual(error?.message, 'Error while getting SAML token: Browser action timed out after 10000 ms.'); await verifyConnectionIsNotUp(connection); }); @@ -46,10 +59,72 @@ describe('External browser authentication tests', function () { connection = await snowflake.createConnection(connectionOption); const connectToBrowserPromise = execWithTimeout('node', [provideBrowserCredentialsPath, 'timeout']); - await Promise.allSettled([connection.connectAsync(function () {}), connectToBrowserPromise]); + await Promise.allSettled([connection.connectAsync(connectAsyncCallback()), connectToBrowserPromise]); + assert.strictEqual(error?.message, 'Error while getting SAML token: Browser action timed out after 100 ms.'); await verifyConnectionIsNotUp(connection); }); }); + + describe('ID Token authentication tests', async () => { + const connectionOption = { ...connParameters.externalBrowser, clientStoreTemporaryCredential: true }; + const key = Util.buildCredentialCacheKey(connectionOption.host, connectionOption.username, 'ID_TOKEN'); + const defaultCredentialManager = new JsonCredentialManager(); + let firstIdToken; + + before(async () => { + await defaultCredentialManager.remove(key); + await cleanBrowserProcesses(); + }); + + afterEach(async () => { + await cleanBrowserProcesses(); + await destroyConnection(connection); + }); + + it('obtains the id token from the server and saves it on the local storage', async function () { + connection = snowflake.createConnection(connectionOption); + const provideCredentialsPromise = execWithTimeout('node', [provideBrowserCredentialsPath, 'success', login, password], 15000); + await Promise.allSettled([connection.connectAsync(connectAsyncCallback()), provideCredentialsPromise]); + verifyNoErrorWasThrown(); + await verifyConnectionIsUp(connection); + }); + + it('the token is saved in the credential manager', async function () { + firstIdToken = await defaultCredentialManager.read(key); + assert.notStrictEqual(firstIdToken, null); + }); + + it('authenticates by token, browser credentials not needed', async function () { + connection = snowflake.createConnection(connectionOption); + await connection.connectAsync(connectAsyncCallback()); + verifyNoErrorWasThrown(); + await verifyConnectionIsUp(connection); + }); + + it('opens browser okta authentication again when token is incorrect', async function () { + await defaultCredentialManager.write(key, '1234'); + connection = snowflake.createConnection(connectionOption); + const provideCredentialsPromise = execWithTimeout('node', [provideBrowserCredentialsPath, 'success', login, password], 15000); + await Promise.allSettled([connection.connectAsync(connectAsyncCallback()), provideCredentialsPromise]); + verifyNoErrorWasThrown(); + await verifyConnectionIsUp(connection); + }); + + it('refreshes the token for credential cache key', async function () { + const newToken = await defaultCredentialManager.read(key); + assert.notStrictEqual(firstIdToken, newToken); + }); + }); + + function connectAsyncCallback() { + return function (err) { + error = err; + }; + } + + function verifyNoErrorWasThrown() { + assert.equal(error, null); + } }); async function cleanBrowserProcesses() { @@ -58,17 +133,25 @@ async function cleanBrowserProcesses() { } async function verifyConnectionIsUp(connection) { - assert.ok(await connection.isValidAsync()); + assert.ok(connection.isUp(), 'Connection is not up'); + assert.ok(await connection.isValidAsync(), 'Connection is not valid'); await testUtil.executeCmdAsync(connection, 'Select 1'); } -async function verifyConnectionIsNotUp(connection) { +async function verifyConnectionIsNotUp(connection, message = 'Unable to perform operation because a connection was never established.') { + assert.ok(!(connection.isUp()), 'Connection should not be up'); + assert.ok(!(await connection.isValidAsync()), 'Connection should not be valid'); try { - assert(!(await connection.isValidAsync())); await testUtil.executeCmdAsync(connection, 'Select 1'); assert.fail('Expected error was not thrown'); } catch (error) { - assert.strictEqual(error.message, 'Unable to perform operation because a connection was never established.'); + assert.strictEqual(error.message, message); + } +} + +async function destroyConnection(connection) { + if (connection !== undefined && connection.isUp() && connection.isValidAsync()) { + await testUtil.destroyConnectionAsync(connection); } } diff --git a/test/integration/connectionOptions.js b/test/integration/connectionOptions.js index 38c0ee623..28185ff2e 100644 --- a/test/integration/connectionOptions.js +++ b/test/integration/connectionOptions.js @@ -16,7 +16,6 @@ const snowflakeTestRole = process.env.SNOWFLAKE_TEST_ROLE; const snowflakeTestPassword = process.env.SNOWFLAKE_TEST_PASSWORD; const snowflakeTestAdminUser = process.env.SNOWFLAKE_TEST_ADMIN_USER; const snowflakeTestAdminPassword = process.env.SNOWFLAKE_TEST_ADMIN_PASSWORD; -const snowflakeTestBrowserUser = process.env.SNOWFLAKE_TEST_BROWSER_USER; const snowflakeTestPrivateKeyUser = process.env.SNOWFLAKE_JWT_TEST_USER; const snowflakeTestPrivateKey = process.env.SNOWFLAKE_TEST_PRIVATE_KEY; const snowflakeTestPrivateKeyPath = process.env.SNOWFLAKE_TEST_PRIVATE_KEY_PATH; @@ -89,32 +88,6 @@ const wrongPwd = account: snowflakeTestAccount }; -const externalBrowser = -{ - accessUrl: accessUrl, - username: snowflakeTestBrowserUser, - account: snowflakeTestAccount, - warehouse: snowflakeTestWarehouse, - database: snowflakeTestDatabase, - schema: snowflakeTestSchema, - role: snowflakeTestRole, - host: snowflakeTestHost, - authenticator: 'EXTERNALBROWSER' -}; - -const externalBrowserWithShortTimeout = { - ...externalBrowser, - browserActionTimeout: 100, -}; - -const externalBrowserMismatchUser = -{ - accessUrl: accessUrl, - username: 'node', - account: snowflakeTestAccount, - authenticator: 'EXTERNALBROWSER' -}; - const keypairPrivateKey = { accessUrl: accessUrl, @@ -233,9 +206,6 @@ exports.wrongUserName = wrongUserName; exports.wrongPwd = wrongPwd; exports.accessUrl = accessUrl; exports.account = snowflakeTestAccount; -exports.externalBrowser = externalBrowser; -exports.externalBrowserWithShortTimeout = externalBrowserWithShortTimeout; -exports.externalBrowserMismatchUser = externalBrowserMismatchUser; exports.keypairPrivateKey = keypairPrivateKey; exports.keypairPathEncrypted = keypairPathEncrypted; exports.keypairPathUnencrypted = keypairPathUnencrypted; diff --git a/test/integration/testManualConnection.js b/test/integration/testManualConnection.js index a262ef738..b3b93e4d2 100644 --- a/test/integration/testManualConnection.js +++ b/test/integration/testManualConnection.js @@ -13,78 +13,6 @@ const JsonCredentialManager = require('../../lib/authentication/secure_storage/j if (process.env.RUN_MANUAL_TESTS_ONLY === 'true') { describe('Run manual tests', function () { - describe('Connection - ID Token authenticator', function () { - const connectionOption = { ...connOption.externalBrowser, clientStoreTemporaryCredential: true }; - const key = Util.buildCredentialCacheKey(connectionOption.host, connectionOption.username, 'ID_TOKEN'); - const defaultCredentialManager = new JsonCredentialManager(); - let oldToken; - before( async () => { - await defaultCredentialManager.remove(key); - }); - - it('test - obtain the id token from the server and save it on the local storage', function (done) { - const connection = snowflake.createConnection(connectionOption); - connection.connectAsync(function (err) { - try { - assert.ok(!err); - done(); - } catch (err){ - done(err); - } - }); - }); - - it('test - the token is saved in the credential manager correctly', function (done) { - defaultCredentialManager.read(key).then((idToken) => { - try { - oldToken = idToken; - assert.notStrictEqual(idToken, null); - done(); - } catch (err){ - done(err); - } - }); - }); - - - // Web Browser should not be open. - it('test - id token authentication', function (done) { - const idTokenConnection = snowflake.createConnection(connectionOption); - try { - idTokenConnection.connectAsync(function (err) { - assert.ok(!err); - done(); - }); - } catch (err) { - done(err); - } - }); - - // Web Browser should be open. - it('test - id token reauthentication', function (done) { - defaultCredentialManager.write(key, '1234').then(() => { - const wrongTokenConnection = snowflake.createConnection(connectionOption); - wrongTokenConnection.connectAsync(function (err) { - assert.ok(!err); - done(); - }); - }); - }); - - //Compare two idToken. Those two should be different. - it('test - the token is refreshed', function (done) { - oldToken = undefined; - defaultCredentialManager.read(key).then((idToken) => { - try { - assert.notStrictEqual(idToken, oldToken); - done(); - } catch (err) { - done(err); - } - }); - }); - }); - describe('Connection test - oauth', function () { it('Simple Connect', function (done) { const connection = snowflake.createConnection(connOption.oauth); @@ -136,36 +64,6 @@ if (process.env.RUN_MANUAL_TESTS_ONLY === 'true') { }); }); - describe('Connection test - okta', function () { - it('Simple Connect', function (done) { - const connection = snowflake.createConnection(connOption.okta); - - async.series([ - function (callback) { - connection.connectAsync(function (err) { - done(err); - assert.ok(!err, JSON.stringify(err)); - callback(); - }); - }, - function (callback) { - assert.ok(connection.isUp(), 'not active'); - callback(); - }, - function (callback) { - connection.destroy(function (err) { - assert.ok(!err, JSON.stringify(err)); - callback(); - }); - }, - function (callback) { - assert.ok(!connection.isUp(), 'still active'); - callback(); - }, - ]); - }); - }); - describe('Connection - MFA authenticator with DUO', function () { const connectionOption = connOption.MFA;