Skip to content

This container image provides an API and business logic to connect the 2FA Admin Frontend module to Keycloak.

License

Notifications You must be signed in to change notification settings

univention/2fa-helpdesk

Repository files navigation

2FA Helpdesk

This project provides:

  • 2fa backend: This handles all communication related to keycloak
  • 2fa frontend: This provides a self-service and an admin page and allows to reset OTP tokens

Local Development and Testing with Docker Compose

The project includes a Docker Compose setup for local development and testing. All necessary images can be built locally from the docker/ directory.

Prerequisites

  • Docker and Docker Compose installed
  • Git repository cloned locally

Setting up test-realm on keycloak version changes

For all types of integration tests we need a keycloak realm which has a specific setup. The realm's export is part of this repository and is loaded by keycloak in the docker compose environment.

This section describes how to (re-)create the realm if necessary. The steps in this sections are only required to be executed whenever we change the keycloak version and the realm we've exported under tests/integration/data/export/realm-export-with-user.json doesn't work well anymore.

Start keycloak setup container

Start the keycloak setup container:

docker compose up -d keycloak-setup
docker compose exec -it keycloak-setup bash

Start keycloak within the setup container:

export PATH=/opt/keycloak/bin:$PATH
kc.sh start-dev

Configure keycloak via UI

  1. Login as admin (pw: admin) under localhost:8080
  2. Create realm: test-realm
  3. Create client: 2fa-helpdesk
    • Activate Direct Access Grants
    • Set "Valid redirect URIs": *
    • Set "Valid post logout redirect URIs": *
    • Set "Web origins": *
  4. Create groups:
    • 2fa-users
    • 2FA Admins
  5. Create "Client Scope": twofa-default
    • Create and add new mapper: 2fa Groups
      • Set "Claim Name": 2fa_user_groups
      • Turn off the "Full group path" option
  6. Add client scope twofa-default to client 2fa-helpdesk
  7. Adjust "Authentication Flow":
    • Deactivate OTP for direct grant
    • Change browser/Conditional OTP to required.
  8. Create user with username test
    • Add user to groups:
      • 2fa-users
      • 2FA Admins
    • Create non-temporary password: 123qwe

Export keycloak realm

Stop keycloak the keycloak setup container and export the realm (by Ctrl+C). Then export the realms to JSON:

kc.sh export --file=/opt/keycloak/data/export/realm-export.json --optimized

Exit and shutdown the container:

docker compose down --timeout 0 keycloak-setup

Remove the admin user from the export to avoid failures on import:

jq '.[].users |= map(select(.realmRoles | index("admin") | not))' \
   tests/integration/data/export/realm-export.json \
   > tests/integration/data/export/realm-export-with-user.json
rm tests/integration/data/export/realm-export.json

Building and Running Services

The Docker Compose configuration supports different profiles for different use cases:

Setup Integration Testing

To run the complete 2FA helpdesk stack with all services:

# Build all images and start the integration test environment
docker compose --profile test-it up --build

# This will start:
# - Keycloak server (localhost:8080)
# - 2FA Helpdesk Backend API (localhost:8081)

Running Integration Tests with Docker Compose

# (Re-)build and run the testrunner for testing tests/integration/api-2fa
docker compose run -it --rm --build testrunner

# Alternatively: (Re-)build and run the testrunner for testing tests/integration/api-2fa
docker compose run -it --rm --build testrunner pytest -vv tests/integration/api-2fa

# Run tests from above without explicit building
docker compose run -it --rm testrunner
docker compose run -it --rm testrunner pytest -vv tests/integration/api-2fa

In case you want to build and use images named the same as the one specified in the compose file:

# Build the testrunner image
docker compose --profile test-it up --build --no-start
docker compose --profile test-it down --remove-orphans --volumes

# Run the integration tests
docker compose run -it --rm testrunner pytest -vv tests/integration/api-2fa

Running Integration Tests locally

Prerequisites:

  • Python 3.13

First load the pipenv environment:

cd docker/testrunner
pipenv sync
pipenv shell
cd -

The execute the tests:

pytest -vv tests/integration/api-2fa

Setup E2E Testing

To run the complete 2FA helpdesk stack with all services, to build all images and to start the e2e test environment for testing headless via docker compose:

docker compose --profile test-e2e-service up --build

# This will start:
# - Keycloak server (keycloak:8080, localhost:8080)
# - 2FA Helpdesk Backend API (api:8080, localhost:8081)
# - 2FA Helpdesk Frontend (keycloak:80)

In the scenario above keycloak and frontend share the same network (similar to a sidecar container in k8s). The reason is, that otherwise keycloak won't make the login page available and raise a "Web Crypto API is not available" error in the browser. That's a security feature, which enforces that pure http requests for login are only supported from localhost. Thus the e2e tests wouldn't run properly. (see also keycloak/keycloak#36804)

To run the complete 2FA helpdesk stack with all services, to build all images and start the e2e test environment for testing directly via pytest:

docker compose --profile test-e2e-local up --build

# This will start:
# - Keycloak server (keycloak:8080, localhost:8080)
# - 2FA Helpdesk Backend API (api:8080, localhost:8081)
# - 2FA Helpdesk Frontend (frontend:80, localhost:3000)

This allows also to test in headed mode which is helpful during development.

Running E2E Tests with Docker Compose

Ensure all services are started with the profile test-e2e-service (see section on E2E setup).

# (Re-)build and run the testrunner for testing tests/integration/api-2fa
docker compose run -it --rm --build testrunner

# Alternatively: (Re-)build and run the testrunner for testing tests/integration/api-2fa
docker compose run -it --rm --build testrunner pytest -vv tests/integration/e2e

# Run tests from above without explicit building
docker compose run -it --rm testrunner
docker compose run -it --rm testrunner pytest -vv tests/integration/e2e

In case you want to build and use an image named the same as the one specified in the compose file:

# Build the testrunner image
docker compose --profile test-e2e up --build --no-start
docker compose --profile test-e2e down --remove-orphans --volumes

# Run the integration tests
docker compose run -it --rm testrunner pytest -vv tests/integration/e2e

Running E2E Tests locally

Prerequisites:

  • Python 3.13

First load the pipenv environment:

cd docker/testrunner
pipenv sync
pipenv shell
cd -

The execute the tests:

pytest -vv tests/integration/e2e

Running Helm Chart Tests

# Run the test suite
docker compose run -it --rm test-chart

# Deal with trouble via pdb
docker compose run -it --rm test-chart tests/chart --pdb

# Have a shell
docker compose run -it --rm test-chart bash
pytest tests/chart

Accessing Services

When running the full stack (either with profile test-it or test-e2e-services):

When running the full stack (with profile test-e2e-local):

Rebuilding Images

To force rebuild all images:

docker compose --profile test-e2e build --no-cache

To rebuild a specific service:

docker compose build --no-cache api

API

reset_own_token_token_reset_own__post

Code samples

POST /token/reset/own/

Reset Own Token

Example responses

200 Response

{
  "users": [
    {
      "keycloak_internal_id": "string",
      "username": "string",
      "email": "string",
      "firstname": "string",
      "lastname": "string"
    }
  ],
  "succes": true,
  "detail": "string"
}

Responses

Status Meaning Description Schema
200 OK Successful Response ListUserResponse
To perform this operation, you must be authenticated by means of one of the following methods: OAuth2AuthorizationCodeBearer ( Scopes: openid ), OAuth2AuthorizationCodeBearer

reset_user_tokens_token_reset_user__post

Code samples

POST /token/reset/user/

Reset User Tokens

Body parameter

{
  "user_ids": [
    "string"
  ]
}

Parameters

Name In Type Required Description
body body ResetUsersRequest true none

Example responses

200 Response

{
  "success": true,
  "detail": "string",
  "resets_by_user": ""
}

Responses

Status Meaning Description Schema
200 OK Successful Response ResetResponse
422 Unprocessable Entity Validation Error HTTPValidationError
To perform this operation, you must be authenticated by means of one of the following methods: OAuth2AuthorizationCodeBearer ( Scopes: openid ), OAuth2AuthorizationCodeBearer

list_users_list_users_post

Code samples

POST /list_users

List Users

Body parameter

{
  "query": ""
}

Parameters

Name In Type Required Description
body body ListUserQuery false none

Example responses

200 Response

{
  "users": [
    {
      "keycloak_internal_id": "string",
      "username": "string",
      "email": "string",
      "firstname": "string",
      "lastname": "string"
    }
  ],
  "succes": true,
  "detail": "string"
}

Responses

Status Meaning Description Schema
200 OK Successful Response ListUserResponse
422 Unprocessable Entity Validation Error HTTPValidationError
To perform this operation, you must be authenticated by means of one of the following methods: OAuth2AuthorizationCodeBearer ( Scopes: openid ), OAuth2AuthorizationCodeBearer

whoami_whoami_get

Code samples

GET /whoami

Whoami

Example responses

200 Response

{
  "token": {},
  "success": true,
  "twofa_admin": true
}

Responses

Status Meaning Description Schema
200 OK Successful Response WhoAmIResponse
To perform this operation, you must be authenticated by means of one of the following methods: OAuth2AuthorizationCodeBearer ( Scopes: openid ), OAuth2AuthorizationCodeBearer

Schemas

HTTPValidationError

{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string"
    }
  ]
}

HTTPValidationError

Properties

Name Type Required Restrictions Description
detail [ValidationError] false none none

ListUserQuery

{
  "query": ""
}

ListUserQuery

Properties

Name Type Required Restrictions Description
query any false none Search for users matching this query

anyOf

Name Type Required Restrictions Description
» anonymous string false none none

or

Name Type Required Restrictions Description
» anonymous null false none none

ListUserResponse

{
  "users": [
    {
      "keycloak_internal_id": "string",
      "username": "string",
      "email": "string",
      "firstname": "string",
      "lastname": "string"
    }
  ],
  "succes": true,
  "detail": "string"
}

ListUserResponse

Properties

Name Type Required Restrictions Description
users [User] true none none
succes boolean true none none
detail string true none none

ResetResponse

{
  "success": true,
  "detail": "string",
  "resets_by_user": ""
}

ResetResponse

Properties

Name Type Required Restrictions Description
success boolean true none none
detail string true none none
resets_by_user object false none Map of usernames to reset counts
» additionalProperties integer false none none

ResetUsersRequest

{
  "user_ids": [
    "string"
  ]
}

ResetUsersRequest

Properties

Name Type Required Restrictions Description
user_ids [string] true none none

User

{
  "keycloak_internal_id": "string",
  "username": "string",
  "email": "string",
  "firstname": "string",
  "lastname": "string"
}

User

Properties

Name Type Required Restrictions Description
keycloak_internal_id string true none none
username string true none none
email any false none none

anyOf

Name Type Required Restrictions Description
» anonymous string false none none

or

Name Type Required Restrictions Description
» anonymous null false none none

continued

Name Type Required Restrictions Description
firstname any false none none

anyOf

Name Type Required Restrictions Description
» anonymous string false none none

or

Name Type Required Restrictions Description
» anonymous null false none none

continued

Name Type Required Restrictions Description
lastname any false none none

anyOf

Name Type Required Restrictions Description
» anonymous string false none none

or

Name Type Required Restrictions Description
» anonymous null false none none

ValidationError

{
  "loc": [
    "string"
  ],
  "msg": "string",
  "type": "string"
}

ValidationError

Properties

Name Type Required Restrictions Description
loc [anyOf] true none none

anyOf

Name Type Required Restrictions Description
» anonymous string false none none

or

Name Type Required Restrictions Description
» anonymous integer false none none

continued

Name Type Required Restrictions Description
msg string true none none
type string true none none

WhoAmIResponse

{
  "token": {},
  "success": true,
  "twofa_admin": true
}

WhoAmIResponse

Properties

Name Type Required Restrictions Description
token object true none none
success boolean true none none
twofa_admin boolean true none none

About

This container image provides an API and business logic to connect the 2FA Admin Frontend module to Keycloak.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 8