Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions .github/workflows/run-auth-idp-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: Run auth IdP tests

# External-IdP authentication e2e suite (Keycloak OIDC, LDAP): runs the dhis-test-e2e
# "auth-idp" tagged tests against the docker-compose.e2e-auth.yml stack.
# Runs on every PR (reusing the dhis2/core-pr image published by "Run api tests"
# when possible), nightly on master, and manually.

env:
MAVEN_OPTS: -Xmx1024m -Xms1024m -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.class=standard -Dmaven.wagon.http.retryHandler.count=3 -Dmaven.wagon.httpconnectionManager.ttlSeconds=125

on:
pull_request:
schedule:
# Nightly on the default branch
- cron: "30 3 * * *"
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
auth-idp-test:
runs-on: ubuntu-latest
env:
# Mirrors DOCKERHUB_PUSH in run-api-tests.yml: for same-repo PRs the api tests
# workflow pushes dhis2/core-pr:<number> to Docker Hub, so we pull it instead of
# rebuilding. Fork/dependabot PRs, nightly and dispatch runs build the image here.
REUSE_PR_IMAGE: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }}
CORE_IMAGE_NAME: ${{ (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]') && format('dhis2/core-pr:{0}', github.event.number) || 'dhis2/core-dev:local' }}
steps:
- uses: actions/checkout@v6
- name: Set up JDK 17
if: env.REUSE_PR_IMAGE != 'true'
uses: actions/setup-java@v5
with:
java-version: 17
distribution: temurin
cache: maven

- name: Wait for PR image from api tests build
if: env.REUSE_PR_IMAGE == 'true'
run: |
expected="${{ github.event.pull_request.head.sha }}"
echo "Waiting for $CORE_IMAGE_NAME with revision $expected"
for i in $(seq 1 60); do
if docker pull --quiet "$CORE_IMAGE_NAME" > /dev/null 2>&1; then
revision=$(docker inspect --format '{{ index .Config.Labels "DHIS2_BUILD_REVISION" }}' "$CORE_IMAGE_NAME")
if [ "$revision" = "$expected" ]; then
echo "Found $CORE_IMAGE_NAME for revision $revision"
exit 0
fi
echo "Image revision '$revision' is stale, retrying..."
fi
sleep 20
done
echo "Timed out waiting for $CORE_IMAGE_NAME with revision $expected (did the api tests build fail?)" >&2
exit 1

- name: Build container image
if: env.REUSE_PR_IMAGE != 'true'
run: |
mvn clean verify --threads 2C --batch-mode --no-transfer-progress \
-DskipTests -Dpackaging.type=jar -Dmdep.analyze.skip --update-snapshots --file dhis-2/pom.xml \
--projects dhis-web-server --also-make --activate-profiles embedded,jibDockerBuild \
-Djib.to.image=$CORE_IMAGE_NAME

- name: Run auth IdP tests
run: |
cd dhis-2/dhis-test-e2e
DHIS2_E2E_TEST_ARGS="-Pauth-idp" SELENIUM_IMAGE=selenium/standalone-chrome:latest DHIS2_IMAGE=$CORE_IMAGE_NAME \
docker compose -f docker-compose.yml -f docker-compose.e2e.yml -f docker-compose.e2e-auth.yml \
up --remove-orphans --exit-code-from test

- name: Upload logs
if: failure()
run: |
cd dhis-2/dhis-test-e2e
docker compose -f docker-compose.yml -f docker-compose.e2e.yml -f docker-compose.e2e-auth.yml logs web > ~/web-logs.txt
docker compose -f docker-compose.yml -f docker-compose.e2e.yml -f docker-compose.e2e-auth.yml logs keycloak > ~/keycloak-logs.txt

- uses: actions/upload-artifact@v7
if: failure()
with:
name: "auth_idp_logs"
path: |
~/web-logs.txt
~/keycloak-logs.txt

send-slack-message:
runs-on: ubuntu-latest
if: |
always() &&
contains(needs.*.result, 'failure') &&
github.event_name == 'schedule'
needs: [auth-idp-test]
steps:
- uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_BACKEND_WEBHOOK }}
SLACK_CHANNEL: "team-backend"
SLACK_MESSAGE: "Nightly auth IdP (Keycloak/LDAP) e2e run failed and needs investigation :detective-duck:."
SLACK_COLOR: "#ff0000"
78 changes: 78 additions & 0 deletions dhis-2/dhis-test-e2e/config/dhis2_home_auth/dhis.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# DHIS2_HOME config for the auth-idp e2e stack (docker-compose.e2e-auth.yml).
# Identical to config/dhis2_home/dhis.conf plus the external Keycloak OIDC provider,
# kept separate so the extra login provider never perturbs the default e2e suite.

connection.dialect = org.hibernate.dialect.PostgreSQLDialect
connection.driver_class = org.postgresql.Driver
connection.url = jdbc:postgresql://db/dhis
connection.username = dhis
connection.password = dhis

# Redis configuration
redis.enabled = true
redis.host = redis

filestore.provider = s3
filestore.container = dhis2
filestore.location = eu-west-1
filestore.endpoint = http://minio:9000
filestore.identity = root
filestore.secret = dhisdhis

tracker.import.preheat.cache.enabled=off
audit.logger=off

# Analytics configuration
analytics.table.unlogged = on

login.security.totp_2fa.enabled = on
login.security.email_2fa.enabled = on

oauth2.server.enabled = on
server.base.url = http://web:8080/
oidc.jwt.token.authentication.enabled = on

oidc.oauth2.login.enabled = on
oidc.logout.redirect_url = http://web:8080/
oidc.provider.dhis2.client_id = dhis2-client
oidc.provider.dhis2.client_secret = secret
oidc.provider.dhis2.mapping_claim = email
oidc.provider.dhis2.display_alias = Sign in with dhis2
oidc.provider.dhis2.enable_logout = on
oidc.provider.dhis2.scopes = email
oidc.provider.dhis2.authorization_uri = http://web:8080/oauth2/authorize
oidc.provider.dhis2.token_uri = http://web:8080/oauth2/token
oidc.provider.dhis2.issuer_uri = http://web:8080/
oidc.provider.dhis2.jwk_uri = http://web:8080/oauth2/jwks
oidc.provider.dhis2.user_info_uri = http://web:8080/userinfo

# External IdP: Keycloak (see config/keycloak/realm-dhis2.json)
# Generic provider id "keycloak"; callback is {baseUrl}/oauth2/code/keycloak.
# issuer_uri must exactly match the "iss" claim of Keycloak tokens (KC_HOSTNAME).
oidc.provider.keycloak.client_id = dhis2-oidc
oidc.provider.keycloak.client_secret = dhis2-oidc-secret
oidc.provider.keycloak.mapping_claim = email
oidc.provider.keycloak.display_alias = Sign in with Keycloak
oidc.provider.keycloak.scopes = email
oidc.provider.keycloak.authorization_uri = http://keycloak:8080/realms/dhis2/protocol/openid-connect/auth
oidc.provider.keycloak.token_uri = http://keycloak:8080/realms/dhis2/protocol/openid-connect/token
oidc.provider.keycloak.user_info_uri = http://keycloak:8080/realms/dhis2/protocol/openid-connect/userinfo
oidc.provider.keycloak.jwk_uri = http://keycloak:8080/realms/dhis2/protocol/openid-connect/certs
oidc.provider.keycloak.issuer_uri = http://keycloak:8080/realms/dhis2

# LDAP bind authentication (see config/ldap/users.ldif).
# Active because ldap.url differs from the default and manager DN is set.
# DHIS2 users opt in via ldapId + externalAuth=true; the (cn={0}) filter is
# filled with User.ldapId, not the username.
ldap.url = ldap://openldap:389
ldap.manager.dn = cn=admin,dc=dhis2,dc=org
ldap.manager.password = admin
ldap.search.base = ou=users,dc=dhis2,dc=org
ldap.search.filter = (cn={0})

route.remote_servers_allowed = http://web:8080

# Enable monitoring API for /api/metrics endpoint testing
monitoring.api.enabled = on
monitoring.jvm.enabled = on
monitoring.dbpool.enabled = on
83 changes: 83 additions & 0 deletions dhis-2/dhis-test-e2e/config/keycloak/realm-dhis2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"realm": "dhis2",
"enabled": true,
"registrationAllowed": false,
"sslRequired": "none",
"clients": [
{
"clientId": "dhis2-oidc",
"name": "DHIS2 e2e OIDC client",
"enabled": true,
"protocol": "openid-connect",
"publicClient": false,
"secret": "dhis2-oidc-secret",
"standardFlowEnabled": true,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": false,
"redirectUris": [
"http://web:8080/oauth2/code/keycloak",
"http://localhost:8080/oauth2/code/keycloak"
],
"webOrigins": ["+"],
"protocolMappers": [
{
"name": "dhis2-audience",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
"included.client.audience": "dhis2-oidc",
"access.token.claim": "true",
"id.token.claim": "false"
}
}
]
}
],
"users": [
{
"username": "kcmapped",
"enabled": true,
"email": "kcmapped@dhis2.org",
"emailVerified": true,
"firstName": "Kc",
"lastName": "Mapped",
"credentials": [
{ "type": "password", "value": "KcMapped123###", "temporary": false }
]
},
{
"username": "kclimited",
"enabled": true,
"email": "kclimited@dhis2.org",
"emailVerified": true,
"firstName": "Kc",
"lastName": "Limited",
"credentials": [
{ "type": "password", "value": "KcLimited123###", "temporary": false }
]
},
{
"username": "kcloginmapped",
"enabled": true,
"email": "kcloginmapped@dhis2.org",
"emailVerified": true,
"firstName": "Kc",
"lastName": "Loginmapped",
"credentials": [
{ "type": "password", "value": "KcLogin123###", "temporary": false }
]
},
{
"username": "kcunmapped",
"enabled": true,
"email": "kcunmapped@dhis2.org",
"emailVerified": true,
"firstName": "Kc",
"lastName": "Unmapped",
"credentials": [
{ "type": "password", "value": "KcUnmapped123###", "temporary": false }
]
}
]
}
27 changes: 27 additions & 0 deletions dhis-2/dhis-test-e2e/config/ldap/users.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Seed entries for the auth-idp LDAP e2e tests (LdapLoginTest).
# Loaded by the osixia/openldap container (docker-compose.e2e-auth.yml).
# The DHIS2-side users are created by the tests; DHIS2 binds with the user's
# ldapId as the (cn={0}) filter value, so cn here deliberately differs from
# the DHIS2 username to pin that rewrite.

dn: ou=users,dc=dhis2,dc=org
objectClass: organizationalUnit
ou: users

dn: cn=ldapcn1,ou=users,dc=dhis2,dc=org
objectClass: inetOrgPerson
cn: ldapcn1
sn: One
userPassword: ldapPass1###

dn: cn=ldapcn2,ou=users,dc=dhis2,dc=org
objectClass: inetOrgPerson
cn: ldapcn2
sn: Two
userPassword: ldapPass2###

dn: cn=ldaporphancn,ou=users,dc=dhis2,dc=org
objectClass: inetOrgPerson
cn: ldaporphancn
sn: Orphan
userPassword: ldapOrphan###
44 changes: 44 additions & 0 deletions dhis-2/dhis-test-e2e/docker-compose.e2e-auth.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Overlay for the auth-idp e2e suite (external IdP tests: Keycloak OIDC, LDAP).
# Usage:
# DHIS2_E2E_TEST_ARGS="-Pauth-idp" docker compose \
# -f docker-compose.yml -f docker-compose.e2e.yml -f docker-compose.e2e-auth.yml \
# up --remove-orphans --exit-code-from test
#
# The web service gets a dedicated DHIS2_HOME (config/dhis2_home_auth/) so the extra
# login providers never affect the default e2e suite.

services:
web:
volumes:
- ./config/dhis2_home_auth/dhis.conf:/opt/dhis2/dhis.conf:ro
depends_on:
keycloak:
condition: service_started
openldap:
condition: service_started

keycloak:
image: quay.io/keycloak/keycloak:26.0
command: ["start-dev", "--import-realm"]
environment:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
# Fixed hostname so token "iss" is stable (http://keycloak:8080/realms/dhis2)
# and matches oidc.provider.keycloak.issuer_uri in dhis.conf.
KC_HOSTNAME: http://keycloak:8080
volumes:
- ./config/keycloak/realm-dhis2.json:/opt/keycloak/data/import/realm-dhis2.json:ro
ports:
- "8080"

openldap:
image: osixia/openldap:1.5.0
command: ["--copy-service"]
environment:
LDAP_ORGANISATION: "DHIS2"
LDAP_DOMAIN: "dhis2.org"
LDAP_ADMIN_PASSWORD: "admin"
volumes:
- ./config/ldap/users.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/users.ldif:ro
ports:
- "389"
18 changes: 16 additions & 2 deletions dhis-2/dhis-test-e2e/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@
</build>

<profiles>
<!-- Default profile will run all e2e tests except the analytics ones -->
<!-- Default profile will run all e2e tests except analytics and external-IdP (auth-idp) ones -->
<profile>
<id>default</id>
<activation>
Expand All @@ -301,7 +301,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<excludedGroups>analytics</excludedGroups>
<excludedGroups>analytics,auth-idp</excludedGroups>
</configuration>
</plugin>
</plugins>
Expand Down Expand Up @@ -342,5 +342,19 @@
</plugins>
</build>
</profile>
<profile>
<id>auth-idp</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<groups>auth-idp</groups>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Loading
Loading