diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index e78086018..c81d25e04 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -55,6 +55,7 @@ plugins.security.allow_default_init_securityindex: true plugins.security.authcz.admin_dn: - CN=kirk,OU=client,O=client,L=test, C=de +plugins.security.unsupported.restapi.allow_securityconfig_modification: true plugins.security.audit.type: internal_opensearch plugins.security.enable_snapshot_restore_privilege: true plugins.security.check_snapshot_restore_write_privileges: true diff --git a/idp-private-key.pem b/idp-private-key.pem new file mode 100644 index 000000000..7e5f47b7a --- /dev/null +++ b/idp-private-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+eflDQihJ4zoZ +urwXuY3Nk9jRA3ms5Ge6jYuvId9mPjJ9ezndOdiVWnTgdqfmYUFMRsQ53iJJmprp +GHctBIPWpaz5Ao4oeco0D87CcySLA94tp8SG5ja2vasVYiNsaN9er1oWVo8uQP82 +iLGF5jXOBDaN3iPub8W2w78oSfgV2Kw5fgmcEIxUGyf/Jr1a8X5TdbX1rS8cZ08D +crZ8DAQZ9ndLl6wJ6RKnp7dqtEys1BT2LXQVvGkFp7zrN74AnNl8+FUE269Wct3t +sPVkIKgGxURGj+ky6oHT9MbVp7vvE9+7dcwfQMDqbd5elOIFZsWad0wd4LgIFUGa +ATnaSyXrAgMBAAECggEAGiwOWPSFLqnfONvUrnnbhyzSdN3CYUQ9EIAUemrwFE9l +hqJg8AnbvaHhP1pudZRVcZKjscPY+D4bHP40meXt65d2Lfzw5ZTeeMrXQRarJcLR +S3qq4VJOoEttb5G9hk7eqlbzzO/12ogpySd9JQXdzMH9cP7d9ww6oYNCB9oqEW4o +QVjwF5RHuq9dB6FmhsNux9VbbP6gGVTXA7IpmNwFddW8JoDM2mj952POYjAwEy00 +VQb0dzVrfuEw8DmpWZAOfyDt5QYO+7SC+L4eHbhrZrxeCzXr2hEhiCa1Nn3ieAHD +fF5FaguRMJcfGI3UEQZAw0++0SYeWPnjnwM1wE/nyQKBgQDyZA+6D1f3H/0fg4tT +ZrpGydnRPqqvMF+GBCtfa7Gjf6NdFQ+j0NzUXGzbIGy+0pJKEfEk4hE/2xFjCzru +LYJ5hSz/GXZH6qn4H39f0byl9oT6DYKDmrcXt1jRM4ioCU4Q1c58e2MqsvttVCnw +dVDQuTMb1xm6okHqcm3Fn8rUBwKBgQDJK7wbgrQcWriFxKDITdLvAivDcYAQ/p/e +XDw+nVdwq0p9Kpe+N5Lt6kvlTVyrS1aWfa9ucLVKMSKCbkjJBhesJsZU3nZgLa65 +XT5+sP4640/gBoHWvauYBBjOy0iHHzhrIx+Kw0BXWzrYE0ol0aIWYZt1/I1LQtoB +E0+ojQLN/QKBgQDCtZhgiNTLwhmOSBgSffHizWC4klN/+SayvASvWQ5QXUa4jiOL +H0tVF42mFIzmWLaE45bHXwYmOm7kFfBXxZ0Kyu0TWrvGF35Dv+GM8ilNVBML3vBZ +kV3EolapbnE3MopQQb/mBSPq9+26rCIoc8TgdfTVR1v2rUKv9w2w86R13wKBgGh5 +GQikeUscZiW6NtGvcPMFCptGb37j7Tx6ZCMUbVuq6VVVcFat39VEz0N3SMAAsSgY +f6n4SH4ORGC+S3hyfIq/3FIo8gsCznGflhwPaQhGEq5CUt2lxN5+ii+i7LiXoyIo +rHHQ8rIrQ8UBR4mac/XxnN3KWcqTHkpesAjVqnY1AoGAXoc9kr4v3WP0AG6lUhjB +P77AIrpCokbvnXfsAj9coPBJjK16XY534jjmnem4lwvxad/+PDWfU2i3wlP64YCi +BJkN8s9vJyJqHDFJjQ18zl6rdlFt+43x+YCt/zHsqazdcm47XN9kbXrunDYpVh2E +PZWa3aHt74aNhNMshynDIXI= +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/idp-public-cert.pem b/idp-public-cert.pem new file mode 100644 index 000000000..0e5f8db49 --- /dev/null +++ b/idp-public-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDVjCCAj4CCQCgL9L+gaw8+jANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJV +UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEQ +MA4GA1UECgwHSmFua3lDbzEfMB0GA1UEAwwWVGVzdCBJZGVudGl0eSBQcm92aWRl +cjAeFw0yMjA3MTUxNjU1MjRaFw00MjA3MTAxNjU1MjRaMG0xCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRAw +DgYDVQQKDAdKYW5reUNvMR8wHQYDVQQDDBZUZXN0IElkZW50aXR5IFByb3ZpZGVy +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvnn5Q0IoSeM6Gbq8F7mN +zZPY0QN5rORnuo2LryHfZj4yfXs53TnYlVp04Han5mFBTEbEOd4iSZqa6Rh3LQSD +1qWs+QKOKHnKNA/OwnMkiwPeLafEhuY2tr2rFWIjbGjfXq9aFlaPLkD/NoixheY1 +zgQ2jd4j7m/FtsO/KEn4FdisOX4JnBCMVBsn/ya9WvF+U3W19a0vHGdPA3K2fAwE +GfZ3S5esCekSp6e3arRMrNQU9i10FbxpBae86ze+AJzZfPhVBNuvVnLd7bD1ZCCo +BsVERo/pMuqB0/TG1ae77xPfu3XMH0DA6m3eXpTiBWbFmndMHeC4CBVBmgE52ksl +6wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQADzGujWOBelV81BoxkS5vB+VGmEDAx +I67cZCZ7734vJsrf5c6QV45zz+NiOqyLkY2JEsHMh89Ns8n+1MsbIPn01tfjXFgL +arJLhLBRxFhqZr0H81E8DAzHwjAtx8Qmr/IQXcLPhJ0SMubIGC7EhCkYrphteTyd +2Rr5C9lCwF4Lb3xgoT2RsEO/IWDKb/CthcisQdDTw1XWLeAc+pJa76kOgDSkP93i +hHoZJMswOFU8KnLiXMaSxUZOXHLOYY7k4+xyh7dGqEkwKRYyY3TJ3mAULcJr5Ngz +UJvwmjmuEVCIgVNWqW45UsXJqkvdGFtUKj3UGfgyuvSV33daqXjkAims +-----END CERTIFICATE----- \ No newline at end of file diff --git a/package.json b/package.json index 41e345fbc..692778a1b 100644 --- a/package.json +++ b/package.json @@ -17,19 +17,20 @@ "lint:es": "node ../../scripts/eslint", "lint:style": "node ../../scripts/stylelint", "lint": "yarn run lint:es && yarn run lint:style", + "pretest:jest_server": "node ./test/jest_integration/runIdpServer.js &", "test:jest_server": "node ./test/run_jest_tests.js --config ./test/jest.config.server.js", "test:jest_ui": "node ./test/run_jest_tests.js --config ./test/jest.config.ui.js" }, "devDependencies": { "@elastic/eslint-import-resolver-kibana": "link:../../packages/osd-eslint-import-resolver-opensearch-dashboards", - "typescript": "4.0.2", - "gulp-rename": "2.0.0", "@testing-library/react-hooks": "^7.0.2", - "@types/hapi__wreck": "^15.0.1" + "@types/hapi__wreck": "^15.0.1", + "selenium-webdriver": "^4.0.0-alpha.7", + "saml-idp": "^1.2.1" }, "dependencies": { - "@hapi/wreck": "^17.1.0", "@hapi/cryptiles": "5.0.0", + "@hapi/wreck": "^17.1.0", "html-entities": "1.3.1" } } diff --git a/public/apps/account/account-nav-button.tsx b/public/apps/account/account-nav-button.tsx index 1100e9f31..7bd0e578b 100644 --- a/public/apps/account/account-nav-button.tsx +++ b/public/apps/account/account-nav-button.tsx @@ -93,7 +93,11 @@ export function AccountNavButton(props: { {resolveTenantName(props.tenant || '', username)}} + label={ + + {resolveTenantName(props.tenant || '', username)} + + } /> @@ -140,7 +144,7 @@ export function AccountNavButton(props: { ); return ( - + + tenant1 @@ -114,4 +117,4 @@ exports[`Account navigation button renders 1`] = ` -`; +`; \ No newline at end of file diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index 79454272c..4d2bb8c27 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -14,21 +14,18 @@ */ import { schema } from '@osd/config-schema'; -import { - IRouter, - SessionStorageFactory, - OpenSearchDashboardsRequest, -} from '../../../../../../src/core/server'; +import { IRouter, SessionStorageFactory } from '../../../../../../src/core/server'; import { SecuritySessionCookie } from '../../../session/security_cookie'; import { SecurityPluginConfigType } from '../../..'; import { SecurityClient } from '../../../backend/opensearch_security_client'; +import { API_AUTH_LOGOUT } from '../../../../common'; import { CoreSetup } from '../../../../../../src/core/server'; import { validateNextUrl } from '../../../utils/next_url'; +import { AuthType } from '../../../../common/index'; export class SamlAuthRoutes { constructor( private readonly router: IRouter, - // @ts-ignore: unused variable private readonly config: SecurityPluginConfigType, private readonly sessionStorageFactory: SessionStorageFactory, private readonly securityClient: SecurityClient, @@ -38,7 +35,7 @@ export class SamlAuthRoutes { public setupRoutes() { this.router.get( { - path: `/auth/saml/login`, + path: '/auth/saml/login', validate: { query: schema.object({ nextUrl: schema.maybe( @@ -86,7 +83,8 @@ export class SamlAuthRoutes { this.router.post( { - path: `/_opendistro/_security/saml/acs`, + // need to change to /_opendistro to execute the tests. + path: '/_opendistro/_security/saml/acs', validate: { body: schema.any(), }, @@ -143,11 +141,12 @@ export class SamlAuthRoutes { credentials: { authHeaderValue: credentials.authorization, }, - authType: 'saml', // TODO: create constant + authType: AuthType.SAML, expiryTime, }; this.sessionStorageFactory.asScoped(request).set(cookie); if (redirectHash) { + console.log('The server base path is : ' + this.coreSetup.http.basePath.serverBasePath); return response.redirected({ headers: { location: `${ @@ -174,7 +173,7 @@ export class SamlAuthRoutes { this.router.post( { - path: `/_opendistro/_security/saml/acs/idpinitiated`, + path: '/_plugins/_security/saml/acs/idpinitiated', validate: { body: schema.any(), }, @@ -183,7 +182,7 @@ export class SamlAuthRoutes { }, }, async (context, request, response) => { - const acsEndpoint = `${this.coreSetup.http.basePath.serverBasePath}/_opendistro/_security/saml/acs/idpinitiated`; + const acsEndpoint = `${this.coreSetup.http.basePath.serverBasePath}/_plugins/_security/saml/acs/idpinitiated`; try { const credentials = await this.securityClient.authToken( undefined, @@ -211,7 +210,7 @@ export class SamlAuthRoutes { credentials: { authHeaderValue: credentials.authorization, }, - authType: 'saml', // TODO: create constant + authType: AuthType.SAML, expiryTime, }; this.sessionStorageFactory.asScoped(request).set(cookie); @@ -229,8 +228,6 @@ export class SamlAuthRoutes { } ); - // captureUrlFragment is the first route that will be invoked in the SP initiated login. - // This route will execute the captureUrlFragment.js script. this.coreSetup.http.resources.register( { path: '/auth/saml/captureUrlFragment', @@ -261,7 +258,6 @@ export class SamlAuthRoutes { } ); - // This script will store the URL Hash in browser's local storage. this.coreSetup.http.resources.register( { path: '/auth/saml/captureUrlFragment.js', @@ -291,9 +287,6 @@ export class SamlAuthRoutes { } ); - // Once the User is authenticated via the '_opendistro/_security/saml/acs' route, - // the browser will be redirected to '/auth/saml/redirectUrlFragment' route, - // which will execute the redirectUrlFragment.js. this.coreSetup.http.resources.register( { path: '/auth/saml/redirectUrlFragment', @@ -319,8 +312,6 @@ export class SamlAuthRoutes { } ); - // This script will pop the Hash from local storage if it exists. - // And forward the browser to the next url. this.coreSetup.http.resources.register( { path: '/auth/saml/redirectUrlFragment.js', @@ -344,7 +335,7 @@ export class SamlAuthRoutes { this.router.get( { - path: `/auth/logout`, + path: API_AUTH_LOGOUT, validate: false, }, async (context, request, response) => { diff --git a/server/auth/types/saml/saml_auth.ts b/server/auth/types/saml/saml_auth.ts index 201e76c43..ee8762406 100644 --- a/server/auth/types/saml/saml_auth.ts +++ b/server/auth/types/saml/saml_auth.ts @@ -58,6 +58,7 @@ export class SamlAuthentication extends AuthenticationType { return escape(path); } + // Check if we can get the previous tenant information from the expired cookie. private redirectSAMlCapture = (request: OpenSearchDashboardsRequest, toolkit: AuthToolkit) => { const nextUrl = this.generateNextUrl(request); const clearOldVersionCookie = clearOldVersionCookieValue(this.config); @@ -97,6 +98,7 @@ export class SamlAuthentication extends AuthenticationType { }; } + // Can be improved to check if the token is expiring. async isValidCookie(cookie: SecuritySessionCookie): Promise { return ( cookie.authType === this.type && diff --git a/test/helper/cookie.ts b/test/helper/cookie.ts index 381891f9a..dcbea1489 100644 --- a/test/helper/cookie.ts +++ b/test/helper/cookie.ts @@ -20,7 +20,7 @@ import { AUTHORIZATION_HEADER_NAME } from '../constant'; export function extractAuthCookie(response: Response) { const setCookieHeaders = response.header['set-cookie'] as string[]; - let securityAuthCookie: string; + let securityAuthCookie: string | null = null; for (const setCookie of setCookieHeaders) { if (setCookie.startsWith('security_authentication=')) { securityAuthCookie = setCookie.split(';')[0]; diff --git a/test/jest_integration/runIdpServer.js b/test/jest_integration/runIdpServer.js new file mode 100644 index 000000000..ad29cccd5 --- /dev/null +++ b/test/jest_integration/runIdpServer.js @@ -0,0 +1,22 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +const { runServer } = require('saml-idp'); + +// Create certificate pair on the fly and pass it to runServer +runServer({ + acsUrl: 'http://localhost:5601/_opendistro/_security/saml/acs', + audience: 'https://localhost:9200', +}); diff --git a/test/jest_integration/saml_auth.test.ts b/test/jest_integration/saml_auth.test.ts new file mode 100644 index 000000000..636d3ea23 --- /dev/null +++ b/test/jest_integration/saml_auth.test.ts @@ -0,0 +1,336 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import * as osdTestServer from '../../../../src/core/test_helpers/osd_server'; +import { Root } from '../../../../src/core/server/root'; +import { resolve } from 'path'; +import { describe, expect, it, beforeAll, afterAll } from '@jest/globals'; +import { + ADMIN_CREDENTIALS, + OPENSEARCH_DASHBOARDS_SERVER_USER, + OPENSEARCH_DASHBOARDS_SERVER_PASSWORD, +} from '../constant'; +import wreck from '@hapi/wreck'; +import { Builder, By, until } from 'selenium-webdriver'; +import { Options } from 'selenium-webdriver/firefox'; + +describe('start OpenSearch Dashboards server', () => { + let root: Root; + let config; + + // XPath Constants + const userIconBtnXPath = '//button[@id="user-icon-btn"]'; + const signInBtnXPath = '//*[@id="btn-sign-in"]'; + const skipWelcomeBtnXPath = '//button[@data-test-subj="skipWelcomeScreen"]'; + const tenantNameLabelXPath = '//*[@id="tenantName"]'; + const pageTitleXPath = '//*[@id="osdOverviewPageHeader__title"]'; + + beforeAll(async () => { + root = osdTestServer.createRootWithSettings( + { + plugins: { + scanDirs: [resolve(__dirname, '../..')], + }, + server: { + host: 'localhost', + port: 5601, + xsrf: { + whitelist: [ + '/_opendistro/_security/saml/acs/idpinitiated', + '/_opendistro/_security/saml/acs', + '/_opendistro/_security/saml/logout', + ], + }, + }, + opensearch: { + hosts: ['https://localhost:9200'], + ignoreVersionMismatch: true, + ssl: { verificationMode: 'none' }, + username: OPENSEARCH_DASHBOARDS_SERVER_USER, + password: OPENSEARCH_DASHBOARDS_SERVER_PASSWORD, + requestHeadersWhitelist: ['authorization', 'securitytenant'], + }, + opensearch_security: { + auth: { + anonymous_auth_enabled: false, + type: 'saml', + }, + multitenancy: { + enabled: true, + tenants: { + enable_global: true, + enable_private: true, + preferred: ['Private', 'Global'], + }, + }, + }, + }, + { + // to make ignoreVersionMismatch setting work + // can be removed when we have corresponding ES version + dev: true, + } + ); + + console.log('Starting OpenSearchDashboards server..'); + await root.setup(); + await root.start(); + + console.log('Starting the Selenium Web Driver'); + await wreck.patch('https://localhost:9200/_plugins/_security/api/rolesmapping/all_access', { + payload: [ + { + op: 'add', + path: '/users', + value: ['saml.jackson@example.com'], + }, + ], + rejectUnauthorized: false, + headers: { + 'Content-Type': 'application/json', + authorization: ADMIN_CREDENTIALS, + }, + }); + console.log('Starting to Download Flights Sample Data'); + await wreck.post('http://localhost:5601/api/sample_data/flights', { + payload: {}, + rejectUnauthorized: false, + headers: { + 'Content-Type': 'application/json', + authorization: ADMIN_CREDENTIALS, + security_tenant: 'global', + }, + }); + console.log('Downloaded Sample Data'); + const getConfigResponse = await wreck.get( + 'https://localhost:9200/_plugins/_security/api/securityconfig', + { + rejectUnauthorized: false, + headers: { + authorization: ADMIN_CREDENTIALS, + }, + } + ); + const responseBody = (getConfigResponse.payload as Buffer).toString(); + config = JSON.parse(responseBody).config; + const samlConfig = { + http_enabled: true, + transport_enabled: false, + order: 5, + http_authenticator: { + challenge: true, + type: 'saml', + config: { + idp: { + metadata_url: 'http://localhost:7000/metadata', + entity_id: 'urn:example:idp', + }, + sp: { + entity_id: 'https://localhost:9200', + }, + kibana_url: 'http://localhost:5601', + exchange_key: '6aff3042-1327-4f3d-82f0-40a157ac4464', + }, + }, + authentication_backend: { + type: 'noop', + config: {}, + }, + }; + try { + config.dynamic!.authc!.saml_auth_domain = samlConfig; + config.dynamic!.authc!.basic_internal_auth_domain.http_authenticator.challenge = false; + config.dynamic!.http!.anonymous_auth_enabled = false; + await wreck.put('https://localhost:9200/_plugins/_security/api/securityconfig/config', { + payload: config, + rejectUnauthorized: false, + headers: { + 'Content-Type': 'application/json', + authorization: ADMIN_CREDENTIALS, + }, + }); + } catch (error) { + console.log('Got an error!!', error.stack); + } + console.log('The Config Response is : ' + JSON.stringify(config)); + }); + + afterAll(async () => { + console.log('Remove the Sample Data'); + await wreck + .delete('http://localhost:5601/api/sample_data/flights', { + rejectUnauthorized: false, + headers: { + 'Content-Type': 'application/json', + authorization: ADMIN_CREDENTIALS, + }, + }) + .then((value) => { + Promise.resolve(value); + }) + .catch((value) => { + Promise.resolve(value); + }); + console.log('Remove the Role Mapping'); + await wreck + .patch('https://localhost:9200/_plugins/_security/api/rolesmapping/all_access', { + payload: [ + { + op: 'remove', + path: '/users', + users: ['saml.jackson@example.com'], + }, + ], + rejectUnauthorized: false, + headers: { + 'Content-Type': 'application/json', + authorization: ADMIN_CREDENTIALS, + }, + }) + .then((value) => { + Promise.resolve(value); + }) + .catch((value) => { + Promise.resolve(value); + }); + console.log('Remove the Security Config'); + await wreck + .patch('https://localhost:9200/_plugins/_security/api/securityconfig', { + payload: [ + { + op: 'remove', + path: '/config/dynamic/authc/saml_auth_domain', + }, + ], + rejectUnauthorized: false, + headers: { + 'Content-Type': 'application/json', + authorization: ADMIN_CREDENTIALS, + }, + }) + .then((value) => { + Promise.resolve(value); + }) + .catch((value) => { + Promise.resolve(value); + }); + // shutdown OpenSearchDashboards server + await root.shutdown(); + }); + + it('Login to app/opensearch_dashboards_overview#/ when SAML is enabled', async () => { + const driver = getDriver().build(); + await driver.get('http://localhost:5601/app/opensearch_dashboards_overview#/'); + await driver.findElement(By.id('btn-sign-in')).click(); + await driver.wait(until.elementsLocated(By.xpath(pageTitleXPath)), 10000); + + const cookie = await driver.manage().getCookies(); + expect(cookie.length).toEqual(2); + await driver.manage().deleteAllCookies(); + await driver.quit(); + }); + + it('Login to app/dev_tools#/console when SAML is enabled', async () => { + const driver = getDriver().build(); + await driver.get('http://localhost:5601/app/dev_tools#/console'); + await driver.findElement(By.id('btn-sign-in')).click(); + + await driver.wait( + until.elementsLocated( + By.xpath('/html/body/div[1]/div/div/div/div[2]/div/main/div[1]/span/button/span') + ), + 10000 + ); + + const cookie = await driver.manage().getCookies(); + expect(cookie.length).toEqual(2); + await driver.manage().deleteAllCookies(); + await driver.quit(); + }); + + it('Login to Dashboard with Hash', async () => { + const urlWithHash = `http://localhost:5601/app/dashboards#/view/7adfa750-4c81-11e8-b3d7-01146121b73d?_g=(filters:!(),refreshInterval:(pause:!f,value:900000),time:(from:now-24h,to:now))&_a=(description:'Analyze%20mock%20flight%20data%20for%20OpenSearch-Air,%20Logstash%20Airways,%20OpenSearch%20Dashboards%20Airlines%20and%20BeatsWest',filters:!(),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),query:(language:kuery,query:''),timeRestore:!t,title:'%5BFlights%5D%20Global%20Flight%20Dashboard',viewMode:view)`; + const driver = getDriver().build(); + await driver.manage().deleteAllCookies(); + await driver.get(urlWithHash); + await driver.wait(until.elementsLocated(By.xpath('/html/body/nav/div/div[1]/a/i/span')), 60000); + await driver.findElement(By.xpath(signInBtnXPath)).click(); + await driver.wait( + until.elementsLocated(By.xpath('/html/body/div[1]/div/header/div/div[2]')), + 20000 + ); + const windowHash = await driver.getCurrentUrl(); + expect(windowHash).toEqual(urlWithHash); + const cookie = await driver.manage().getCookies(); + expect(cookie.length).toEqual(2); + await driver.manage().deleteAllCookies(); + await driver.quit(); + }); + + it('Tenant information retained after logout', async () => { + const driver = getDriver().build(); + + await driver.get('http://localhost:5601/app/opensearch_dashboards_overview#/'); + + await driver.findElement(By.xpath(signInBtnXPath)).click(); + + await driver.wait(until.elementsLocated(By.xpath(pageTitleXPath)), 10000); + + await driver.wait( + until.elementsLocated(By.xpath('//button[@aria-label="Closes this modal window"]')), + 10000 + ); + + // Select Global Tenant Radio Button + const radio = await driver.findElement(By.xpath('//input[@id="global"]')); + await driver.executeScript('arguments[0].scrollIntoView(true);', radio); + await driver.executeScript('arguments[0].click();', radio); + + await driver.findElement(By.xpath('//button[@data-test-subj="confirm"]')).click(); + + await driver.wait(until.elementsLocated(By.xpath(userIconBtnXPath)), 10000); + + await driver.findElement(By.xpath(userIconBtnXPath)).click(); + + await driver.findElement(By.xpath('//*[@data-test-subj="log-out-1"]')).click(); + + // RELOGIN AND CHECK TENANT + + await driver.wait(until.elementsLocated(By.xpath(signInBtnXPath)), 10000); + + await driver.findElement(By.xpath(signInBtnXPath)).click(); + + await driver.wait(until.elementsLocated(By.xpath(skipWelcomeBtnXPath)), 10000); + + await driver.findElement(By.xpath(skipWelcomeBtnXPath)).click(); + + await driver.findElement(By.xpath(userIconBtnXPath)).click(); + + await driver.wait(until.elementsLocated(By.xpath(tenantNameLabelXPath)), 10000); + + const tenantName = await driver.findElement(By.xpath(tenantNameLabelXPath)).getText(); + + console.log('Tenant after login is %s', tenantName); + + expect(tenantName).toEqual('Global'); + + await driver.manage().deleteAllCookies(); + await driver.quit(); + }); +}); + +function getDriver() { + return new Builder().forBrowser('firefox').setFirefoxOptions(new Options().headless()); +}