From d8633681c5b84bb20b6c0e5fd6faf500270cf94e Mon Sep 17 00:00:00 2001 From: Ryan Liang <109499885+RyanL1997@users.noreply.github.com> Date: Wed, 29 Nov 2023 11:44:07 -0800 Subject: [PATCH] Cypress13 testing frame work for OIDC and SAML (#1579) * Cypress13 testing frame work for OIDC and SAML --------- Signed-off-by: Ryan Liang --- .eslintignore | 1 + .eslintrc.js | 32 +- .github/workflows/cypress-test-oidc-e2e.yml | 256 ++++++++++++ .github/workflows/cypress-test-saml-e2e.yml | 216 ++++++++++ .gitignore | 2 + cypress.config.js | 35 ++ package.json | 5 + test/cypress/e2e/oidc/oidc_auth_test.spec.js | 125 ++++++ test/cypress/e2e/saml/saml_auth_test.spec.js | 121 ++++++ .../fixtures/saml/samlUserRoleMappiing.json | 4 + test/cypress/support/commands.js | 80 ++++ test/cypress/support/constants.js | 42 ++ test/cypress/support/e2e.js | 36 ++ test/cypress/support/index.d.ts | 55 +++ test/jest_integration/saml_auth.test.ts | 374 ------------------ 15 files changed, 1000 insertions(+), 384 deletions(-) create mode 100644 .github/workflows/cypress-test-oidc-e2e.yml create mode 100644 .github/workflows/cypress-test-saml-e2e.yml create mode 100644 cypress.config.js create mode 100644 test/cypress/e2e/oidc/oidc_auth_test.spec.js create mode 100644 test/cypress/e2e/saml/saml_auth_test.spec.js create mode 100644 test/cypress/fixtures/saml/samlUserRoleMappiing.json create mode 100644 test/cypress/support/commands.js create mode 100644 test/cypress/support/constants.js create mode 100644 test/cypress/support/e2e.js create mode 100644 test/cypress/support/index.d.ts delete mode 100644 test/jest_integration/saml_auth.test.ts diff --git a/.eslintignore b/.eslintignore index 17ca04c94..154111996 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,4 @@ node_modules /build /target /.eslintrc.js +/cypress.config.js diff --git a/.eslintrc.js b/.eslintrc.js index 2b5f80b11..5e176b8bb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -15,9 +15,15 @@ const LICENSE_HEADER = ` */ ` -module.exports = { - root: true, +module.exports = { + root: true, extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'], + env: { + 'cypress/globals': true, + }, + plugins: [ + 'cypress', + ], rules: { // "@osd/eslint/require-license-header": "off" '@osd/eslint/no-restricted-paths': [ @@ -27,11 +33,17 @@ module.exports = { zones: [ { target: ['(public|server)/**/*'], - from: ['../../packages/**/*','packages/**/*'] + from: ['../../packages/**/*','packages/**/*'], }, - ] - } - ] + ], + }, + ], + // Add cypress specific rules here + 'cypress/no-assigning-return-values': 'error', + 'cypress/no-unnecessary-waiting': 'error', + 'cypress/assertion-before-screenshot': 'warn', + 'cypress/no-force': 'warn', + 'cypress/no-async-tests': 'error', }, overrides: [ { @@ -43,8 +55,8 @@ module.exports = { licenses: [ LICENSE_HEADER ], }, ], - "no-console": 0 - } - } + 'no-console': 0, + }, + }, ], -}; \ No newline at end of file +}; diff --git a/.github/workflows/cypress-test-oidc-e2e.yml b/.github/workflows/cypress-test-oidc-e2e.yml new file mode 100644 index 000000000..c673018b7 --- /dev/null +++ b/.github/workflows/cypress-test-oidc-e2e.yml @@ -0,0 +1,256 @@ +name: Snapshot based E2E OIDC tests workflow +on: + pull_request: + branches: [ '**' ] +env: + OPENSEARCH_VERSION: '3.0.0' + KEYCLOAK_VERSION: '21.0.1' + TEST_KEYCLOAK_CLIENT_SECRET: 'oacHfNaXyy81r2uHq1A9RY4ASryre4rZ' + CI: 1 + # avoid warnings like "tput: No value for $TERM and no -T specified" + TERM: xterm + PLUGIN_NAME: opensearch-security + # This is the SHA256 checksum of the known good kc.sh script for Keycloak version 21.0.1. + KNOWN_CHECKSUM_OF_KEYCLOAK_SCRIPT: 'f825ea1a9ffa5ad91673737c06857ababbb69b6b8f09e0c637b4c998517f9608' + +jobs: + tests: + name: Run Cypress E2E OIDC tests + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + runs-on: ${{ matrix.os }} + + steps: + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Checkout Branch + uses: actions/checkout@v3 + + - name: Set env + run: | + opensearch_version=$(node -p "require('./package.json').opensearchDashboards.version") + plugin_version=$(node -p "require('./package.json').version") + echo "OPENSEARCH_VERSION=$opensearch_version" >> $GITHUB_ENV + echo "PLUGIN_VERSION=$plugin_version" >> $GITHUB_ENV + shell: bash + + # Download and Check Keycloak Version + - name: Download and Check Keyloak Version on Linux + if: ${{ runner.os == 'Linux' }} + run: | + echo "Downloading Keycloak ${{ env.KEYCLOAK_VERSION }}" + wget https://github.com/keycloak/keycloak/releases/download/${{ env.KEYCLOAK_VERSION }}/keycloak-${{ env.KEYCLOAK_VERSION }}.tar.gz + echo "Unpacking Keycloak" + tar -xzf keycloak-${{ env.KEYCLOAK_VERSION }}.tar.gz + cd keycloak-${{ env.KEYCLOAK_VERSION }}/bin + chmod +x ./kc.sh + echo "Generating checksum for the downloaded kc.sh script..." + DOWNLOADED_CHECKSUM=$(sha256sum kc.sh | awk '{print $1}') + echo "Downloaded kc.sh checksum: $DOWNLOADED_CHECKSUM" + echo "Known good kc.sh checksum: ${{ env.KNOWN_CHECKSUM_OF_KEYCLOAK_SCRIPT }}" + KNOWN_GOOD_CHECKSUM="${{ env.KNOWN_CHECKSUM_OF_KEYCLOAK_SCRIPT }}" + if [ "$DOWNLOADED_CHECKSUM" != "$KNOWN_GOOD_CHECKSUM" ]; then + echo "Checksum mismatch. The kc.sh script does not match the known good version. Please check https://github.com/keycloak/keycloak and verify the updates." + exit 1 + else + echo "Checksum match confirmed. Proceeding with setup." + fi + chmod +x ./kc.sh + + # Setup and Run Keycloak + - name: Get and run Keycloak on Linux + if: ${{ runner.os == 'Linux' }} + run: | + export KEYCLOAK_ADMIN=admin + export KEYCLOAK_ADMIN_PASSWORD=admin + cd keycloak-${{ env.KEYCLOAK_VERSION }}/bin + echo "Starting keycloak" + ./kc.sh start-dev --http-enabled=true --hostname-strict-https=false --http-host=localhost --http-relative-path /auth --health-enabled=true & + timeout 300 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/auth/health)" != "200" ]]; do sleep 5; done' + chmod +x kcadm.sh + echo "Creating client" + ./kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin --password admin + CID=$(./kcadm.sh create clients -r master -s clientId=opensearch -s secret="${{ env.TEST_KEYCLOAK_CLIENT_SECRET }}" -s 'attributes."access.token.lifespan"=60' -s 'redirectUris=["http://localhost:5603/auth/openid/login", "http://localhost:5601", "http://localhost:5601/auth/openid/login"]' -i) + ./kcadm.sh get clients/$CID/installation/providers/keycloak-oidc-keycloak-json > tmp + echo "Getting client secret for dashboards configuration purpose" + CLIENT_SECRET=$(grep -o '"secret" : "[^"]*' tmp | grep -o '[^"]*$') + echo "KEYCLOAK_CLIENT_SECRET=$CLIENT_SECRET" >> $GITHUB_ENV + echo "The client secret is: $CLIENT_SECRET" + echo "Creating client mapper" + ./kcadm.sh create clients/$CID/protocol-mappers/models -r master -s 'config."id.token.claim"=true' -s 'config."multivalued"=true' -s 'config."claim.name"="roles"' -s 'config."userinfo.token.claim"=true' -s 'config."access.token.claim"=true' -s 'name=rolemapper' -s 'protocolMapper=oidc-usermodel-realm-role-mapper' -s "protocol=openid-connect" + + - name: Download security plugin and create setup scripts + uses: ./.github/actions/download-plugin + with: + opensearch-version: ${{ env.OPENSEARCH_VERSION }} + plugin-name: ${{ env.PLUGIN_NAME }} + plugin-version: ${{ env.PLUGIN_VERSION }} + + # Download OpenSearch + - name: Download OpenSearch for Linux + uses: peternied/download-file@v2 + if: ${{ runner.os == 'Linux' }} + with: + url: https://artifacts.opensearch.org/snapshots/core/opensearch/${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/opensearch-min-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT-linux-x64-latest.tar.gz + + # Extract downloaded tar/zip + - name: Extract downloaded tar + if: ${{ runner.os == 'Linux' }} + run: | + tar -xzf opensearch-*.tar.gz + rm -f opensearch-*.tar.gz + shell: bash + + # Install the security plugin + - name: Install Plugin into OpenSearch for Linux + if: ${{ runner.os == 'Linux'}} + run: | + chmod +x ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/bin/opensearch-plugin + /bin/bash -c "yes | ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/bin/opensearch-plugin install file:$(pwd)/opensearch-security.zip" + shell: bash + + # Add OIDC Configuration + - name: Injecting OIDC Configuration for Linux + if: ${{ runner.os == 'Linux'}} + run: | + echo "Creating new SAML configuration" + cd ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/config/opensearch-security/ + rm -rf config.yml + cat << 'EOT' > config.yml + --- + _meta: + type: "config" + config_version: 2 + config: + dynamic: + http: + anonymous_auth_enabled: false + authc: + basic_internal_auth_domain: + description: "Authenticate via HTTP Basic against internal users database" + http_enabled: true + transport_enabled: true + order: 0 + http_authenticator: + type: basic + challenge: false + authentication_backend: + type: intern + openid_auth_domain: + http_enabled: true + transport_enabled: true + order: 1 + http_authenticator: + type: openid + challenge: false + config: + subject_key: preferred_username + roles_key: roles + openid_connect_url: http://localhost:8080/auth/realms/master/.well-known/openid-configuration + authentication_backend: + type: noop + EOT + echo "THIS IS THE SECURITY CONFIG FILE: " + cat config.yml + + # TODO: REMOVE THIS ONCE ADMIN JAVA TOOL SUPPORT IT + - name: Write password to initialAdminPassword location + if: ${{ runner.os == 'Linux'}} + run: + echo admin >> ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/config/initialAdminPassword.txt + shell: bash + + # Run any configuration scripts + - name: Run Setup Script for Linux + if: ${{ runner.os == 'Linux' }} + run: | + echo "running linux setup" + chmod +x ./setup.sh + ./setup.sh + shell: bash + + # Run OpenSearch + - name: Run OpenSearch with plugin on Linux + if: ${{ runner.os == 'Linux'}} + run: | + /bin/bash -c "./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/bin/opensearch &" + shell: bash + + # Give the OpenSearch process some time to boot up before sending any requires, might need to increase the default time! + - name: Sleep while OpenSearch starts + uses: peternied/action-sleep@v1 + with: + seconds: 30 + + # Verify that the server is operational + - name: Check OpenSearch Running on Linux + if: ${{ runner.os != 'Windows'}} + run: curl https://localhost:9200/_cat/plugins -u 'admin:admin' -k -v + shell: bash + + - if: always() + run: cat ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/logs/opensearch.log + shell: bash + + # OSD bootstrap + - name: Run Dashboard with Security Dashboards Plugin + uses: ./.github/actions/install-dashboards + with: + plugin_name: security-dashboards-plugin + + # Configure the Dashboard for OIDC setup + - name: Configure and Run OpenSearch Dashboards with Cypress Test Cases + if: ${{ runner.os == 'Linux' }} + run: | + cd ./OpenSearch-Dashboards + rm -rf ./config/opensearch_dashboards.yml + cat << 'EOT' > ./config/opensearch_dashboards.yml + server.host: "localhost" + opensearch.hosts: ["https://localhost:9200"] + opensearch.ssl.verificationMode: none + opensearch.username: "kibanaserver" + opensearch.password: "kibanaserver" + opensearch.requestHeadersWhitelist: [ authorization,securitytenant ] + opensearch_security.multitenancy.enabled: true + opensearch_security.multitenancy.tenants.preferred: ["Private", "Global"] + opensearch_security.readonly_mode.roles: ["kibana_read_only"] + opensearch_security.cookie.secure: false + opensearch_security.openid.connect_url: "http://127.0.0.1:8080/auth/realms/master/.well-known/openid-configuration" + opensearch_security.openid.client_id: "opensearch" + opensearch_security.openid.client_secret: "${{ env.TEST_KEYCLOAK_CLIENT_SECRET }}" + opensearch_security.auth.type: ["openid"] + opensearch_security.auth.multiple_auth_enabled: true + opensearch_security.ui.openid.login.buttonname: "OIDC" + home.disableWelcomeScreen: true + EOT + echo 'HERE IS THE DASHBOARD CONFIG FILE: ' + cat ./config/opensearch_dashboards.yml + nohup yarn start --no-base-path --no-watch | tee dashboard.log & + + # Check if OSD is ready with a max timeout of 600 seconds + - name : Check If OpenSearch Dashboards Is Ready + if: ${{ runner.os == 'Linux' }} + run: | + cd ./OpenSearch-Dashboards + echo "Start checking OpenSearch Dashboards." + for i in {1..60}; do + if grep -q "bundles compiled successfully after" "dashboard.log"; then + echo "OpenSearch Dashboards compiled successfully." + break + fi + if [ $i -eq 60 ]; then + echo "Timeout for 600 seconds reached. OpenSearch Dashboards did not finish compiling." + exit 1 + fi + sleep 10 + done + + - name: Run Cypress + run : | + yarn add cypress --save-dev + yarn cypress:run --browser chrome --headless --spec 'test/cypress/e2e/oidc/*.js' diff --git a/.github/workflows/cypress-test-saml-e2e.yml b/.github/workflows/cypress-test-saml-e2e.yml new file mode 100644 index 000000000..7a329a9cc --- /dev/null +++ b/.github/workflows/cypress-test-saml-e2e.yml @@ -0,0 +1,216 @@ +name: Snapshot based E2E SAML tests workflow +on: + pull_request: + branches: [ '**' ] +env: + OPENSEARCH_VERSION: '3.0.0' + CI: 1 + # avoid warnings like "tput: No value for $TERM and no -T specified" + TERM: xterm + PLUGIN_NAME: opensearch-security + +jobs: + tests: + name: Run Cypress E2E SAML tests + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + runs-on: ${{ matrix.os }} + + steps: + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Checkout Branch + uses: actions/checkout@v3 + + - name: Set env + run: | + opensearch_version=$(node -p "require('./package.json').opensearchDashboards.version") + plugin_version=$(node -p "require('./package.json').version") + echo "OPENSEARCH_VERSION=$opensearch_version" >> $GITHUB_ENV + echo "PLUGIN_VERSION=$plugin_version" >> $GITHUB_ENV + shell: bash + + - name: Download security plugin and create setup scripts + uses: ./.github/actions/download-plugin + with: + opensearch-version: ${{ env.OPENSEARCH_VERSION }} + plugin-name: ${{ env.PLUGIN_NAME }} + plugin-version: ${{ env.PLUGIN_VERSION }} + + # Download OpenSearch + - name: Download OpenSearch for Linux + uses: peternied/download-file@v2 + if: ${{ runner.os == 'Linux' }} + with: + url: https://artifacts.opensearch.org/snapshots/core/opensearch/${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/opensearch-min-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT-linux-x64-latest.tar.gz + + # Extract downloaded tar/zip + - name: Extract downloaded tar + if: ${{ runner.os == 'Linux' }} + run: | + tar -xzf opensearch-*.tar.gz + rm -f opensearch-*.tar.gz + shell: bash + + # TODO: REMOVE THIS ONCE ADMIN JAVA TOOL SUPPORT IT + - name: Write password to initialAdminPassword location + if: ${{ runner.os == 'Linux'}} + run: + echo admin >> ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/config/initialAdminPassword.txt + shell: bash + + # Install the security plugin + - name: Install Plugin into OpenSearch for Linux + if: ${{ runner.os == 'Linux'}} + run: | + chmod +x ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/bin/opensearch-plugin + /bin/bash -c "yes | ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/bin/opensearch-plugin install file:$(pwd)/opensearch-security.zip" + shell: bash + + # Add SAML Configuration + - name: Injecting SAML Configuration for Linux + if: ${{ runner.os == 'Linux'}} + run: | + echo "Creating new SAML configuration" + cd ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/config/opensearch-security/ + rm -rf config.yml + cat << 'EOT' > config.yml + --- + _meta: + type: "config" + config_version: 2 + config: + dynamic: + http: + anonymous_auth_enabled: false + authc: + basic_internal_auth_domain: + description: "Authenticate via HTTP Basic against internal users database" + http_enabled: true + transport_enabled: true + order: 0 + http_authenticator: + type: basic + challenge: false + authentication_backend: + type: intern + saml_auth_domain: + http_enabled: true + transport_enabled: false + order: 1 + http_authenticator: + type: saml + challenge: true + config: + idp: + entity_id: urn:example:idp + metadata_url: http://localhost:7000/metadata + sp: + entity_id: https://localhost:9200 + kibana_url: http://localhost:5601 + exchange_key: 6aff3042-1327-4f3d-82f0-40a157ac4464 + authentication_backend: + type: noop + EOT + echo "THIS IS THE SECURITY CONFIG FILE: " + cat config.yml + + # Run any configuration scripts + - name: Run Setup Script for Linux + if: ${{ runner.os == 'Linux' }} + run: | + echo "running linux setup" + chmod +x ./setup.sh + ./setup.sh + shell: bash + + # Run OpenSearch + - name: Run OpenSearch with plugin on Linux + if: ${{ runner.os == 'Linux'}} + run: | + /bin/bash -c "./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/bin/opensearch &" + shell: bash + + # Give the OpenSearch process some time to boot up before sending any requires, might need to increase the default time! + - name: Sleep while OpenSearch starts + uses: peternied/action-sleep@v1 + with: + seconds: 30 + + # Verify that the server is operational + - name: Check OpenSearch Running on Linux + if: ${{ runner.os != 'Windows'}} + run: curl https://localhost:9200/_cat/plugins -u 'admin:admin' -k -v + shell: bash + + - if: always() + run: cat ./opensearch-${{ env.OPENSEARCH_VERSION }}-SNAPSHOT/logs/opensearch.log + shell: bash + + # OSD bootstrap + - name: Run Dashboard with Security Dashboards Plugin + uses: ./.github/actions/install-dashboards + with: + plugin_name: security-dashboards-plugin + + # Setup and Run SAML Idp + - name: Get and run SAML Idp on Linux + if: ${{ runner.os == 'Linux' }} + run: | + cd ./OpenSearch-Dashboards/plugins/security-dashboards-plugin + yarn pretest:jest_server + + # Configure the Dashboard for SAML setup + - name: Configure and Run OpenSearch Dashboards with SAML Configuration + if: ${{ runner.os == 'Linux' }} + run: | + cd ./OpenSearch-Dashboards + rm -rf ./config/opensearch_dashboards.yml + cat << 'EOT' > ./config/opensearch_dashboards.yml + server.host: "localhost" + opensearch.hosts: ["https://localhost:9200"] + opensearch.ssl.verificationMode: none + opensearch.username: "kibanaserver" + opensearch.password: "kibanaserver" + opensearch.requestHeadersWhitelist: [ authorization,securitytenant ] + opensearch_security.multitenancy.enabled: true + opensearch_security.multitenancy.tenants.preferred: ["Private", "Global"] + opensearch_security.readonly_mode.roles: ["kibana_read_only"] + opensearch_security.cookie.secure: false + server.xsrf.allowlist: ["/_plugins/_security/api/authtoken", "/_opendistro/_security/api/authtoken", "/_opendistro/_security/saml/acs", "/_opendistro/_security/saml/acs/idpinitiated", "/_opendistro/_security/saml/logout"] + opensearch_security.auth.type: ["saml"] + opensearch_security.auth.multiple_auth_enabled: true + opensearch_security.auth.anonymous_auth_enabled: false + home.disableWelcomeScreen: true + EOT + echo 'HERE IS THE DASHBOARD CONFIG FILE: ' + cat ./config/opensearch_dashboards.yml + nohup yarn start --no-base-path --no-watch | tee dashboard.log & + + # Check if OSD is ready with a max timeout of 600 seconds + - name : Check If OpenSearch Dashboards Is Ready + if: ${{ runner.os == 'Linux' }} + run: | + cd ./OpenSearch-Dashboards + echo "Start checking OpenSearch Dashboards." + for i in {1..60}; do + if grep -q "bundles compiled successfully after" "dashboard.log"; then + echo "OpenSearch Dashboards compiled successfully." + break + fi + if [ $i -eq 60 ]; then + echo "Timeout for 600 seconds reached. OpenSearch Dashboards did not finish compiling." + exit 1 + fi + sleep 10 + done + + - name: Run Cypress + run : | + yarn add cypress --save-dev + yarn cypress:run --browser chrome --headless --spec 'test/cypress/e2e/saml/*.js' diff --git a/.gitignore b/.gitignore index 513364a9b..45a42aca7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ yarn-error.log kibana-coverage/ .DS_Store .idea/ +test/cypress/screenshots +test/cypress/downloads diff --git a/cypress.config.js b/cypress.config.js new file mode 100644 index 000000000..56d619ab5 --- /dev/null +++ b/cypress.config.js @@ -0,0 +1,35 @@ +/* + * 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 { defineConfig } = require('cypress'); + +module.exports = defineConfig({ + screenshotsFolder: 'test/cypress/screenshots', + downloadsFolder: 'test/cypress/downloads', + defaultCommandTimeout: 60000, + requestTimeout: 60000, + responseTimeout: 60000, + e2e: { + setupNodeEvents(on, config) {}, + supportFile: 'test/cypress/support/e2e.js', + baseUrl: 'http://localhost:5601', + specPattern: 'test/cypress/e2e/**/*.spec.js', + }, + env: { + openSearchUrl: 'https://localhost:9200', + adminUserName: 'admin', + adminPassword: 'admin', + }, +}); diff --git a/package.json b/package.json index e3993e5a5..eea00c4c7 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "license": "Apache-2.0", "homepage": "https://github.com/opensearch-project/security-dashboards-plugin", "scripts": { + "cypress:open": "cypress open", + "cypress:run": "cypress run", "plugin-helpers": "node ../../scripts/plugin_helpers", "osd": "node ../../scripts/osd", "opensearch": "node ../../scripts/opensearch", @@ -25,6 +27,8 @@ "@elastic/eslint-import-resolver-kibana": "link:../../packages/osd-eslint-import-resolver-opensearch-dashboards", "@testing-library/react-hooks": "^7.0.2", "@types/hapi__wreck": "^15.0.1", + "cypress": "^13.5.1", + "cypress-mochawesome-reporter": "^3.3.0", "gulp-rename": "2.0.0", "jose": "^4.11.2", "saml-idp": "^1.2.1", @@ -34,6 +38,7 @@ "dependencies": { "@hapi/cryptiles": "5.0.0", "@hapi/wreck": "^17.1.0", + "eslint-plugin-cypress": "^2.8.1", "html-entities": "1.3.1" }, "resolutions": { diff --git a/test/cypress/e2e/oidc/oidc_auth_test.spec.js b/test/cypress/e2e/oidc/oidc_auth_test.spec.js new file mode 100644 index 000000000..b4c5c80d2 --- /dev/null +++ b/test/cypress/e2e/oidc/oidc_auth_test.spec.js @@ -0,0 +1,125 @@ +/* + * 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. + */ + +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +const login = 'admin'; +const password = 'admin'; + +describe('Log in via OIDC', () => { + afterEach(() => { + cy.origin('http://localhost:5601', () => { + cy.clearCookies(); + cy.clearLocalStorage(); + }); + }); + + const kcLogin = () => { + cy.get('#kc-page-title').should('be.visible'); + cy.get('input[id=username]').should('be.visible').type(login); + cy.get('input[id=password]').should('be.visible').type(password); + cy.get('#kc-login').click(); + }; + + it('Login to app/opensearch_dashboards_overview#/ when OIDC is enabled', () => { + cy.visit('http://localhost:5601/app/opensearch_dashboards_overview', { + failOnStatusCode: false, + }); + + kcLogin(); + + cy.origin('http://localhost:5601', () => { + localStorage.setItem('opendistro::security::tenant::saved', '""'); + localStorage.setItem('home:newThemeModal:show', 'false'); + + cy.get('#osdOverviewPageHeader__title').should('be.visible'); + + cy.getCookie('security_authentication').should('exist'); + }); + }); + + it('Login to app/dev_tools#/console when OIDC is enabled', () => { + cy.visit('http://localhost:5601/app/opensearch_dashboards_overview', { + failOnStatusCode: false, + }); + + kcLogin(); + + cy.origin('http://localhost:5601', () => { + localStorage.setItem('opendistro::security::tenant::saved', '""'); + localStorage.setItem('home:newThemeModal:show', 'false'); + + cy.visit('http://localhost:5601/app/dev_tools#/console'); + + cy.get('a').contains('Dev Tools').should('be.visible'); + + cy.getCookie('security_authentication').should('exist'); + }); + }); + + it('Login to Dashboard with Hash', () => { + cy.visit( + `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)` + ); + + kcLogin(); + + cy.origin('http://localhost:5601', () => { + localStorage.setItem('opendistro::security::tenant::saved', '""'); + localStorage.setItem('home:newThemeModal:show', 'false'); + + cy.get('.euiHeader.euiHeader--default.euiHeader--fixed.primaryHeader').should('be.visible'); + + cy.getCookie('security_authentication').should('exist'); + }); + }); + + it('Tenancy persisted after logout in OIDC', () => { + cy.visit('http://localhost:5601/app/opensearch_dashboards_overview#/', { + failOnStatusCode: false, + }); + + kcLogin(); + + cy.origin('http://localhost:5601', () => { + localStorage.setItem('home:newThemeModal:show', 'false'); + + cy.get('#private').should('be.enabled'); + cy.get('#private').click({ force: true }); + + cy.get('button[data-test-subj="confirm"]').click(); + + cy.get('#osdOverviewPageHeader__title').should('be.visible'); + + cy.get('button[id="user-icon-btn"]').click(); + + cy.get('button[data-test-subj^="log-out-"]').click(); + }); + + kcLogin(); + + cy.origin('http://localhost:5601', () => { + cy.get('#user-icon-btn').should('be.visible'); + cy.get('#user-icon-btn').click(); + + cy.get('#osdOverviewPageHeader__title').should('be.visible'); + + cy.get('#tenantName').should('have.text', 'Private'); + }); + }); +}); diff --git a/test/cypress/e2e/saml/saml_auth_test.spec.js b/test/cypress/e2e/saml/saml_auth_test.spec.js new file mode 100644 index 000000000..925256ee8 --- /dev/null +++ b/test/cypress/e2e/saml/saml_auth_test.spec.js @@ -0,0 +1,121 @@ +/* + * 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. + */ + +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ALL_ACCESS_ROLE } from '../../support/constants'; + +import samlUserRoleMapping from '../../fixtures/saml/samlUserRoleMappiing.json'; + +before(() => { + cy.intercept('https://localhost:9200'); + + // Avoid Cypress lock onto the ipv4 range, so fake `visit()` before `request()`. + // See: https://github.com/cypress-io/cypress/issues/25397#issuecomment-1402556488 + cy.visit('http://localhost:5601'); + + cy.createRoleMapping(ALL_ACCESS_ROLE, samlUserRoleMapping); + cy.clearCookies(); + cy.clearLocalStorage(); +}); + +afterEach(() => { + cy.clearCookies(); + cy.clearLocalStorage(); +}); + +describe('Log in via SAML', () => { + const samlLogin = () => { + cy.get('input[id=userName]').should('be.visible'); + cy.get('button[id=btn-sign-in]').should('be.visible').click(); + }; + + it('Login to app/opensearch_dashboards_overview#/ when SAML is enabled', () => { + localStorage.setItem('opendistro::security::tenant::saved', '"__user__"'); + localStorage.setItem('home:newThemeModal:show', 'false'); + + cy.visit('http://localhost:5601/app/opensearch_dashboards_overview', { + failOnStatusCode: false, + }); + + samlLogin(); + + cy.get('#osdOverviewPageHeader__title').should('be.visible'); + cy.getCookie('security_authentication').should('exist'); + }); + + it('Login to app/dev_tools#/console when SAML is enabled', () => { + localStorage.setItem('opendistro::security::tenant::saved', '"__user__"'); + localStorage.setItem('home:newThemeModal:show', 'false'); + + cy.visit('http://localhost:5601/app/dev_tools#/console', { + failOnStatusCode: false, + }); + + samlLogin(); + + cy.get('a.euiBreadcrumb--last').contains('Dev Tools'); + cy.getCookie('security_authentication').should('exist'); + }); + + it('Login to Dashboard with Hash', () => { + localStorage.setItem('opendistro::security::tenant::saved', '"__user__"'); + localStorage.setItem('home:newThemeModal:show', 'false'); + + const urlWithHash = `http://localhost:5601/app/security-dashboards-plugin#/getstarted`; + + cy.visit(urlWithHash, { + failOnStatusCode: false, + }); + + samlLogin(); + + cy.get('h1.euiTitle--large').contains('Get started'); + cy.getCookie('security_authentication').should('exist'); + }); + + it('Tenancy persisted after logout in SAML', () => { + localStorage.setItem('home:newThemeModal:show', 'false'); + + cy.visit('http://localhost:5601/app/opensearch_dashboards_overview', { + failOnStatusCode: false, + }); + + samlLogin(); + + cy.get('#private').should('be.enabled'); + cy.get('#private').click({ force: true }); + + cy.get('button[data-test-subj="confirm"]').click(); + + cy.get('#osdOverviewPageHeader__title').should('be.visible'); + + cy.get('button[id="user-icon-btn"]').click(); + + cy.get('button[data-test-subj^="log-out-"]').click(); + + samlLogin(); + + cy.get('#user-icon-btn').should('be.visible'); + cy.get('#user-icon-btn').click(); + + cy.get('#osdOverviewPageHeader__title').should('be.visible'); + + cy.get('#tenantName').should('have.text', 'Private'); + }); +}); diff --git a/test/cypress/fixtures/saml/samlUserRoleMappiing.json b/test/cypress/fixtures/saml/samlUserRoleMappiing.json new file mode 100644 index 000000000..b1f015cfc --- /dev/null +++ b/test/cypress/fixtures/saml/samlUserRoleMappiing.json @@ -0,0 +1,4 @@ +{ + "backend_roles" : [ "admin" ], + "users" : [ "saml.jackson@example.com" ] +} diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js new file mode 100644 index 000000000..ade3591cf --- /dev/null +++ b/test/cypress/support/commands.js @@ -0,0 +1,80 @@ +/* + * 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. + */ + +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SEC_API, ADMIN_AUTH } from './constants'; + +/** + * Overwrite request command to support authentication similar to visit. + * The request function parameters can be url, or (method, url), or (method, url, body). + */ +Cypress.Commands.overwrite('request', (originalFn, ...args) => { + const defaults = {}; + defaults.auth = ADMIN_AUTH; + let options = {}; + if (typeof args[0] === 'object' && args[0] !== null) { + options = { ...args[0] }; + } else if (args.length === 1) { + [options.url] = args; + } else if (args.length === 2) { + [options.method, options.url] = args; + } else if (args.length === 3) { + [options.method, options.url, options.body] = args; + } + + return originalFn({ ...defaults, ...options }); +}); + +Cypress.Commands.add('createTenant', (tenantID, tenantJson) => { + cy.request( + 'PUT', + `${Cypress.env('openSearchUrl')}${SEC_API.TENANTS_BASE}/${tenantID}`, + tenantJson + ).then((response) => { + expect(response.status).to.eq(200); + }); +}); + +Cypress.Commands.add('createInternalUser', (userID, userJson) => { + cy.request( + 'PUT', + `${Cypress.env('openSearchUrl')}${SEC_API.INTERNALUSERS_BASE}/${userID}`, + userJson + ).then((response) => { + expect(response.status).to.eq(200); + }); +}); + +Cypress.Commands.add('createRole', (roleID, roleJson) => { + cy.request('PUT', `${Cypress.env('openSearchUrl')}${SEC_API.ROLE_BASE}/${roleID}`, roleJson).then( + (response) => { + expect(response.status).to.eq(200); + } + ); +}); + +Cypress.Commands.add('createRoleMapping', (roleID, rolemappingJson) => { + cy.request( + 'PUT', + `${Cypress.env('openSearchUrl')}${SEC_API.ROLE_MAPPING_BASE}/${roleID}`, + rolemappingJson + ).then((response) => { + expect(response.status).to.eq(200); + }); +}); diff --git a/test/cypress/support/constants.js b/test/cypress/support/constants.js new file mode 100644 index 000000000..95cb4da04 --- /dev/null +++ b/test/cypress/support/constants.js @@ -0,0 +1,42 @@ +/* + * 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. + */ + +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + ***************************** + SECURITY DASHBOARDS PLUGIN CONSTANTS + ***************************** + */ + +export const ALL_ACCESS_ROLE = 'all_access'; + +//Admin Credential +export const ADMIN_AUTH = { + username: Cypress.env('adminUserName'), + password: Cypress.env('adminPassword'), +}; + +//Security API Constants +export const SEC_API_PREFIX = '/_plugins/_security/api'; +export const SEC_API = { + TENANTS_BASE: `${SEC_API_PREFIX}/tenants`, + INTERNALUSERS_BASE: `${SEC_API_PREFIX}/internalusers`, + ROLE_BASE: `${SEC_API_PREFIX}/roles`, + ROLE_MAPPING_BASE: `${SEC_API_PREFIX}/rolesmapping`, +}; diff --git a/test/cypress/support/e2e.js b/test/cypress/support/e2e.js new file mode 100644 index 000000000..433a1cab9 --- /dev/null +++ b/test/cypress/support/e2e.js @@ -0,0 +1,36 @@ +/* + * 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. + */ + +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +// *********************************************************** +// This example support/e2e.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +import './commands'; diff --git a/test/cypress/support/index.d.ts b/test/cypress/support/index.d.ts new file mode 100644 index 000000000..61362a8c4 --- /dev/null +++ b/test/cypress/support/index.d.ts @@ -0,0 +1,55 @@ +/* + * 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. + */ + +// type definitions for custom commands like "createDefaultTodos" +// / + +declare namespace Cypress { + interface Chainable { + /** + * Create a test tenant by calling REST API + * @example + * cy.createTenant('test_tenant', tenantJsonFixture ) + */ + createTenant(tenantID: string, tenantJson: string): Chainable; + } + + interface Chainable { + /** + * Create an internal user by calling REST API + * @example + * cy.createInternalUser('test_user', userJsonFixture ) + */ + createInternalUser(userID: string, userJson: string): Chainable; + } + + interface Chainable { + /** + * Create a role by calling REST API + * @example + * cy.createRole('role_name', roleJsonFixture ) + */ + createRole(roleID: string, roleJson: string): Chainable; + } + + interface Chainable { + /** + * Create a role mapping by calling REST API + * @example + * cy.createRoleMapping('role_name', rolemappingJsonFixture ) + */ + createRoleMapping(roleID: string, rolemappingJson: string): Chainable; + } +} diff --git a/test/jest_integration/saml_auth.test.ts b/test/jest_integration/saml_auth.test.ts deleted file mode 100644 index e48635e41..000000000 --- a/test/jest_integration/saml_auth.test.ts +++ /dev/null @@ -1,374 +0,0 @@ -/* - * 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'; -import { AuthType } from '../../common'; - -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"]'; - const tenantSwitchBtnXPath = '//button[@data-test-subj="switch-tenants"]'; - // Browser Settings - const browser = 'firefox'; - const options = new Options().headless(); - - beforeAll(async () => { - root = osdTestServer.createRootWithSettings( - { - plugins: { - scanDirs: [resolve(__dirname, '../..')], - }, - home: { disableWelcomeScreen: true }, - server: { - host: 'localhost', - port: 5601, - xsrf: { - whitelist: [ - '/_opendistro/_security/saml/acs/idpinitiated', - '/_opendistro/_security/saml/acs', - '/_opendistro/_security/saml/logout', - ], - }, - }, - logging: { - silent: true, - verbose: false, - }, - 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: AuthType.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(); - - 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: AuthType.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 while updating security config!!', error.stack); - fail(error); - } - }); - - 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(browser, options).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(3); - await driver.manage().deleteAllCookies(); - await driver.quit(); - }); - - it('Login to app/dev_tools#/console when SAML is enabled', async () => { - const driver = getDriver(browser, options).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('//*[@data-test-subj="sendRequestButton"]')), - 10000 - ); - - const cookie = await driver.manage().getCookies(); - expect(cookie.length).toEqual(3); - await driver.manage().deleteAllCookies(); - await driver.quit(); - }); - - it('Login to Dashboard with Hash', async () => { - const urlWithHash = `http://localhost:5601/app/security-dashboards-plugin#/getstarted`; - const driver = getDriver(browser, options).build(); - await driver.manage().deleteAllCookies(); - await driver.get(urlWithHash); - await driver.findElement(By.xpath(signInBtnXPath)).click(); - // TODO Use a better XPath. - 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(3); - await driver.manage().deleteAllCookies(); - await driver.quit(); - }); - - it.skip('Tenancy persisted after Logout in SAML', async () => { - const driver = getDriver(browser, options).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.wait(until.elementIsSelected(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(); - const localStorageItem = await driver.executeScript( - `return window.localStorage.getItem("opendistro::security::tenant::saved")` - ); - - // Retry previous steps one more time if the webdriver doens't reload as expected - if (tenantName === 'Private' && localStorageItem === '""') { - await driver.wait(until.elementsLocated(By.xpath(tenantSwitchBtnXPath)), 10000); - await driver.findElement(By.xpath(tenantSwitchBtnXPath)).click(); - - await driver.executeScript('arguments[0].scrollIntoView(true);', radio); - await driver.executeScript('arguments[0].click();', radio); - await driver.wait(until.elementIsSelected(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(); - - await driver.wait(until.elementsLocated(By.xpath(signInBtnXPath)), 10000); - await driver.findElement(By.xpath(signInBtnXPath)).click(); - - await driver.wait(until.elementsLocated(By.xpath(userIconBtnXPath)), 10000); - await driver.findElement(By.xpath(userIconBtnXPath)).click(); - await driver.wait(until.elementsLocated(By.xpath(tenantNameLabelXPath)), 10000); - - const newtenantName = await driver.findElement(By.xpath(tenantNameLabelXPath)).getText(); - expect(newtenantName).toEqual('Global'); - } else { - expect(localStorageItem).toEqual('""'); - expect(tenantName).toEqual('Global'); - } - await driver.manage().deleteAllCookies(); - await driver.quit(); - - expect(localStorageItem).toEqual('""'); - }); -}); - -function getDriver(browser: string, options: Options) { - return new Builder().forBrowser(browser).setFirefoxOptions(options); -}