diff --git a/lib/authentication/auth_oauth_pat.js b/lib/authentication/auth_oauth_pat.js new file mode 100644 index 000000000..b439a15d1 --- /dev/null +++ b/lib/authentication/auth_oauth_pat.js @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2015-2024 Snowflake Computing Inc. All rights reserved. + */ + +const Util = require('../util'); +/** + * Creates an oauth authenticator. + * + * @param {String} token + * + * @returns {Object} + * @constructor + */ +function AuthOauthPAT(token, password) { + /** + * Update JSON body with token. + * + * @param {JSON} body + * + * @returns {null} + */ + this.updateBody = function (body) { + if (Util.exists(token)) { + body['data']['TOKEN'] = token; + } else if (Util.exists(password)) { + body['data']['TOKEN'] = password; + } + }; + + this.authenticate = async function () {}; +} +module.exports = AuthOauthPAT; diff --git a/lib/authentication/authentication.js b/lib/authentication/authentication.js index 79a7252a8..aa50d2944 100644 --- a/lib/authentication/authentication.js +++ b/lib/authentication/authentication.js @@ -6,6 +6,7 @@ const AuthDefault = require('./auth_default'); const AuthWeb = require('./auth_web'); const AuthKeypair = require('./auth_keypair'); const AuthOauth = require('./auth_oauth'); +const AuthOauthPAT = require('./auth_oauth_pat'); const AuthOkta = require('./auth_okta'); const AuthIDToken = require('./auth_idtoken'); const Logger = require('../logger'); @@ -73,8 +74,10 @@ exports.getAuthenticator = function getAuthenticator(connectionConfig, httpClien } } else if (authType === AuthenticationTypes.KEY_PAIR_AUTHENTICATOR) { auth = new AuthKeypair(connectionConfig); - } else if (authType === AuthenticationTypes.OAUTH_AUTHENTICATOR) { + } else if (authType === AuthenticationTypes.OAUTH_AUTHENTICATOR ) { auth = new AuthOauth(connectionConfig.getToken()); + } else if (authType === AuthenticationTypes.PROGRAMMATIC_ACCESS_TOKEN ) { + auth = new AuthOauthPAT(connectionConfig.getToken(), connectionConfig.password); } else if (this.isOktaAuth(authType)) { auth = new AuthOkta(connectionConfig, httpClient); } else { diff --git a/lib/authentication/authentication_types.js b/lib/authentication/authentication_types.js index e16b40984..ef808cda0 100644 --- a/lib/authentication/authentication_types.js +++ b/lib/authentication/authentication_types.js @@ -6,6 +6,7 @@ const AuthenticationTypes = OAUTH_AUTHENTICATOR: 'OAUTH', USER_PWD_MFA_AUTHENTICATOR: 'USERNAME_PASSWORD_MFA', ID_TOKEN_AUTHENTICATOR: 'ID_TOKEN', + PROGRAMMATIC_ACCESS_TOKEN: 'PROGRAMMATIC_ACCESS_TOKEN', }; module.exports = AuthenticationTypes; \ No newline at end of file diff --git a/lib/connection/connection_config.js b/lib/connection/connection_config.js index 55e8ef426..36293cc42 100644 --- a/lib/connection/connection_config.js +++ b/lib/connection/connection_config.js @@ -170,7 +170,8 @@ function ConnectionConfig(options, validateCredentials, qaMode, clientInfo) { // username is not required for oauth and external browser authenticators if (!Util.exists(options.authenticator) || (options.authenticator.toUpperCase() !== AuthenticationTypes.OAUTH_AUTHENTICATOR && - options.authenticator.toUpperCase() !== AuthenticationTypes.EXTERNAL_BROWSER_AUTHENTICATOR)) { + options.authenticator.toUpperCase() !== AuthenticationTypes.EXTERNAL_BROWSER_AUTHENTICATOR && + options.authenticator.toUpperCase() !== AuthenticationTypes.PROGRAMMATIC_ACCESS_TOKEN)) { // check for missing username Errors.checkArgumentExists(Util.exists(options.username), ErrorCodes.ERR_CONN_CREATE_MISSING_USERNAME); @@ -194,6 +195,23 @@ function ConnectionConfig(options, validateCredentials, qaMode, clientInfo) { ErrorCodes.ERR_CONN_CREATE_INVALID_PASSWORD); } + if (!Util.exists(options.authenticator) || + options.authenticator === AuthenticationTypes.PROGRAMMATIC_ACCESS_TOKEN) { + // PASSWORD or TOKEN is needed + Errors.checkArgumentExists(Util.exists(options.password) || Util.exists(options.token), + ErrorCodes.ERR_CONN_CREATE_MISSING_PASSWORD); + + if (Util.exists(options.password)) { + // check for invalid password + Errors.checkArgumentValid(Util.isString(options.password), + ErrorCodes.ERR_CONN_CREATE_INVALID_PASSWORD); + } + if (Util.exists(options.token)) { + Errors.checkArgumentValid(Util.isString(options.token), + ErrorCodes.ERR_CONN_CREATE_INVALID_OAUTH_TOKEN); + } + } + consolidateHostAndAccount(options); } diff --git a/lib/constants/error_messages.js b/lib/constants/error_messages.js index b1bfff5e0..277671b7a 100644 --- a/lib/constants/error_messages.js +++ b/lib/constants/error_messages.js @@ -84,6 +84,7 @@ exports[404053] = 'A host must be specified.'; exports[404054] = 'Invalid host. The specified value must be a string.'; exports[404055] = 'Invalid passcodeInPassword. The specified value must be a boolean'; exports[404056] = 'Invalid passcode. The specified value must be a string'; +exports[404057] = 'A password or token must be specified.'; // 405001 exports[405001] = 'Invalid callback. The specified value must be a function.'; diff --git a/lib/errors.js b/lib/errors.js index 9a31d81fd..a2ff611d7 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -88,6 +88,7 @@ codes.ERR_CONN_CREATE_MISSING_HOST = 404053; codes.ERR_CONN_CREATE_INVALID_HOST = 404054; codes.ERR_CONN_CREATE_INVALID_PASSCODE_IN_PASSWORD = 404055; codes.ERR_CONN_CREATE_INVALID_PASSCODE = 404056; +codes.ERR_CONN_CREATE_MISSING_PASSWORD_AND_TOKEN = 404057; // 405001 codes.ERR_CONN_CONNECT_INVALID_CALLBACK = 405001; diff --git a/package.json b/package.json index 3ed2a8a25..6129033c5 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,9 @@ "mocha": "^10.2.0", "mock-require": "^3.0.3", "nyc": "^15.1.0", - "test-console": "^2.0.0" + "test-console": "^2.0.0", + "wiremock": "^3.10.0", + "wiremock-rest-client": "^1.11.0" }, "peerDependencies": { "asn1.js": "^5.4.1" @@ -71,7 +73,9 @@ "test:ci:coverage": "nyc npm run test:ci", "test:ci:withSystemTests": "mocha -timeout 180000 --recursive --full-trace 'test/{unit,integration}/**/*.js' system_test/*.js", "test:ci:withSystemTests:coverage": "nyc npm run test:ci:withSystemTests", - "test:manual": "mocha -timeout 180000 --full-trace --full-trace test/integration/testManualConnection.js" + "test:manual": "mocha -timeout 180000 --full-trace --full-trace test/integration/testManualConnection.js", + "serve-wiremock": "wiremock --enable-browser-proxying --proxy-pass-through false --port 8081 ", + "wiremock": "npm run serve-wiremock" }, "author": { "name": "Snowflake Computing, Inc.", diff --git a/test/authentication/connectionParameters.js b/test/authentication/connectionParameters.js index 7d9d10745..2036c2a53 100644 --- a/test/authentication/connectionParameters.js +++ b/test/authentication/connectionParameters.js @@ -54,6 +54,19 @@ const oauth = authenticator: 'OAUTH' }; +const oauthPATOnWiremock = + { + ...baseParameters, + accessUrl: null, + username: 'MOCK_USERNAME', + account: 'MOCK_ACCOUNT_NAME', + host: 'localhost', + protocol: 'http', + authenticator: 'PROGRAMMATIC_ACCESS_TOKEN', + // proxyHost: '127.0.0.1', + // proxyPort: 8080 + }; + const keypairPrivateKey = { ...baseParameters, @@ -81,6 +94,7 @@ const keypairEncryptedPrivateKeyPath = exports.externalBrowser = externalBrowser; exports.okta = okta; exports.oauth = oauth; +exports.oauthPATOnWiremock = oauthPATOnWiremock; exports.keypairPrivateKey = keypairPrivateKey; exports.keypairPrivateKeyPath = keypairPrivateKeyPath; exports.keypairEncryptedPrivateKeyPath = keypairEncryptedPrivateKeyPath; diff --git a/test/authentication/testOauth.js b/test/authentication/testOauth.js index 5f1148626..d52b65ad3 100644 --- a/test/authentication/testOauth.js +++ b/test/authentication/testOauth.js @@ -1,12 +1,86 @@ const assert = require('assert'); +const fs = require('fs'); +const net = require('net'); const connParameters = require('./connectionParameters'); const axios = require('axios'); -const { snowflakeAuthTestOktaUser, snowflakeAuthTestOktaPass, snowflakeAuthTestRole, snowflakeAuthTestOauthClientId, +const { snowflakeAuthTestOauthClientId, snowflakeAuthTestOauthClientSecret, snowflakeAuthTestOauthUrl } = require('./connectionParameters'); const AuthTest = require('./authTestsBaseClass'); +const WireMockRestClient = require('wiremock-rest-client').WireMockRestClient; +const { exec } = require('child_process'); + +async function runWireMockAsync(port) { + let timeoutHandle; + const waitingWireMockPromise = new Promise(async (resolve, reject) => { + try { + exec(`npx wiremock --enable-browser-proxying --proxy-pass-through false --port ${port} `); + const wireMock = new WireMockRestClient(`http://localhost:${port}`); + const readyWireMock = await waitForWiremockStarted(wireMock); + resolve(readyWireMock); + } catch (err) { + reject(err); + } + }); + + + const timeout = new Promise((resolve, reject) => + timeoutHandle = setTimeout( + () => reject('Wiremock unavailable after 6000 ms.'), + 6000)); + return Promise.race([waitingWireMockPromise, timeout]) + .then(result => { + clearTimeout(timeoutHandle); + return result; + }); +} + +async function waitForWiremockStarted(wireMock) { + return fetch(wireMock.baseUri) + .then(async (resp) => { + if (resp.ok) { + return Promise.resolve(wireMock); + } else { + await new Promise(resolve => setTimeout(resolve, 1000)); + console.log(`Retry connection to WireMock after wrong response status: ${resp.status}`); + return await waitForWiremockStarted(wireMock); + } + }) + .catch(async (err) => { + await new Promise(resolve => setTimeout(resolve, 1000)); + console.log(`Retry connection to WireMock after error: ${err}`); + return await waitForWiremockStarted(wireMock); + }); +} + +describe('Wiremock test', function () { + it('Run Wiremock instance, wait, verify connection and shutdown', async function () { + const wireMock = await runWireMockAsync(); + try { + assert.doesNotReject(async () => await wireMock.mappings.getAllMappings()); + } finally { + await wireMock.global.shutdown(); + } + }); + it('Add mappings', async function () { + const wireMock = await runWireMockAsync(); + try { + const requests = JSON.parse(fs.readFileSync('wiremock/mappings/test.json', 'utf8')); + for (const mapping of requests.mappings) { + await wireMock.mappings.createMapping(mapping); + } + const mappings = await wireMock.mappings.getAllMappings(); + assert.strictEqual(mappings.mappings.length, 2); + const response = await axios.get('http://localhost:8081/test/authorize.html'); + assert.strictEqual(response.status, 200); + } finally { + await wireMock.global.shutdown(); + } + }); +}); + describe('Oauth authentication', function () { let authTest; @@ -45,8 +119,59 @@ describe('Oauth authentication', function () { }); }); +describe('Oauth PAT authentication', function () { + let port; + let authTest; + let wireMock; + before(async () => { + port = await getPortFree(); + wireMock = await runWireMockAsync(port); + }); + beforeEach(async () => { + authTest = new AuthTest(); + }); + afterEach(async () => { + wireMock.scenarios.resetAllScenarios(); + }); + after(async () => { + await wireMock.global.shutdown(); + }); + + + it('Successful flow scenario PAT as token', async function () { + await addWireMockMappingsFromFile('wiremock/mappings/pat/successful_flow.json'); + const connectionOption = { ...connParameters.oauthPATOnWiremock, token: 'MOCK_TOKEN', port: port }; + authTest.createConnection(connectionOption); + await authTest.connectAsync(); + authTest.verifyNoErrorWasThrown(); + }); + + it('Successful flow scenario PAT as password', async function () { + await addWireMockMappingsFromFile('wiremock/mappings/pat/successful_flow.json'); + const connectionOption = { ...connParameters.oauthPATOnWiremock, password: 'MOCK_TOKEN', port: port }; + authTest.createConnection(connectionOption); + await authTest.connectAsync(); + authTest.verifyNoErrorWasThrown(); + }); + + it('Invalid token', async function () { + await addWireMockMappingsFromFile('wiremock/mappings/pat/invalid_pat_token.json'); + const connectionOption = { ...connParameters.oauthPATOnWiremock, token: 'INVALID_TOKEN', port: port }; + authTest.createConnection(connectionOption); + await authTest.connectAsync(); + authTest.verifyErrorWasThrown('Programmatic access token is invalid.'); + }); + + async function addWireMockMappingsFromFile(filePath) { + const requests = JSON.parse(fs.readFileSync(filePath, 'utf8')); + for (const mapping of requests.mappings) { + await wireMock.mappings.createMapping(mapping); + } + } +}); + async function getToken() { - const response = await axios.post(snowflakeAuthTestOauthUrl, data, { + const response = await axios.post(snowflakeAuthTestOauthUrl, {}, { headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' }, @@ -59,9 +184,12 @@ async function getToken() { return response.data.access_token; } -const data = [ - `username=${snowflakeAuthTestOktaUser}`, - `password=${snowflakeAuthTestOktaPass}`, - 'grant_type=password', - `scope=session:role:${snowflakeAuthTestRole.toLowerCase()}` -].join('&'); +async function getPortFree() { + return new Promise( res => { + const srv = net.createServer(); + srv.listen(0, () => { + const port = srv.address().port; + srv.close((err) => res(port)); + }); + }); +} diff --git a/wiremock/mappings/oauth/authorization_code/external_idp_custom_urls.json b/wiremock/mappings/oauth/authorization_code/external_idp_custom_urls.json new file mode 100644 index 000000000..c03129042 --- /dev/null +++ b/wiremock/mappings/oauth/authorization_code/external_idp_custom_urls.json @@ -0,0 +1,76 @@ +{ + "mappings": [ + { + "scenarioName": "Custom urls OAuth authorization code flow", + "requiredScenarioState": "Started", + "newScenarioState": "Authorized", + "request": { + "urlPathPattern": "/authorization", + "method": "GET", + "queryParameters": { + "response_type": { + "equalTo": "code" + }, + "scope": { + "equalTo": "session:role:ANALYST" + }, + "code_challenge_method": { + "equalTo": "S256" + }, + "redirect_uri": { + "equalTo": "http://localhost:8007/snowflake/oauth-redirect" + }, + "code_challenge": { + "matches": ".*" + }, + "state": { + "matches": ".*" + }, + "client_id": { + "equalTo": "123" + } + } + }, + "response": { + "status": 302, + "headers": { + "Location": "http://localhost:8007/snowflake/oauth-redirect?code=123&state=abc123" + } + } + }, + { + "scenarioName": "Custom urls OAuth authorization code flow", + "requiredScenarioState": "Authorized", + "newScenarioState": "Acquired access token", + "request": { + "urlPathPattern": "/tokenrequest.*", + "method": "POST", + "headers": { + "Authorization": { + "contains": "Basic" + }, + "Content-Type": { + "contains": "application/x-www-form-urlencoded; charset=UTF-8" + } + }, + "bodyPatterns": [ + { + "contains": "grant_type=authorization_code&code=123&redirect_uri=http%3A%2F%2Flocalhost%3A8007%2Fsnowflake%2Foauth-redirect&code_verifier=" + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "access_token": "access-token-123", + "refresh_token": "123", + "token_type": "Bearer", + "username": "user", + "scope": "refresh_token session:role:ANALYST", + "expires_in": 600, + "refresh_token_expires_in": 86399, + "idpInitiated": false + } } + } + ] +} diff --git a/wiremock/mappings/oauth/authorization_code/invalid_scope_error.json b/wiremock/mappings/oauth/authorization_code/invalid_scope_error.json new file mode 100644 index 000000000..f92029a2b --- /dev/null +++ b/wiremock/mappings/oauth/authorization_code/invalid_scope_error.json @@ -0,0 +1,17 @@ +{ + "mappings": [ + { + "scenarioName": "Invalid scope authorization error", + "request": { + "urlPathPattern": "/oauth/authorize.*", + "method": "GET" + }, + "response": { + "status": 302, + "headers": { + "Location": "http://localhost:8002/snowflake/oauth-redirect?error=invalid_scope&error_description=One+or+more+scopes+are+not+configured+for+the+authorization+server+resource." + } + } + } + ] +} \ No newline at end of file diff --git a/wiremock/mappings/oauth/authorization_code/invalid_state_error.json b/wiremock/mappings/oauth/authorization_code/invalid_state_error.json new file mode 100644 index 000000000..fb19c1cba --- /dev/null +++ b/wiremock/mappings/oauth/authorization_code/invalid_state_error.json @@ -0,0 +1,17 @@ +{ + "mappings": [ + { + "scenarioName": "Invalid scope authorization error", + "request": { + "urlPathPattern": "/oauth/authorize.*", + "method": "GET" + }, + "response": { + "status": 302, + "headers": { + "Location": "http://localhost:8010/snowflake/oauth-redirect?code=123&state=invalidstate" + } + } + } + ] +} \ No newline at end of file diff --git a/wiremock/mappings/oauth/authorization_code/successful_flow.json b/wiremock/mappings/oauth/authorization_code/successful_flow.json new file mode 100644 index 000000000..f49b224b4 --- /dev/null +++ b/wiremock/mappings/oauth/authorization_code/successful_flow.json @@ -0,0 +1,77 @@ +{ + "mappings": [ + { + "scenarioName": "Successful OAuth authorization code flow", + "requiredScenarioState": "Started", + "newScenarioState": "Authorized", + "request": { + "urlPathPattern": "/oauth/authorize", + "queryParameters": { + "response_type": { + "equalTo": "code" + }, + "scope": { + "equalTo": "session:role:ANALYST" + }, + "code_challenge_method": { + "equalTo": "S256" + }, + "redirect_uri": { + "equalTo": "http://localhost:8009/snowflake/oauth-redirect" + }, + "code_challenge": { + "matches": ".*" + }, + "state": { + "matches": ".*" + }, + "client_id": { + "equalTo": "123" + } + }, + "method": "GET" + }, + "response": { + "status": 302, + "headers": { + "Location": "http://localhost:8009/snowflake/oauth-redirect?code=123&state=abc123" + } + } + }, + { + "scenarioName": "Successful OAuth authorization code flow", + "requiredScenarioState": "Authorized", + "newScenarioState": "Acquired access token", + "request": { + "urlPathPattern": "/oauth/token-request.*", + "method": "POST", + "headers": { + "Authorization": { + "contains": "Basic" + }, + "Content-Type": { + "contains": "application/x-www-form-urlencoded; charset=UTF-8" + } + }, + "bodyPatterns": [ + { + "contains": "grant_type=authorization_code&code=123&redirect_uri=http%3A%2F%2Flocalhost%3A8009%2Fsnowflake%2Foauth-redirect&code_verifier=" + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "access_token": "access-token-123", + "refresh_token": "123", + "token_type": "Bearer", + "username": "user", + "scope": "refresh_token session:role:ANALYST", + "expires_in": 600, + "refresh_token_expires_in": 86399, + "idpInitiated": false + } + } + } + ] +} diff --git a/wiremock/mappings/oauth/authorization_code/token_request_error.json b/wiremock/mappings/oauth/authorization_code/token_request_error.json new file mode 100644 index 000000000..792e1e363 --- /dev/null +++ b/wiremock/mappings/oauth/authorization_code/token_request_error.json @@ -0,0 +1,44 @@ +{ + "mappings": [ + { + "scenarioName": "OAuth token request error", + "requiredScenarioState": "Started", + "newScenarioState": "Authorized", + "request": { + "urlPathPattern": "/oauth/authorize.*", + "method": "GET" + }, + "response": { + "status": 302, + "headers": { + "Location": "http://localhost:8003/snowflake/oauth-redirect?code=123&state=abc123" + } + } + }, + { + "scenarioName": "OAuth token request error", + "requiredScenarioState": "Authorized", + "newScenarioState": "Token request error", + "request": { + "urlPathPattern": "/oauth/token-request.*", + "method": "POST", + "headers": { + "Authorization": { + "contains": "Basic" + }, + "Content-Type": { + "contains": "application/x-www-form-urlencoded; charset=UTF-8" + } + }, + "bodyPatterns": [ + { + "contains": "grant_type=authorization_code&code=123&redirect_uri=http%3A%2F%2Flocalhost%3A8003%2Fsnowflake%2Foauth-redirect&code_verifier=" + } + ] + }, + "response": { + "status": 400 + } + } + ] +} \ No newline at end of file diff --git a/wiremock/mappings/oauth/client_credentials/successful_flow.json b/wiremock/mappings/oauth/client_credentials/successful_flow.json new file mode 100644 index 000000000..10ed78c84 --- /dev/null +++ b/wiremock/mappings/oauth/client_credentials/successful_flow.json @@ -0,0 +1,39 @@ +{ + "mappings": [ + { + "scenarioName": "Successful OAuth client credentials flow", + "requiredScenarioState": "Started", + "newScenarioState": "Acquired access token", + "request": { + "urlPathPattern": "/oauth/token-request.*", + "method": "POST", + "headers": { + "Authorization": { + "contains": "Basic" + }, + "Content-Type": { + "contains": "application/x-www-form-urlencoded; charset=UTF-8" + } + }, + "bodyPatterns": [ + { + "contains": "grant_type=client_credentials&scope=session%3Arole%3AANALYST" + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "access_token": "access-token-123", + "refresh_token": "123", + "token_type": "Bearer", + "username": "user", + "scope": "refresh_token session:role:ANALYST", + "expires_in": 600, + "refresh_token_expires_in": 86399, + "idpInitiated": false + } + } + } + ] +} diff --git a/wiremock/mappings/oauth/client_credentials/token_request_error.json b/wiremock/mappings/oauth/client_credentials/token_request_error.json new file mode 100644 index 000000000..72eb97481 --- /dev/null +++ b/wiremock/mappings/oauth/client_credentials/token_request_error.json @@ -0,0 +1,29 @@ +{ + "mappings": [ + { + "scenarioName": "OAuth client credentials flow with token request error", + "requiredScenarioState": "Started", + "newScenarioState": "Acquired access token", + "request": { + "urlPathPattern": "/oauth/token-request.*", + "method": "POST", + "headers": { + "Authorization": { + "contains": "Basic" + }, + "Content-Type": { + "contains": "application/x-www-form-urlencoded; charset=UTF-8" + } + }, + "bodyPatterns": [ + { + "contains": "grant_type=client_credentials&scope=session%3Arole%3AANALYST" + } + ] + }, + "response": { + "status": 400 + } + } + ] +} \ No newline at end of file diff --git a/wiremock/mappings/oauth/token_caching/caching_refreshed_access_token_and_new_refresh_token.json b/wiremock/mappings/oauth/token_caching/caching_refreshed_access_token_and_new_refresh_token.json new file mode 100644 index 000000000..86fefd68c --- /dev/null +++ b/wiremock/mappings/oauth/token_caching/caching_refreshed_access_token_and_new_refresh_token.json @@ -0,0 +1,173 @@ +{ + "mappings": [ + { + "scenarioName": "Caching refreshed access token and new refresh token", + "requiredScenarioState": "Started", + "newScenarioState": "Removed expired access token", + "request": { + "urlPathPattern": "/session/v1/login-request.*", + "method": "POST", + "headers": { + "CLIENT_APP_ID": { + "equalTo": "MOCK_APP_ID" + }, + "CLIENT_APP_VERSION": { + "equalTo": "MOCK_APP_VERSION" + }, + "Authorization": { + "equalTo": "Basic" + }, + "accept": { + "equalTo": "application/json" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "data": { + "ACCOUNT_NAME": "MOCK_ACCOUNT_NAME", + "CLIENT_APP_ID": "MOCK_APP_ID", + "CLIENT_ENVIRONMENT": { + "tracing": "INFO", + "OCSP_MODE": "FAIL_OPEN" + }, + "CLIENT_APP_VERSION": "MOCK_APP_VERSION", + "TOKEN": "expired-access-token-123", + "LOGIN_NAME": "MOCK_USERNAME", + "AUTHENTICATOR": "OAUTH" + } + }, + "ignoreExtraElements": true + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "nextAction": "RETRY_LOGIN", + "authnMethod": "OAUTH", + "signInOptions": {} + }, + "code": "390318", + "message": "OAuth access token expired. [1172527951366]", + "success": false, + "headers": null + } + } + }, + { + "scenarioName": "Caching refreshed access token and new refresh token", + "requiredScenarioState": "Removed expired access token", + "newScenarioState": "Acquired new access token and new refresh token", + "request": { + "urlPathPattern": "/oauth/token-request.*", + "method": "POST", + "headers": { + "Authorization": { + "contains": "Basic" + }, + "Content-Type": { + "contains": "application/x-www-form-urlencoded; charset=UTF-8" + } + }, + "bodyPatterns": [ + { + "contains": "grant_type=refresh_token&refresh_token=some-refresh-token-123&scope=session%3Arole%3AANALYST" + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "access_token" : "new-refreshed-access-token-123", + "refresh_token": "new-refresh-token-123", + "token_type" : "Bearer", + "expires_in" : 599, + "idpInitiated" : false + } + } + }, + { + "scenarioName": "Caching refreshed access token and new refresh token", + "requiredScenarioState": "Acquired new access token and new refresh token", + "newScenarioState": "Established session with newly acquired access token and cache both tokens", + "request": { + "urlPathPattern": "/session/v1/login-request.*", + "method": "POST", + "headers": { + "CLIENT_APP_ID": { + "equalTo": "MOCK_APP_ID" + }, + "CLIENT_APP_VERSION": { + "equalTo": "MOCK_APP_VERSION" + }, + "Authorization": { + "equalTo": "Basic" + }, + "accept": { + "equalTo": "application/json" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "data": { + "ACCOUNT_NAME": "MOCK_ACCOUNT_NAME", + "CLIENT_APP_ID": "MOCK_APP_ID", + "CLIENT_ENVIRONMENT": { + "tracing": "INFO", + "OCSP_MODE": "FAIL_OPEN" + }, + "CLIENT_APP_VERSION": "MOCK_APP_VERSION", + "TOKEN": "new-refreshed-access-token-123", + "LOGIN_NAME": "MOCK_USERNAME", + "AUTHENTICATOR": "OAUTH" + } + }, + "ignoreExtraElements": true + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "masterToken": "master token", + "token": "session token", + "validityInSeconds": 3600, + "masterValidityInSeconds": 14400, + "displayUserName": "OAUTH_TEST_AUTH_CODE", + "serverVersion": "8.48.0 b2024121104444034239f05", + "firstLogin": false, + "remMeToken": null, + "remMeValidityInSeconds": 0, + "healthCheckInterval": 45, + "newClientForUpgrade": "3.12.3", + "sessionId": 1172562260498, + "parameters": [ + { + "name": "CLIENT_PREFETCH_THREADS", + "value": 4 + } + ], + "sessionInfo": { + "databaseName": "TEST_DHEYMAN", + "schemaName": "TEST_JDBC", + "warehouseName": "TEST_XSMALL", + "roleName": "ANALYST" + }, + "idToken": null, + "idTokenValidityInSeconds": 0, + "responseData": null, + "mfaToken": null, + "mfaTokenValidityInSeconds": 0 + }, + "code": null, + "message": null, + "success": true + } + } + } + ] +} diff --git a/wiremock/mappings/oauth/token_caching/caching_tokens_after_connecting.json b/wiremock/mappings/oauth/token_caching/caching_tokens_after_connecting.json new file mode 100644 index 000000000..20a04d615 --- /dev/null +++ b/wiremock/mappings/oauth/token_caching/caching_tokens_after_connecting.json @@ -0,0 +1,119 @@ +{ + "mappings": [ + { + "scenarioName": "Successful writing access token and refresh token to empty cache", + "requiredScenarioState": "Started", + "newScenarioState": "Acquired tokens", + "request": { + "urlPathPattern": "/oauth/token-request.*", + "method": "POST", + "headers": { + "Authorization": { + "contains": "Basic" + }, + "Content-Type": { + "contains": "application/x-www-form-urlencoded; charset=UTF-8" + } + }, + "bodyPatterns": [ + { + "contains": "grant_type=client_credentials&scope=session%3Arole%3AANALYST" + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "access_token": "access-token-123", + "refresh_token": "refresh-token-123", + "token_type": "Bearer", + "username": "user", + "scope": "refresh_token session:role:ANALYST", + "expires_in": 600, + "refresh_token_expires_in": 86399, + "idpInitiated": false + } } + }, + { + "scenarioName": "Successful writing access token and refresh token to empty cache", + "requiredScenarioState": "Acquired tokens", + "newScenarioState": "Cached tokens", + "request": { + "urlPathPattern": "/session/v1/login-request.*", + "method": "POST", + "headers": { + "CLIENT_APP_ID": { + "equalTo": "MOCK_APP_ID" + }, + "CLIENT_APP_VERSION": { + "equalTo": "MOCK_APP_VERSION" + }, + "Authorization": { + "equalTo": "Basic" + }, + "accept": { + "equalTo": "application/json" + } + }, + "bodyPatterns": [ + { + "equalToJson" : { + "data": { + "ACCOUNT_NAME": "MOCK_ACCOUNT_NAME", + "CLIENT_APP_ID": "MOCK_APP_ID", + "CLIENT_ENVIRONMENT": { + "tracing": "INFO", + "OCSP_MODE": "FAIL_OPEN" + }, + "CLIENT_APP_VERSION": "MOCK_APP_VERSION", + "TOKEN": "access-token-123", + "LOGIN_NAME": "MOCK_USERNAME", + "AUTHENTICATOR": "OAUTH" + } + }, + "ignoreExtraElements" : true + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "masterToken": "master token", + "token": "session token", + "validityInSeconds": 3600, + "masterValidityInSeconds": 14400, + "displayUserName": "OAUTH_TEST_AUTH_CODE", + "serverVersion": "8.48.0 b2024121104444034239f05", + "firstLogin": false, + "remMeToken": null, + "remMeValidityInSeconds": 0, + "healthCheckInterval": 45, + "newClientForUpgrade": "3.12.3", + "sessionId": 1172562260498, + "parameters": [ + { + "name": "CLIENT_PREFETCH_THREADS", + "value": 4 + } + ], + "sessionInfo": { + "databaseName": "TEST_DHEYMAN", + "schemaName": "TEST_JDBC", + "warehouseName": "TEST_XSMALL", + "roleName": "ANALYST" + }, + "idToken": null, + "idTokenValidityInSeconds": 0, + "responseData": null, + "mfaToken": null, + "mfaTokenValidityInSeconds": 0 + }, + "code": null, + "message": null, + "success": true + } + } + } + ] +} diff --git a/wiremock/mappings/oauth/token_caching/refreshing_expired_access_token.json b/wiremock/mappings/oauth/token_caching/refreshing_expired_access_token.json new file mode 100644 index 000000000..6fe251ed1 --- /dev/null +++ b/wiremock/mappings/oauth/token_caching/refreshing_expired_access_token.json @@ -0,0 +1,172 @@ +{ + "mappings": [ + { + "scenarioName": "Refreshing expired access token from cache", + "requiredScenarioState": "Started", + "newScenarioState": "Removed expired access token", + "request": { + "urlPathPattern": "/session/v1/login-request.*", + "method": "POST", + "headers": { + "CLIENT_APP_ID": { + "equalTo": "MOCK_APP_ID" + }, + "CLIENT_APP_VERSION": { + "equalTo": "MOCK_APP_VERSION" + }, + "Authorization": { + "equalTo": "Basic" + }, + "accept": { + "equalTo": "application/json" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "data": { + "ACCOUNT_NAME": "MOCK_ACCOUNT_NAME", + "CLIENT_APP_ID": "MOCK_APP_ID", + "CLIENT_ENVIRONMENT": { + "tracing": "INFO", + "OCSP_MODE": "FAIL_OPEN" + }, + "CLIENT_APP_VERSION": "MOCK_APP_VERSION", + "TOKEN": "expired-access-token-123", + "LOGIN_NAME": "MOCK_USERNAME", + "AUTHENTICATOR": "OAUTH" + } + }, + "ignoreExtraElements": true + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "nextAction": "RETRY_LOGIN", + "authnMethod": "OAUTH", + "signInOptions": {} + }, + "code": "390318", + "message": "OAuth access token expired. [1172527951366]", + "success": false, + "headers": null + } + } + }, + { + "scenarioName": "Refreshing expired access token from cache", + "requiredScenarioState": "Removed expired access token", + "newScenarioState": "Acquired new access token using refresh token", + "request": { + "urlPathPattern": "/oauth/token-request.*", + "method": "POST", + "headers": { + "Authorization": { + "contains": "Basic" + }, + "Content-Type": { + "contains": "application/x-www-form-urlencoded; charset=UTF-8" + } + }, + "bodyPatterns": [ + { + "contains": "grant_type=refresh_token&refresh_token=some-refresh-token-123&scope=session%3Arole%3AANALYST" + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "access_token" : "new-refreshed-access-token-123", + "token_type" : "Bearer", + "expires_in" : 599, + "idpInitiated" : false + } + } + }, + { + "scenarioName": "Refreshing expired access token from cache", + "requiredScenarioState": "Acquired new access token using refresh token", + "newScenarioState": "Established session with newly acquired access token", + "request": { + "urlPathPattern": "/session/v1/login-request.*", + "method": "POST", + "headers": { + "CLIENT_APP_ID": { + "equalTo": "MOCK_APP_ID" + }, + "CLIENT_APP_VERSION": { + "equalTo": "MOCK_APP_VERSION" + }, + "Authorization": { + "equalTo": "Basic" + }, + "accept": { + "equalTo": "application/json" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "data": { + "ACCOUNT_NAME": "MOCK_ACCOUNT_NAME", + "CLIENT_APP_ID": "MOCK_APP_ID", + "CLIENT_ENVIRONMENT": { + "tracing": "INFO", + "OCSP_MODE": "FAIL_OPEN" + }, + "CLIENT_APP_VERSION": "MOCK_APP_VERSION", + "TOKEN": "new-refreshed-access-token-123", + "LOGIN_NAME": "MOCK_USERNAME", + "AUTHENTICATOR": "OAUTH" + } + }, + "ignoreExtraElements": true + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "masterToken": "master token", + "token": "session token", + "validityInSeconds": 3600, + "masterValidityInSeconds": 14400, + "displayUserName": "OAUTH_TEST_AUTH_CODE", + "serverVersion": "8.48.0 b2024121104444034239f05", + "firstLogin": false, + "remMeToken": null, + "remMeValidityInSeconds": 0, + "healthCheckInterval": 45, + "newClientForUpgrade": "3.12.3", + "sessionId": 1172562260498, + "parameters": [ + { + "name": "CLIENT_PREFETCH_THREADS", + "value": 4 + } + ], + "sessionInfo": { + "databaseName": "TEST_DHEYMAN", + "schemaName": "TEST_JDBC", + "warehouseName": "TEST_XSMALL", + "roleName": "ANALYST" + }, + "idToken": null, + "idTokenValidityInSeconds": 0, + "responseData": null, + "mfaToken": null, + "mfaTokenValidityInSeconds": 0 + }, + "code": null, + "message": null, + "success": true + } + } + } + ] +} diff --git a/wiremock/mappings/oauth/token_caching/restarting_full_flow_on_expiration_and_no_refresh_token.json b/wiremock/mappings/oauth/token_caching/restarting_full_flow_on_expiration_and_no_refresh_token.json new file mode 100644 index 000000000..45599a369 --- /dev/null +++ b/wiremock/mappings/oauth/token_caching/restarting_full_flow_on_expiration_and_no_refresh_token.json @@ -0,0 +1,174 @@ +{ + "mappings": [ + { + "scenarioName": "Restarting full flow on access token expiration but no refresh token", + "requiredScenarioState": "Started", + "newScenarioState": "Removed expired access token", + "request": { + "urlPathPattern": "/session/v1/login-request.*", + "method": "POST", + "headers": { + "CLIENT_APP_ID": { + "equalTo": "MOCK_APP_ID" + }, + "CLIENT_APP_VERSION": { + "equalTo": "MOCK_APP_VERSION" + }, + "Authorization": { + "equalTo": "Basic" + }, + "accept": { + "equalTo": "application/json" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "data": { + "ACCOUNT_NAME": "MOCK_ACCOUNT_NAME", + "CLIENT_APP_ID": "MOCK_APP_ID", + "CLIENT_ENVIRONMENT": { + "tracing": "INFO", + "OCSP_MODE": "FAIL_OPEN" + }, + "CLIENT_APP_VERSION": "MOCK_APP_VERSION", + "TOKEN": "expired-access-token-123", + "LOGIN_NAME": "MOCK_USERNAME", + "AUTHENTICATOR": "OAUTH" + } + }, + "ignoreExtraElements": true + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "nextAction": "RETRY_LOGIN", + "authnMethod": "OAUTH", + "signInOptions": {} + }, + "code": "390318", + "message": "OAuth access token expired. [1172527951366]", + "success": false, + "headers": null + } + } + }, + { + "scenarioName": "Restarting full flow on access token expiration but no refresh token", + "requiredScenarioState": "Removed expired access token", + "newScenarioState": "Acquired new access token via Client Credentials flow (no refresh token in cache)", + "request": { + "urlPathPattern": "/oauth/token-request.*", + "method": "POST", + "headers": { + "Authorization": { + "contains": "Basic" + }, + "Content-Type": { + "contains": "application/x-www-form-urlencoded; charset=UTF-8" + } + }, + "bodyPatterns": [ + { + "contains": "grant_type=client_credentials&scope=session%3Arole%3AANALYST" + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "access_token": "newly-obtained-access-token-123", + "token_type": "Bearer", + "username": "user", + "scope": "session:role:ANALYST", + "expires_in": 600, + "idpInitiated": false + } + } + }, + { + "scenarioName": "Restarting full flow on access token expiration but no refresh token", + "requiredScenarioState": "Acquired new access token via Client Credentials flow (no refresh token in cache)", + "newScenarioState": "Established session with newly acquired access token", + "request": { + "urlPathPattern": "/session/v1/login-request.*", + "method": "POST", + "headers": { + "CLIENT_APP_ID": { + "equalTo": "MOCK_APP_ID" + }, + "CLIENT_APP_VERSION": { + "equalTo": "MOCK_APP_VERSION" + }, + "Authorization": { + "equalTo": "Basic" + }, + "accept": { + "equalTo": "application/json" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "data": { + "ACCOUNT_NAME": "MOCK_ACCOUNT_NAME", + "CLIENT_APP_ID": "MOCK_APP_ID", + "CLIENT_ENVIRONMENT": { + "tracing": "INFO", + "OCSP_MODE": "FAIL_OPEN" + }, + "CLIENT_APP_VERSION": "MOCK_APP_VERSION", + "TOKEN": "newly-obtained-access-token-123", + "LOGIN_NAME": "MOCK_USERNAME", + "AUTHENTICATOR": "OAUTH" + } + }, + "ignoreExtraElements": true + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "masterToken": "master token", + "token": "session token", + "validityInSeconds": 3600, + "masterValidityInSeconds": 14400, + "displayUserName": "OAUTH_TEST_AUTH_CODE", + "serverVersion": "8.48.0 b2024121104444034239f05", + "firstLogin": false, + "remMeToken": null, + "remMeValidityInSeconds": 0, + "healthCheckInterval": 45, + "newClientForUpgrade": "3.12.3", + "sessionId": 1172562260498, + "parameters": [ + { + "name": "CLIENT_PREFETCH_THREADS", + "value": 4 + } + ], + "sessionInfo": { + "databaseName": "TEST_DHEYMAN", + "schemaName": "TEST_JDBC", + "warehouseName": "TEST_XSMALL", + "roleName": "ANALYST" + }, + "idToken": null, + "idTokenValidityInSeconds": 0, + "responseData": null, + "mfaToken": null, + "mfaTokenValidityInSeconds": 0 + }, + "code": null, + "message": null, + "success": true + } + } + } + ] +} diff --git a/wiremock/mappings/oauth/token_caching/restarting_full_flow_on_refresh_token_error.json b/wiremock/mappings/oauth/token_caching/restarting_full_flow_on_refresh_token_error.json new file mode 100644 index 000000000..f03c0ffe7 --- /dev/null +++ b/wiremock/mappings/oauth/token_caching/restarting_full_flow_on_refresh_token_error.json @@ -0,0 +1,204 @@ +{ + "mappings": [ + { + "scenarioName": "Restarting full flow on access token refreshing error", + "requiredScenarioState": "Started", + "newScenarioState": "Removed expired access token", + "request": { + "urlPathPattern": "/session/v1/login-request.*", + "method": "POST", + "headers": { + "CLIENT_APP_ID": { + "equalTo": "MOCK_APP_ID" + }, + "CLIENT_APP_VERSION": { + "equalTo": "MOCK_APP_VERSION" + }, + "Authorization": { + "equalTo": "Basic" + }, + "accept": { + "equalTo": "application/json" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "data": { + "ACCOUNT_NAME": "MOCK_ACCOUNT_NAME", + "CLIENT_APP_ID": "MOCK_APP_ID", + "CLIENT_ENVIRONMENT": { + "tracing": "INFO", + "OCSP_MODE": "FAIL_OPEN" + }, + "CLIENT_APP_VERSION": "MOCK_APP_VERSION", + "TOKEN": "expired-access-token-123", + "LOGIN_NAME": "MOCK_USERNAME", + "AUTHENTICATOR": "OAUTH" + } + }, + "ignoreExtraElements": true + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "nextAction": "RETRY_LOGIN", + "authnMethod": "OAUTH", + "signInOptions": {} + }, + "code": "390318", + "message": "OAuth access token expired. [1172527951366]", + "success": false, + "headers": null + } + } + }, + { + "scenarioName": "Restarting full flow on access token refreshing error", + "requiredScenarioState": "Removed expired access token", + "newScenarioState": "Refreshing token error", + "request": { + "urlPathPattern": "/oauth/token-request.*", + "method": "POST", + "headers": { + "Authorization": { + "contains": "Basic" + }, + "Content-Type": { + "contains": "application/x-www-form-urlencoded; charset=UTF-8" + } + }, + "bodyPatterns": [ + { + "contains": "grant_type=refresh_token&refresh_token=some-refresh-token-123&scope=session%3Arole%3AANALYST" + } + ] + }, + "response": { + "status": 400, + "jsonBody": { + "error": "Invalid refresh token" + } + } + }, + { + "scenarioName": "Restarting full flow on access token refreshing error", + "requiredScenarioState": "Refreshing token error", + "newScenarioState": "Acquired new access token via Client Credentials flow", + "request": { + "urlPathPattern": "/oauth/token-request.*", + "method": "POST", + "headers": { + "Authorization": { + "contains": "Basic" + }, + "Content-Type": { + "contains": "application/x-www-form-urlencoded; charset=UTF-8" + } + }, + "bodyPatterns": [ + { + "contains": "grant_type=client_credentials&scope=session%3Arole%3AANALYST" + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "access_token": "newly-obtained-access-token-123", + "refresh_token": "newly-obtained-refresh-token", + "token_type": "Bearer", + "username": "user", + "scope": "refresh_token session:role:ANALYST", + "expires_in": 600, + "refresh_token_expires_in": 86399, + "idpInitiated": false + } + } + }, + { + "scenarioName": "Restarting full flow on access token refreshing error", + "requiredScenarioState": "Acquired new access token via Client Credentials flow", + "newScenarioState": "Established session with newly acquired access token", + "request": { + "urlPathPattern": "/session/v1/login-request.*", + "method": "POST", + "headers": { + "CLIENT_APP_ID": { + "equalTo": "MOCK_APP_ID" + }, + "CLIENT_APP_VERSION": { + "equalTo": "MOCK_APP_VERSION" + }, + "Authorization": { + "equalTo": "Basic" + }, + "accept": { + "equalTo": "application/json" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "data": { + "ACCOUNT_NAME": "MOCK_ACCOUNT_NAME", + "CLIENT_APP_ID": "MOCK_APP_ID", + "CLIENT_ENVIRONMENT": { + "tracing": "INFO", + "OCSP_MODE": "FAIL_OPEN" + }, + "CLIENT_APP_VERSION": "MOCK_APP_VERSION", + "TOKEN": "newly-obtained-access-token-123", + "LOGIN_NAME": "MOCK_USERNAME", + "AUTHENTICATOR": "OAUTH" + } + }, + "ignoreExtraElements": true + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "masterToken": "master token", + "token": "session token", + "validityInSeconds": 3600, + "masterValidityInSeconds": 14400, + "displayUserName": "OAUTH_TEST_AUTH_CODE", + "serverVersion": "8.48.0 b2024121104444034239f05", + "firstLogin": false, + "remMeToken": null, + "remMeValidityInSeconds": 0, + "healthCheckInterval": 45, + "newClientForUpgrade": "3.12.3", + "sessionId": 1172562260498, + "parameters": [ + { + "name": "CLIENT_PREFETCH_THREADS", + "value": 4 + } + ], + "sessionInfo": { + "databaseName": "TEST_DHEYMAN", + "schemaName": "TEST_JDBC", + "warehouseName": "TEST_XSMALL", + "roleName": "ANALYST" + }, + "idToken": null, + "idTokenValidityInSeconds": 0, + "responseData": null, + "mfaToken": null, + "mfaTokenValidityInSeconds": 0 + }, + "code": null, + "message": null, + "success": true + } + } + } + ] +} diff --git a/wiremock/mappings/oauth/token_caching/reusing_cached_access_token_to_authenticate.json b/wiremock/mappings/oauth/token_caching/reusing_cached_access_token_to_authenticate.json new file mode 100644 index 000000000..9775f0148 --- /dev/null +++ b/wiremock/mappings/oauth/token_caching/reusing_cached_access_token_to_authenticate.json @@ -0,0 +1,85 @@ +{ + "mappings": [ + { + "scenarioName": "Reusing access token and refresh token from cache", + "requiredScenarioState": "Started", + "newScenarioState": "Reusing cached tokens", + "request": { + "urlPathPattern": "/session/v1/login-request.*", + "method": "POST", + "headers": { + "CLIENT_APP_ID": { + "equalTo": "MOCK_APP_ID" + }, + "CLIENT_APP_VERSION": { + "equalTo": "MOCK_APP_VERSION" + }, + "Authorization": { + "equalTo": "Basic" + }, + "accept": { + "equalTo": "application/json" + } + }, + "bodyPatterns": [ + { + "equalToJson" : { + "data": { + "ACCOUNT_NAME": "MOCK_ACCOUNT_NAME", + "CLIENT_APP_ID": "MOCK_APP_ID", + "CLIENT_ENVIRONMENT": { + "tracing": "INFO", + "OCSP_MODE": "FAIL_OPEN" + }, + "CLIENT_APP_VERSION": "MOCK_APP_VERSION", + "TOKEN": "reused-access-token-123", + "LOGIN_NAME": "MOCK_USERNAME", + "AUTHENTICATOR": "OAUTH" + } + }, + "ignoreExtraElements" : true + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "masterToken": "master token", + "token": "session token", + "validityInSeconds": 3600, + "masterValidityInSeconds": 14400, + "displayUserName": "OAUTH_TEST_AUTH_CODE", + "serverVersion": "8.48.0 b2024121104444034239f05", + "firstLogin": false, + "remMeToken": null, + "remMeValidityInSeconds": 0, + "healthCheckInterval": 45, + "newClientForUpgrade": "3.12.3", + "sessionId": 1172562260498, + "parameters": [ + { + "name": "CLIENT_PREFETCH_THREADS", + "value": 4 + } + ], + "sessionInfo": { + "databaseName": "TEST_DHEYMAN", + "schemaName": "TEST_JDBC", + "warehouseName": "TEST_XSMALL", + "roleName": "ANALYST" + }, + "idToken": null, + "idTokenValidityInSeconds": 0, + "responseData": null, + "mfaToken": null, + "mfaTokenValidityInSeconds": 0 + }, + "code": null, + "message": null, + "success": true + } + } + } + ] +} diff --git a/wiremock/mappings/pat/invalid_pat_token.json b/wiremock/mappings/pat/invalid_pat_token.json new file mode 100644 index 000000000..69a75baeb --- /dev/null +++ b/wiremock/mappings/pat/invalid_pat_token.json @@ -0,0 +1,48 @@ +{ + "mappings": [ + { + "scenarioName": "Invalid PAT authentication flow", + "requiredScenarioState": "Started", + "newScenarioState": "Authenticated", + "request": { + "urlPathPattern": "/session/v1/login-request.*", + "method": "POST", + "headers": { + "accept": { + "equalTo": "application/json" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "data": { + "ACCOUNT_NAME": "MOCK_ACCOUNT_NAME", + "TOKEN": "INVALID_TOKEN", + "LOGIN_NAME": "MOCK_USERNAME", + "AUTHENTICATOR": "PROGRAMMATIC_ACCESS_TOKEN" + } + }, + "ignoreExtraElements": true + } + ] + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "data": { + "nextAction": "RETRY_LOGIN", + "authnMethod": "PAT", + "signInOptions": {} + }, + "code": "394400", + "message": "Programmatic access token is invalid.", + "success": false, + "headers": null + } + } + } + ] +} diff --git a/wiremock/mappings/pat/successful_flow.json b/wiremock/mappings/pat/successful_flow.json new file mode 100644 index 000000000..f674c5a1d --- /dev/null +++ b/wiremock/mappings/pat/successful_flow.json @@ -0,0 +1,74 @@ +{ + "mappings": [ + { + "scenarioName": "Successful PAT authentication flow", + "requiredScenarioState": "Started", + "newScenarioState": "Authenticated", + "request": { + "urlPathPattern": "/session/v1/login-request.*", + "method": "POST", + "headers": { + "accept": { + "equalTo": "application/json" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "data": { + "ACCOUNT_NAME": "MOCK_ACCOUNT_NAME", + "TOKEN": "MOCK_TOKEN", + "LOGIN_NAME": "MOCK_USERNAME", + "AUTHENTICATOR": "PROGRAMMATIC_ACCESS_TOKEN" + } + }, + "ignoreExtraElements": true + } + ] + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "data": { + "masterToken": "master token", + "token": "session token", + "validityInSeconds": 3600, + "masterValidityInSeconds": 14400, + "displayUserName": "OAUTH_TEST_AUTH_CODE", + "serverVersion": "8.48.0 b2024121104444034239f05", + "firstLogin": false, + "remMeToken": null, + "remMeValidityInSeconds": 0, + "healthCheckInterval": 45, + "newClientForUpgrade": "3.12.3", + "sessionId": 1172562260498, + "parameters": [ + { + "name": "CLIENT_PREFETCH_THREADS", + "value": 4 + } + ], + "sessionInfo": { + "databaseName": "TEST_DHEYMAN", + "schemaName": "TEST_JDBC", + "warehouseName": "TEST_XSMALL", + "roleName": "ANALYST" + }, + "idToken": null, + "idTokenValidityInSeconds": 0, + "responseData": null, + "mfaToken": null, + "mfaTokenValidityInSeconds": 0 + }, + "code": null, + "message": null, + "success": true + } + } + } + ] +} +