Skip to content

Commit

Permalink
Merge pull request #6351 from espoon-voltti/dummy-local-saml-idp
Browse files Browse the repository at this point in the history
Käytetään minimaalista dummy-idp -toteutusta testeissä oikean Suomi.fi-tunnistautumisen korvikkeena
  • Loading branch information
Gekkio authored Feb 14, 2025
2 parents 78d343f + d96d9fb commit 78fb1e5
Show file tree
Hide file tree
Showing 32 changed files with 3,292 additions and 92 deletions.
13 changes: 13 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ precedence = "aggregate"
SPDX-FileCopyrightText = "2017-2020 City of Espoo"
SPDX-License-Identifier = "LGPL-2.1-or-later"

[[annotations]]
path = "**/package-lock.json"
precedence = "aggregate"
SPDX-FileCopyrightText = "2017-2020 City of Espoo"
SPDX-License-Identifier = "LGPL-2.1-or-later"

[[annotations]]
path = "**/tsconfig**.json"
precedence = "aggregate"
Expand Down Expand Up @@ -133,3 +139,10 @@ path = "keycloak/theme/**"
precedence = "aggregate"
SPDX-FileCopyrightText = ["2020 City of Helsinki", "2021-2022 City of Espoo"]
SPDX-License-Identifier = "MIT"

[[annotations]]
path = "dummy-idp/test-cert/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2017-2025 City of Espoo"
SPDX-License-Identifier = "LGPL-2.1-or-later"

18 changes: 18 additions & 0 deletions apigw/config/test-cert/dummy-idp.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC+jCCAeKgAwIBAgITUgHa7VqVvIXzX/WKdNU/h+2XBjANBgkqhkiG9w0BAQsF
ADANMQswCQYDVQQGEwJGSTAeFw0yNTAxMjkxNDEyMTdaFw0yNTAyMjgxNDEyMTda
MA0xCzAJBgNVBAYTAkZJMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
k8JfKhZjQy7PhnMsaZGaUavaQrGkjAxu0jQDIF1P2WM3hpHlvuUXKF4+t07QpObC
OWbPeA4sOZy4IF/mymR/tLv/hWdE5nxm7faPiIXC7lJRTspOMURKy0Y9GMqfYolY
JzYBLLyesadykK4WL9YcvMHmlo1jzcfqsN4xBqD8x4ECbLoIr1gSoTxlhg7itZyq
rszBG/A9khfx+S5wUWXJDnrAJN9EJbH35iKZqfkiWSxDgJ+iZV/VlatppgGbjHcE
UAXLcpjeTHmnnmbaMCpkiXWVZVYq+OfqRKtUzVRaZXuYX19nnPxUSYyJDZy1PAW9
SVD4p6CZeNQNFI2yQbJG6wIDAQABo1MwUTAdBgNVHQ4EFgQUHdNQnrEub0ZYVcu1
HdQCb5qeMLcwHwYDVR0jBBgwFoAUHdNQnrEub0ZYVcu1HdQCb5qeMLcwDwYDVR0T
AQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAJCUU2iSWzJ7tIwSl3SmHMgkZ
3j4jFGw6v3KNyahWZswiZB1M8V5ilB+Wbvc+YLDapdqo+5Di/zweZuJT5GD/ayTO
G9f72EWWh2al5MQR4jTg/XhGiNKsXa0KGoDHlf+koMkfjBiwe+FpetgWbBXa8ENh
GLLsVYwI5IPqA8OOA6yBS1kqZrAP9Ho5lv6TRuoxXxT2WJjDz0+zRDnAKOqhe38I
OAfrIK7yAIx/nWTxVuLZga0E5bHDdBX8f04w1VMMN99jBAqPTje5WkMpJdZkCB/8
pw1KbgykK2YFZudjycE99nqgA7jjq0lXQYs/Wgtzbylw7nUF6c4pl7FKgst6WA==
-----END CERTIFICATE-----
9 changes: 8 additions & 1 deletion apigw/src/shared/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,14 @@ function createLocalDevelopmentOverrides(): Partial<EnvVariables> {
EVAKA_SERVICE_URL: 'http://localhost:8888',

AD_MOCK: true,
SFI_MODE: 'mock',
SFI_MODE: isLocal ? 'test' : 'mock',
SFI_SAML_CALLBACK_URL:
'http://localhost:9099/api/application/auth/saml/login/callback',
SFI_SAML_ENTRYPOINT: 'http://localhost:9090/idp/sso',
SFI_SAML_LOGOUT_URL: 'http://localhost:9090/idp/slo',
SFI_SAML_ISSUER: 'http://localhost:9099/api/application/auth/saml/',
SFI_SAML_PUBLIC_CERT: ['config/test-cert/dummy-idp.pem'],
SFI_SAML_PRIVATE_CERT: 'config/test-cert/saml-private.pem',

EVAKA_SAML_CALLBACK_URL:
'http://localhost:9099/api/employee/auth/ad/login/callback',
Expand Down
1 change: 1 addition & 0 deletions compose/docker-compose.e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ services:
- evaka-proxy
- api-gw
- evaka-srv
- dummy-idp
1 change: 1 addition & 0 deletions compose/docker-compose.e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ services:
depends_on:
- redis
- evaka-srv
- dummy-idp
volumes:
- ../apigw/config/test-cert:/home/evaka/test-cert
environment:
Expand Down
8 changes: 8 additions & 0 deletions compose/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ services:
ports:
- "9000:9000"

dummy-idp:
build:
context: ../dummy-idp
init: true
restart: on-failure
ports:
- "9090:9090"

volumes:
db-data:
driver: local
Expand Down
5 changes: 2 additions & 3 deletions compose/e2e/playwright/bin/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export CI="${CI:-false}"
export FORCE_COLOR=1
export PROXY_URL=${PROXY_URL:-http://localhost:9099}
export KEYCLOAK_URL=${KEYCLOAK_URL:-http://localhost:8080}
export DUMMY_SUOMIFI_URL=${DUMMY_SUOMIFI_URL:-http://localhost:9000}
export DUMMY_IDP_URL=${DUMMY_IDP_URL:-http://localhost:9090}

cd /repo/frontend
yarn set version self
Expand All @@ -23,8 +23,7 @@ yarn exec playwright install

echo 'INFO: Waiting for compose stack to be up ...'
wait-for-url.sh "${PROXY_URL}/api/dev-api/"
wait-for-url.sh "${KEYCLOAK_URL}/auth/realms/evaka-customer/account/" "200"
wait-for-url.sh "${DUMMY_SUOMIFI_URL}/health" "200"
wait-for-url.sh "${DUMMY_IDP_URL}/health" "200"

echo "Running tests ..."

Expand Down
10 changes: 10 additions & 0 deletions dummy-idp/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: 2017-2025 City of Espoo
#
# SPDX-License-Identifier: LGPL-2.1-or-later

*
!src
!test-cert
!package.json
!package-lock.json
!tsconfig.json
7 changes: 7 additions & 0 deletions dummy-idp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: 2017-2025 City of Espoo
#
# SPDX-License-Identifier: LGPL-2.1-or-later

/.idea/
/dist/
/node_modules/
40 changes: 40 additions & 0 deletions dummy-idp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# SPDX-FileCopyrightText: 2017-2025 City of Espoo
#
# SPDX-License-Identifier: LGPL-2.1-or-later

FROM node:22.13.1-bookworm-slim AS base

WORKDIR /project

ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
ENV LANGUAGE=C.UTF-8
RUN apt-get update \
&& apt-get -y dist-upgrade \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
tzdata \
ca-certificates \
&& ln -fs /usr/share/zoneinfo/Europe/Helsinki /etc/localtime \
&& dpkg-reconfigure --frontend noninteractive tzdata \
&& rm -rf /var/lib/apt/lists/*

FROM base AS builder

COPY ./package.json ./package-lock.json ./

RUN npm ci

COPY . .

RUN npm run build

FROM base

COPY --from=builder /project .

RUN npm ci --include=prod && npm cache clean --force

EXPOSE 9090

ENTRYPOINT ["node"]
CMD ["--enable-source-maps", "dist/index.js"]
46 changes: 46 additions & 0 deletions dummy-idp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!--
SPDX-FileCopyrightText: 2017-2025 City of Espoo
SPDX-License-Identifier: LGPL-2.1-or-later
-->

# dummy-idp

dummy-idp is a minimalistic SAML identity provider (IDP) for local eVaka development and automated tests.
It uses a library called samlp for low-level SAML protocol functionality.

## Features

- minimalistic IDP functionality that roughly looks a bit like real Suomi.fi SAML authentication
- in-memory user (= "VTJ person") storage, and a simple REST API for clearing/upserting them
- SAML login flow with a user selection page + confirmation page
- SAML logout flow (service provider initiated only)
- in-memory IDP sessions

## SAML login flow

1. Service provider (= eVaka apigw) redirects the user's browser to `/idp/sso`.
2. If there's already an IDP session, goto step 4. If there's no IDP session, a user selection HTML form is rendered.
3. The user selection form is submitted with a button press to `/idp/sso-login-confirm`
4. A confirmation page is rendered that lists the raw SAML attributes that will be sent in the SAML response to the service provider in the next step
5. The confirmation page form is submitted with a button press to `/idp/sso-login-finish`
6. A SAML response is created and the browser is redirected back to the service provider (= eVaka apigw)

Notes:

- we intentionally use SAML Redirect binding (= GET requests with query parameters) in everything
- the original SAML state (request, signature, RelayState) is passed using hidden form inputs on every page so they persist until the final `/idp/sso-login-finish` request where salmp needs them

## SAML logout flow

1. Service provider (= eVaka apigw) redirects the user's browser to `/idp/slo`.
2. The IDP session is destroyed
3. A SAML response is created and the browser is redirected back to the service provider (= eVaka apigw)

Notes:

- samlp seems to technically support IDP-initiated logout, but this is not implemented in dummy-idp

## Local development

Run `npm run dev` in the dummy-idp project directory.
8 changes: 8 additions & 0 deletions dummy-idp/nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"restartable": false,
"delay": 500,
"verbose": false,
"ignore": [],
"watch": ["dist/"],
"ext": "js json"
}
3 changes: 3 additions & 0 deletions dummy-idp/nodemon.json.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2017-2021 City of Espoo

SPDX-License-Identifier: LGPL-2.1-or-later
Loading

0 comments on commit 78fb1e5

Please sign in to comment.