From 35d78ab05107beae89759dbab99d394e76c59d69 Mon Sep 17 00:00:00 2001 From: Steve Breker Date: Thu, 3 Oct 2024 16:25:04 -0700 Subject: [PATCH] Add OIDC integration test WIP --- .github/workflows/oidc-integration-test.yml | 133 ++++++++++++++++++++ cypress.config.oidc.js | 9 ++ cypress/e2e/oidc/login.cy.js | 13 ++ docker/docker-compose.keycloak.yml | 15 +++ docker/etc/keycloak/realm.json | 129 +++++++++++++++++++ docker/etc/oidc/arOidcPlugin/config/app.yml | 70 +++++++++++ 6 files changed, 369 insertions(+) create mode 100644 .github/workflows/oidc-integration-test.yml create mode 100644 cypress.config.oidc.js create mode 100644 cypress/e2e/oidc/login.cy.js create mode 100644 docker/docker-compose.keycloak.yml create mode 100644 docker/etc/keycloak/realm.json create mode 100644 docker/etc/oidc/arOidcPlugin/config/app.yml diff --git a/.github/workflows/oidc-integration-test.yml b/.github/workflows/oidc-integration-test.yml new file mode 100644 index 0000000000..71b0064110 --- /dev/null +++ b/.github/workflows/oidc-integration-test.yml @@ -0,0 +1,133 @@ +name: OIDC integration tests +on: + pull_request: + push: + branches: + - qa/** + - stable/** +jobs: + integration-tests: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + browser: [Chrome, Electron, Firefox] + name: ${{ matrix.browser }} + env: + COMPOSE_FILE: ${{ github.workspace }}/docker/docker-compose.dev.yml + steps: + - name: Check out code + uses: actions/checkout@v3 + - name: Start containerized services + run: | + sudo sysctl -w vm.max_map_count=262144 + docker compose -p ci up -d percona elasticsearch gearmand + - name: Launch Keycloak service + run: | + docker compose -p ci -f ${{ github.workspace }}/docker/docker-compose.keycloak.yml up -d + - name: Wait for Keycloak to be Ready + run: | + echo "Waiting for Keycloak to be ready..." + for i in {1..30}; do + if nc -z localhost 8080; then + echo "Keycloak is up!" + break + fi + echo "Waiting for Keycloak..." + sleep 5 + done + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: none + extensions: apcu, opcache + - name: Setup PHP-FPM + run: | + sudo apt install php7.4-fpm + sudo service php7.4-fpm start + - name: Cache Composer dependencies + uses: actions/cache@v3 + with: + path: ~/.composer/cache/files + key: 20.04-7.4-composer-${{ hashFiles('composer.lock') }} + - name: Install Composer dependencies + run: composer install + - name: Cache NPM dependencies + uses: actions/cache@v3 + with: + path: | + ~/.npm + ~/.cache/Cypress + key: npm-${{ hashFiles('package-lock.json') }} + - name: Install NPM dependencies + run: sudo npm install -g npm && npm ci + - name: Modify Gearman config + run: | + echo -e "all:\n servers:\n default: 127.0.0.1:63005" \ + > apps/qubit/config/gearman.yml + - name: Build themes + run: | + sudo npm install -g "less@<4.0.0" + make -C plugins/arDominionPlugin + make -C plugins/arArchivesCanadaPlugin + npm run build + - name: Run the installer + run: | + php symfony tools:install \ + --database-host=127.0.0.1 \ + --database-port=63003 \ + --database-name=atom \ + --database-user=atom \ + --database-password=atom_12345 \ + --search-host=127.0.0.1 \ + --search-port=63002 \ + --search-index=atom \ + --demo \ + --no-confirmation + - name: Update OIDC plugin app.yml file + run: sudo cp docker/etc/oidc/arOidcPlugin/config/app.yml plugins/arOidcPlugin/config + - name: Update factories.yml to use oidcUser + run: | + sudo sed -i 's/class: myUser/class: oidcUser/' config/factories.yml + - name: Install OIDC Plugin + run: | + php symfony tools:atom-plugins add arOidcPlugin + - name: Change filesystem permissions + run: sudo chown -R www-data:www-data ${{ github.workspace }} + - name: Start application services + run: | + sudo cp test/etc/fpm_conf /etc/php/7.4/fpm/pool.d/atom.conf + sudo rm /etc/php/7.4/fpm/pool.d/www.conf + sudo systemctl restart php7.4-fpm + sudo php-fpm7.4 --test + sudo cp test/etc/worker_conf /usr/lib/systemd/system/atom-worker.service + sudo systemctl daemon-reload + sudo systemctl start atom-worker + - name: Install and configure Nginx + run: | + sudo apt install nginx + sudo cp test/etc/nginx_conf /etc/nginx/sites-available/atom + sudo ln -s /etc/nginx/sites-available/atom /etc/nginx/sites-enabled + sudo rm -f /etc/nginx/sites-enabled/default + sudo nginx -t + sudo systemctl restart nginx + - name: Create writable Cypress videos and screenshots dirs + run: | + sudo mkdir -p ${{ github.workspace }}/cypress/screenshots ${{ github.workspace }}/cypress/videos + sudo chmod a=rwx ${{ github.workspace }}/cypress/screenshots ${{ github.workspace }}/cypress/videos + - name: Run tests + env: + BROWSER: ${{ matrix.browser }} + CYPRESS_VIDEO: false + CYPRESS_BASE_URL: http://localhost + run: npx cypress run --config-file cypress.config.oidc.js --browser ${BROWSER,} + + - name: Output NGINX Error Log + run: cat /var/log/nginx/error.log + + - name: Tear down services + if: always() + run: | + docker compose -p ci down + docker compose -p ci -f ${{ github.workspace }}/docker/docker-compose.keycloak.yml down diff --git a/cypress.config.oidc.js b/cypress.config.oidc.js new file mode 100644 index 0000000000..9d2fb0a057 --- /dev/null +++ b/cypress.config.oidc.js @@ -0,0 +1,9 @@ +const { defineConfig } = require('cypress') + +module.exports = defineConfig({ + e2e: { + baseUrl: 'http://localhost:63001', + specPattern: 'cypress/e2e/oidc/**/*.cy.js', + supportFile: 'cypress/support/e2e.js' + }, +}) diff --git a/cypress/e2e/oidc/login.cy.js b/cypress/e2e/oidc/login.cy.js new file mode 100644 index 0000000000..2a6a37871b --- /dev/null +++ b/cypress/e2e/oidc/login.cy.js @@ -0,0 +1,13 @@ +describe('Login', () => { + it('Logs in through the user menu', () => { + cy.visit('/') + cy.contains('Log in').click() + cy.get('#csrf_token').should('exist') + cy.get('input#email').type(Cypress.env('adminEmail')) + cy.get('input#password').type(Cypress.env('adminPassword')) + cy.get('#user-menu + .dropdown-menu form').submit() + + cy.get('#user-menu').click() + cy.contains('Log out') + }) +}) diff --git a/docker/docker-compose.keycloak.yml b/docker/docker-compose.keycloak.yml new file mode 100644 index 0000000000..399d5d4701 --- /dev/null +++ b/docker/docker-compose.keycloak.yml @@ -0,0 +1,15 @@ +--- +services: + keycloak: + image: quay.io/keycloak/keycloak:latest + command: ["start-dev", "--import-realm"] + restart: unless-stopped + environment: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + KC_METRICS_ENABLED: true + KC_LOG_LEVEL: INFO + ports: + - 8080:8080 + volumes: + - .etc/keycloak/realm.json:/opt/keycloak/data/import/realm.json:ro diff --git a/docker/etc/keycloak/realm.json b/docker/etc/keycloak/realm.json new file mode 100644 index 0000000000..0f22dc4ff2 --- /dev/null +++ b/docker/etc/keycloak/realm.json @@ -0,0 +1,129 @@ +[ + { + "id": "demo", + "realm": "demo", + "sslRequired": "none", + "enabled": true, + "eventsEnabled": true, + "eventsExpiration": 900, + "adminEventsEnabled": true, + "adminEventsDetailsEnabled": true, + "attributes": { + "adminEventsExpiration": "900" + }, + "clients": [ + { + "id": "atom", + "clientId": "atom", + "name": "atom", + "enabled": true, + "rootUrl": "http://docker-atom:63001", + "adminUrl": "http://docker-atom:63001", + "baseUrl": "http://docker-atom:63001", + "clientAuthenticatorType": "client-secret", + "secret": "example-secret", + "redirectUris": ["http://docker-atom:63001/*"], + "webOrigins": ["http://docker-atom:63001"], + "standardFlowEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false + } + ], + "users": [ + { + "id": "demo", + "email": "demo@example.com", + "username": "demo", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "temporary": false, + "type": "password", + "value": "demo" + } + ], + "realmRoles": [ + "atom-admin", + "atom-editor" + ] + } + ], + "roles": { + "realm": [ + { + "name": "atom-admin", + "description": "Admin role for AtoM" + }, + { + "name": "atom-editor", + "description": "Editor role for AtoM" + } + ] + } + }, + { + "id": "secondary", + "realm": "secondary", + "sslRequired": "none", + "enabled": true, + "eventsEnabled": true, + "eventsExpiration": 900, + "adminEventsEnabled": true, + "adminEventsDetailsEnabled": true, + "attributes": { + "adminEventsExpiration": "900" + }, + "clients": [ + { + "id": "atom-secondary", + "clientId": "atom-secondary", + "name": "atom-secondary", + "enabled": true, + "rootUrl": "http://docker-atom:63001", + "adminUrl": "http://docker-atom:63001", + "baseUrl": "http://docker-atom:63001", + "clientAuthenticatorType": "client-secret", + "secret": "example-secret", + "redirectUris": ["http://docker-atom:63001/*"], + "webOrigins": ["http://docker-atom:63001"], + "standardFlowEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false + } + ], + "users": [ + { + "id": "support", + "email": "support@example.com", + "username": "support", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "temporary": false, + "type": "password", + "value": "support" + } + ], + "realmRoles": [ + "atom-admin" + ] + } + ], + "roles": { + "realm": [ + { + "name": "atom-admin", + "description": "Admin role for AtoM" + }, + { + "name": "atom-editor", + "description": "Editor role for AtoM" + } + ] + } + } +] diff --git a/docker/etc/oidc/arOidcPlugin/config/app.yml b/docker/etc/oidc/arOidcPlugin/config/app.yml new file mode 100644 index 0000000000..5b028925e1 --- /dev/null +++ b/docker/etc/oidc/arOidcPlugin/config/app.yml @@ -0,0 +1,70 @@ +## OIDC Plugin configuration. +all: + oidc: + providers: + demo: + url: 'http://keycloak:8080/realms/demo' + client_id: 'atom' + client_secret: 'example-secret' + send_oidc_logout: true + enable_refresh_token_use: true + server_cert: false + set_groups_from_attributes: true + user_groups: + administrator: + attribute_value: 'atom-admin' + group_id: 100 + editor: + attribute_value: 'atom-editor' + group_id: 101 + contributor: + attribute_value: 'atom-contributor' + group_id: 102 + translator: + attribute_value: 'atom-translator' + group_id: 103 + scopes: + - 'openid' + - 'profile' + - 'email' + roles_source: 'access-token' + roles_path: + - 'realm_access' + - 'roles' + user_matching_source: 'oidc-email' + auto_create_atom_user: true + secondary: + url: 'http://keycloak:8080/realms/secondary' + client_id: 'atom-secondary' + client_secret: 'example-secret' + send_oidc_logout: true + enable_refresh_token_use: true + server_cert: false + set_groups_from_attributes: true + user_groups: + administrator: + attribute_value: 'atom-admin' + group_id: 100 + editor: + attribute_value: 'atom-editor' + group_id: 101 + contributor: + attribute_value: 'atom-contributor' + group_id: 102 + translator: + attribute_value: 'atom-translator' + group_id: 103 + scopes: + - 'openid' + - 'profile' + - 'email' + roles_source: 'access-token' + roles_path: + - 'realm_access' + - 'roles' + user_matching_source: 'oidc-email' + auto_create_atom_user: true + primary_provider_name: primary + provider_query_param_name: secondary + redirect_url: 'http://127.0.0.1:63001/index.php/oidc/login' + logout_redirect_url: 'http://127.0.0.1:63001'