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 03e09f2b0..5e176b8bb 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -15,11 +15,35 @@ 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': [
+ 'error',
+ {
+ basePath: __dirname,
+ zones: [
+ {
+ target: ['(public|server)/**/*'],
+ 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: [
{
@@ -31,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 21017b4f6..b561f9475 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,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 c073f564f..2a1ce5327 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);
-}