From e11ecd220e052b3c14ca99b59280c4385084d067 Mon Sep 17 00:00:00 2001 From: oskarhurst <48417811+oskarhurst@users.noreply.github.com> Date: Tue, 24 Jun 2025 13:16:45 -0700 Subject: [PATCH 01/11] Create CDA-testing.yml --- .github/workflows/CDA-testing.yml | 186 ++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 .github/workflows/CDA-testing.yml diff --git a/.github/workflows/CDA-testing.yml b/.github/workflows/CDA-testing.yml new file mode 100644 index 00000000..9d22a922 --- /dev/null +++ b/.github/workflows/CDA-testing.yml @@ -0,0 +1,186 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + integration-tests: + runs-on: ubuntu-latest + env: + APP_PORT: 8081 + DB_IMAGE: ghcr.io/hydrologicengineeringcenter/cwms-database/cwms/database-ready-ora-23.5:latest-dev + SCHEMA_INS_IMAGE: registry-public.hecdev.net/cwms/schema_installer:latest-dev + DATA_API_IMAGE: ghcr.io/usace/cwms-data-api:latest + KEYCLOAK_IMAGE: quay.io/keycloak/keycloak:19.0.1 + TRAEFIK_IMAGE: traefik:v3.3.3 + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + # If your DATA_API_IMAGE lives in a private registry: + #- name: Log in to Container Registry + # uses: docker/login-action@v2 + # with: + # registry: myregistry + # username: ${{ secrets.REG_USERNAME }} + # password: ${{ secrets.REG_PASSWORD }} + + - name: Create network & volumes + run: | + docker network create testnet || true + docker volume create oracle_data_1 || true + docker volume create auth_data || true + + - name: Start Oracle DB + run: | + docker run -d --name db --network testnet \ + -e ORACLE_PASSWORD=badSYSpassword \ + -e CWMS_PASSWORD=simplecwmspasswD1 \ + -e OFFICE_ID=HQ \ + -e OFFICE_EROC=s0 \ + -v oracle_data_1:/opt/oracle/oradata \ + -p 1525:1521 \ + $DB_IMAGE + + - name: Wait for Oracle + run: | + until nc -z localhost 1525; do + echo "waiting for oracle..." + sleep 10 + done + + - name: Run schema installer + run: | + docker run --rm --network testnet \ + -e DB_HOST_PORT=db:1521 \ + -e DB_NAME=/FREEPDB1 \ + -e CWMS_PASSWORD=simplecwmspasswD1 \ + -e SYS_PASSWORD=badSYSpassword \ + -e OFFICE_ID=HQ \ + -e OFFICE_EROC=s0 \ + -e INSTALLONCE=1 \ + -e QUIET=1 \ + -v $GITHUB_WORKSPACE/compose_files/sql:/setup_sql:ro \ + $SCHEMA_INS_IMAGE \ + sh -xc "sqlplus CWMS_20/$$CWMS_PASSWORD@$$DB_HOST_PORT$$DB_NAME @/setup_sql/users $$OFFICE_EROC" + + - name: Start Traefik + run: | + docker run -d --name traefik --network testnet \ + -p ${APP_PORT}:80 \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + $TRAEFIK_IMAGE \ + --entrypoints.web.address=:80 \ + --providers.docker=true \ + --providers.docker.exposedbydefault=false \ + --api.insecure=true \ + --ping + + - name: Start Keycloak + run: | + docker run -d --name auth --network testnet \ + -e KEYCLOAK_ADMIN=admin \ + -e KEYCLOAK_ADMIN_PASSWORD=admin \ + -e KC_HTTP_PORT=${APP_PORT} \ + -v $GITHUB_WORKSPACE/compose_files/keycloak/realm.json:/opt/keycloak/data/import/realm.json:ro \ + $KEYCLOAK_IMAGE \ + start-dev --features-disabled=admin2 --import-realm + + - name: Wait for Keycloak + run: | + until curl -sfI http://localhost:${APP_PORT}/auth/health/ready; do + echo "waiting for keycloak..." + sleep 5 + done + + - name: Pull Data-API image + run: docker pull $DATA_API_IMAGE + + - name: Start Data-API + run: | + docker run -d --name data-api --network testnet \ + -p 7000:7000 \ + -v $GITHUB_WORKSPACE/compose_files/pki/certs:/conf \ + -v $GITHUB_WORKSPACE/compose_files/tomcat/logging.properties:/usr/local/tomcat/conf/logging.properties:ro \ + -e CDA_JDBC_DRIVER=oracle.jdbc.driver.OracleDriver \ + -e CDA_JDBC_URL=jdbc:oracle:thin:@db/FREEPDB1 \ + -e CDA_JDBC_USERNAME=s0webtest \ + -e CDA_JDBC_PASSWORD=simplecwmspasswD1 \ + -e CDA_POOL_INIT_SIZE=5 \ + -e CDA_POOL_MAX_ACTIVE=10 \ + -e CDA_POOL_MAX_IDLE=5 \ + -e CDA_POOL_MIN_IDLE=2 \ + -e cwms.dataapi.access.provider=MultipleAccessManager \ + -e cwms.dataapi.access.providers=KeyAccessManager,OpenID \ + -e cwms.dataapi.access.openid.create_users=true \ + -e cwms.dataapi.access.openid.wellKnownUrl=http://auth:${APP_PORT}/auth/realms/cwms/.well-known/openid-configuration \ + -e cwms.dataapi.access.openid.altAuthUrl=http://localhost:${APP_PORT} \ + -e cwms.dataapi.access.openid.useAltWellKnown=true \ + -e cwms.dataapi.access.openid.issuer=http://localhost:${APP_PORT}/auth/realms/cwms \ + $DATA_API_IMAGE \ + bash -c "/conf/installcerts.sh && /usr/local/tomcat/bin/catalina.sh run" + + - name: Wait for Data-API + run: | + until curl -sfI http://localhost:7000/cwms-data/offices/HEC; do + echo "waiting for data-api..." + sleep 5 + done + + - name: Set Up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + # Unlike the code-check workflow, this job requires the dev dependencies to be + # installed to make sure we have the necessary, tools, stub files, etc. + - name: Install Poetry + uses: abatilo/actions-poetry@v4 + + - name: Cache Virtual Environment + uses: actions/cache@v4 + with: + path: ./.venv + key: venv-${{ hashFiles('poetry.lock') }} + + - name: Install Dependencies + run: poetry install + + # Run pytest and generate coverage report data. + - name: Run Tests + run: poetry run pytest tests/ --doctest-modules --cov --cov-report=xml:out/coverage.xml + + # Run mypy with strict mode enabled. Only the main source code is type checked (test + # and example code is excluded). + - name: Check Types + run: poetry run mypy --strict cwms/ + + - name: Generate Coverage Report + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: out/coverage.xml + format: markdown + output: both + badge: true + + - name: Generate Job Summary + uses: x-color/github-actions-job-summary@v0.1.1 + with: + file: ./code-coverage-results.md + vars: |- + empty: empty + + - name: Cleanup + if: always() + run: | + docker rm -f data-api auth traefik db || true + docker network rm testnet || true + docker volume rm oracle_data_1 auth_data || true \ No newline at end of file From a412616b4a5c49b5086adfa9a4b186f2cd81bf5d Mon Sep 17 00:00:00 2001 From: oskarhurst <48417811+oskarhurst@users.noreply.github.com> Date: Tue, 24 Jun 2025 13:24:51 -0700 Subject: [PATCH 02/11] Update CDA-testing.yml --- .github/workflows/CDA-testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CDA-testing.yml b/.github/workflows/CDA-testing.yml index 9d22a922..7baea4e9 100644 --- a/.github/workflows/CDA-testing.yml +++ b/.github/workflows/CDA-testing.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: [ main ] + branches: [ main, githubAction-testing ] pull_request: branches: [ main ] workflow_dispatch: From 42baf70c069f0967fa0a0185cd8256b356dfb886 Mon Sep 17 00:00:00 2001 From: oskarhurst <48417811+oskarhurst@users.noreply.github.com> Date: Tue, 24 Jun 2025 13:55:28 -0700 Subject: [PATCH 03/11] test run backend --- .github/workflows/CDA-testing.yml | 133 +- compose_files/keycloak/realm.json | 2324 +++++++++++++++++++++++ compose_files/keycloak/server.keystore | Bin 0 -> 3608 bytes compose_files/sql/users.sql | 32 + compose_files/tomcat/logging.properties | 45 + docker-compose.yml | 141 ++ 6 files changed, 2546 insertions(+), 129 deletions(-) create mode 100644 compose_files/keycloak/realm.json create mode 100644 compose_files/keycloak/server.keystore create mode 100644 compose_files/sql/users.sql create mode 100644 compose_files/tomcat/logging.properties create mode 100644 docker-compose.yml diff --git a/.github/workflows/CDA-testing.yml b/.github/workflows/CDA-testing.yml index 7baea4e9..eeca3831 100644 --- a/.github/workflows/CDA-testing.yml +++ b/.github/workflows/CDA-testing.yml @@ -10,130 +10,12 @@ on: jobs: integration-tests: runs-on: ubuntu-latest - env: - APP_PORT: 8081 - DB_IMAGE: ghcr.io/hydrologicengineeringcenter/cwms-database/cwms/database-ready-ora-23.5:latest-dev - SCHEMA_INS_IMAGE: registry-public.hecdev.net/cwms/schema_installer:latest-dev - DATA_API_IMAGE: ghcr.io/usace/cwms-data-api:latest - KEYCLOAK_IMAGE: quay.io/keycloak/keycloak:19.0.1 - TRAEFIK_IMAGE: traefik:v3.3.3 steps: - - name: Checkout - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - # If your DATA_API_IMAGE lives in a private registry: - #- name: Log in to Container Registry - # uses: docker/login-action@v2 - # with: - # registry: myregistry - # username: ${{ secrets.REG_USERNAME }} - # password: ${{ secrets.REG_PASSWORD }} - - - name: Create network & volumes - run: | - docker network create testnet || true - docker volume create oracle_data_1 || true - docker volume create auth_data || true - - - name: Start Oracle DB - run: | - docker run -d --name db --network testnet \ - -e ORACLE_PASSWORD=badSYSpassword \ - -e CWMS_PASSWORD=simplecwmspasswD1 \ - -e OFFICE_ID=HQ \ - -e OFFICE_EROC=s0 \ - -v oracle_data_1:/opt/oracle/oradata \ - -p 1525:1521 \ - $DB_IMAGE - - - name: Wait for Oracle - run: | - until nc -z localhost 1525; do - echo "waiting for oracle..." - sleep 10 - done - - - name: Run schema installer - run: | - docker run --rm --network testnet \ - -e DB_HOST_PORT=db:1521 \ - -e DB_NAME=/FREEPDB1 \ - -e CWMS_PASSWORD=simplecwmspasswD1 \ - -e SYS_PASSWORD=badSYSpassword \ - -e OFFICE_ID=HQ \ - -e OFFICE_EROC=s0 \ - -e INSTALLONCE=1 \ - -e QUIET=1 \ - -v $GITHUB_WORKSPACE/compose_files/sql:/setup_sql:ro \ - $SCHEMA_INS_IMAGE \ - sh -xc "sqlplus CWMS_20/$$CWMS_PASSWORD@$$DB_HOST_PORT$$DB_NAME @/setup_sql/users $$OFFICE_EROC" - - - name: Start Traefik - run: | - docker run -d --name traefik --network testnet \ - -p ${APP_PORT}:80 \ - -v /var/run/docker.sock:/var/run/docker.sock:ro \ - $TRAEFIK_IMAGE \ - --entrypoints.web.address=:80 \ - --providers.docker=true \ - --providers.docker.exposedbydefault=false \ - --api.insecure=true \ - --ping - - - name: Start Keycloak - run: | - docker run -d --name auth --network testnet \ - -e KEYCLOAK_ADMIN=admin \ - -e KEYCLOAK_ADMIN_PASSWORD=admin \ - -e KC_HTTP_PORT=${APP_PORT} \ - -v $GITHUB_WORKSPACE/compose_files/keycloak/realm.json:/opt/keycloak/data/import/realm.json:ro \ - $KEYCLOAK_IMAGE \ - start-dev --features-disabled=admin2 --import-realm - - - name: Wait for Keycloak - run: | - until curl -sfI http://localhost:${APP_PORT}/auth/health/ready; do - echo "waiting for keycloak..." - sleep 5 - done - - - name: Pull Data-API image - run: docker pull $DATA_API_IMAGE - - - name: Start Data-API - run: | - docker run -d --name data-api --network testnet \ - -p 7000:7000 \ - -v $GITHUB_WORKSPACE/compose_files/pki/certs:/conf \ - -v $GITHUB_WORKSPACE/compose_files/tomcat/logging.properties:/usr/local/tomcat/conf/logging.properties:ro \ - -e CDA_JDBC_DRIVER=oracle.jdbc.driver.OracleDriver \ - -e CDA_JDBC_URL=jdbc:oracle:thin:@db/FREEPDB1 \ - -e CDA_JDBC_USERNAME=s0webtest \ - -e CDA_JDBC_PASSWORD=simplecwmspasswD1 \ - -e CDA_POOL_INIT_SIZE=5 \ - -e CDA_POOL_MAX_ACTIVE=10 \ - -e CDA_POOL_MAX_IDLE=5 \ - -e CDA_POOL_MIN_IDLE=2 \ - -e cwms.dataapi.access.provider=MultipleAccessManager \ - -e cwms.dataapi.access.providers=KeyAccessManager,OpenID \ - -e cwms.dataapi.access.openid.create_users=true \ - -e cwms.dataapi.access.openid.wellKnownUrl=http://auth:${APP_PORT}/auth/realms/cwms/.well-known/openid-configuration \ - -e cwms.dataapi.access.openid.altAuthUrl=http://localhost:${APP_PORT} \ - -e cwms.dataapi.access.openid.useAltWellKnown=true \ - -e cwms.dataapi.access.openid.issuer=http://localhost:${APP_PORT}/auth/realms/cwms \ - $DATA_API_IMAGE \ - bash -c "/conf/installcerts.sh && /usr/local/tomcat/bin/catalina.sh run" - - - name: Wait for Data-API - run: | - until curl -sfI http://localhost:7000/cwms-data/offices/HEC; do - echo "waiting for data-api..." - sleep 5 - done + - name: set up backend + run: docker compose up -d - name: Set Up Python uses: actions/setup-python@v5 @@ -176,11 +58,4 @@ jobs: with: file: ./code-coverage-results.md vars: |- - empty: empty - - - name: Cleanup - if: always() - run: | - docker rm -f data-api auth traefik db || true - docker network rm testnet || true - docker volume rm oracle_data_1 auth_data || true \ No newline at end of file + empty: empty \ No newline at end of file diff --git a/compose_files/keycloak/realm.json b/compose_files/keycloak/realm.json new file mode 100644 index 00000000..d0d283be --- /dev/null +++ b/compose_files/keycloak/realm.json @@ -0,0 +1,2324 @@ +{ + "id": "cwms", + "realm": "cwms", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "none", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "2308cab1-3e1d-4c42-9b4f-cb374ec38d46", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "cwms", + "attributes": {} + }, + { + "id": "d078ae10-0db8-4e2c-97bb-781fc48a551a", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "cwms", + "attributes": {} + }, + { + "id": "aba4c724-1b23-453f-92fd-0776139e0709", + "name": "cwms_user", + "composite": false, + "clientRole": false, + "containerId": "cwms", + "attributes": {} + }, + { + "id": "14213fd3-a4ca-421d-807f-ddad55cd61eb", + "name": "default-roles-cwms", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "view-profile", + "manage-account" + ] + } + }, + "clientRole": false, + "containerId": "cwms", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "135356ef-c109-42b5-8d93-3b81be4304e5", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "a3ab9297-9b01-4d0a-9835-896e84c7e543", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "80c4c89b-7c9d-42b9-aa01-53a52b1611dd", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "41462145-efa3-44ef-990c-4b8614a48698", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-groups", + "query-users" + ] + } + }, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "57196c95-7c33-4031-beaf-859591c81b90", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "ae7a76dc-6871-4bf1-aad0-4bce5ea7d738", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "3164df3b-0cdb-4c31-8bd5-c2f95a870772", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "view-authorization", + "manage-clients", + "view-users", + "manage-realm", + "query-clients", + "query-realms", + "view-clients", + "query-users", + "view-events", + "create-client", + "manage-events", + "manage-authorization", + "impersonation", + "view-realm", + "manage-identity-providers", + "query-groups", + "view-identity-providers", + "manage-users" + ] + } + }, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "18ed216b-6f07-4135-a512-8d7c47be5cfe", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "73dd843a-cfaa-4d8f-af20-8d2b018bdec5", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "0d7aeeed-bcba-481e-8eb4-c17d9882aeca", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "08101efe-b9e2-4d1f-aee3-8f408955cb1c", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "919d1923-51d2-4a25-b4c9-97959074ea60", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "287230b0-ff6c-4918-b7f6-c3406066eaf2", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "bf31a235-595f-4a49-9db3-747af55b198a", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "371b2aad-023a-4dd8-b685-01f775edff9e", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "3532600f-94e2-4e2c-b1f2-83d824bf7712", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "015daddb-0485-4f84-933a-eeec1c46bf63", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "77061713-3092-4d6e-acc5-10b09b36b56e", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + }, + { + "id": "2bab7b7e-dfda-4a00-901e-546a1cebc0da", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "attributes": {} + } + ], + "cwms": [ + { + "id": "a0a7867b-d2e5-4d69-97b8-f31a35ada3ab", + "name": "cwms_user", + "composite": false, + "clientRole": true, + "containerId": "4f792bf7-8603-4bb4-8c8a-1a1eb8fb1855", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "30fd6e01-203f-43fc-896c-7e3569b3a550", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "16fe0e33-38bc-4354-9b58-de1603968ca6", + "attributes": {} + } + ], + "account": [ + { + "id": "66a43c26-614b-4359-8ea6-be5f123ad080", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "c66da339-f384-4b66-84de-0bf0e90bef30", + "attributes": {} + }, + { + "id": "8d7a8740-4257-469a-be02-bf43cc1b4c7a", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "c66da339-f384-4b66-84de-0bf0e90bef30", + "attributes": {} + }, + { + "id": "1393e732-38cd-46d4-a551-69efde900831", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "c66da339-f384-4b66-84de-0bf0e90bef30", + "attributes": {} + }, + { + "id": "ddb6eff3-2f87-4f26-9462-d98ec15a8f21", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "c66da339-f384-4b66-84de-0bf0e90bef30", + "attributes": {} + }, + { + "id": "679393d4-0ca3-4da6-856b-c9e814fbaa1f", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "c66da339-f384-4b66-84de-0bf0e90bef30", + "attributes": {} + }, + { + "id": "340030d7-272f-4134-94c9-2a4b38af1a3c", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "c66da339-f384-4b66-84de-0bf0e90bef30", + "attributes": {} + }, + { + "id": "0da73bcb-4583-45bc-b6dd-f6738ae8e012", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "c66da339-f384-4b66-84de-0bf0e90bef30", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "14213fd3-a4ca-421d-807f-ddad55cd61eb", + "name": "default-roles-cwms", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "cwms" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account" + ] + } + ] + }, + "clients": [ + { + "id": "c66da339-f384-4b66-84de-0bf0e90bef30", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/cwms/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/cwms/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "a87fb54a-a965-4ef0-83d7-6972374a0ed7", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/cwms/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/cwms/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "9f9d082e-4cb5-41bf-869b-2d8d954eebb1", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "d53a9c38-edf5-4b9f-b1cb-452cc23950d8", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "16fe0e33-38bc-4354-9b58-de1603968ca6", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "4f792bf7-8603-4bb4-8c8a-1a1eb8fb1855", + "clientId": "cwms", + "name": "CWMS", + "description": "Realm for CWMS Authentication", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": true, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "https://cwms-data.test:8444/*", + "https://localhost:5010/*" + ], + "webOrigins": [ + "*" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "frontchannel.logout.session.required": "false", + "oauth2.device.authorization.grant.enabled": "true", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "use.refresh.tokens": "true", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "require.pushed.authorization.requests": "false", + "saml.client.signature": "false", + "saml.allow.ecp.flow": "false", + "id.token.as.detached.signature": "false", + "saml.assertion.signature": "false", + "client.secret.creation.time": "1682974463", + "saml.encrypt": "false", + "login_theme": "keycloak", + "saml.server.signature": "false", + "exclude.session.state.from.auth.response": "false", + "saml.artifact.binding": "false", + "saml_force_name_id_format": "false", + "acr.loa.map": "{}", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email", + "subjectDN" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "f09103f0-5563-4dc0-bc70-2ff0309edae7", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "24f41188-c865-418a-b538-3aa5672b4fc9", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/cwms/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/cwms/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "f9778bb3-96cd-4e81-9a6a-8b9e1b2933d2", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "338c4408-7cd0-49e1-8a25-709bd14e48f1", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "4f8e64b6-59da-46a7-84ea-b18cb51268ed", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "1bf5026a-dadf-445e-af68-fe1bde17ad54", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "23729f22-4922-473d-ac67-ff3b103be22b", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "6af8af80-3c0f-4eb0-9136-35d53e5018c5", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "45df9dcc-91db-427f-a36d-7811305675fc", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "ef76d98d-5d52-4ee9-8705-362aec63562e", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "fedb6d7e-ec3e-43f7-a8e6-cdabcaf6aeb3", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "53795e7a-b308-449d-87d7-0ac542457b36", + "name": "subjectDN", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + } + }, + { + "id": "30728b88-cb96-4fb6-809d-ab340682db09", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "5ce03fce-a6d0-420d-bb4f-b3d4ae495435", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "afac43ea-170d-4abe-b877-98cc0284353b", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "11ffa367-1ebf-4036-b90e-a15df3a7def1", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "8f36cb35-b3d7-4d41-91ce-7684ea0081e9", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "6b84c205-4bf9-418c-a310-126334e100f8", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "c94681e1-3330-49e2-b875-b18f8e0e8874", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "e49d13b0-5b9d-448c-b9f6-dfc2dd4c1c92", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "15261047-da5a-4335-bfc9-53d08a881ba8", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "133f7d35-f187-4f49-b5c5-04b732225f28", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "8f22d2e9-1d8d-4f2c-80a2-40ef037998bb", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "3eeac6b7-25b8-4791-b252-5b36a1bfb43e", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "1e64a79a-e1b8-48b1-a179-aea045c0b08c", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "5bd1ba07-df3f-4943-b047-7a1715303f4c", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "ac344111-927d-41b9-b031-3df63a2346b9", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "ba73aa53-2045-4abb-90b3-5942fccab296", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "355b4486-40a2-47a7-8bb3-238601048bbd", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "c36bf63e-09ae-4e37-9704-d35d82b74374", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "ba4d6483-9384-4cf3-826a-f5324f7d8c5c", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "78ab0836-3364-491a-ac46-9e177cc7f5e6", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "a2a94285-8be3-4703-a6ca-6fe4a761c5cc", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "13e91fc0-cc6d-442e-b362-b344c6eb1a60", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "7857b239-a638-4632-a081-93aa71ee7edd", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "67d61c51-4f6a-4874-bf57-0687f8a0ba99", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "0c4150ae-593e-4ad1-a43c-be0300d2f42e", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "5cacf69a-2f05-4f30-b2d4-9985e6feb049", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "7f5b1698-2ed3-4c0a-b657-cfa518e34364", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "86d02213-316f-46fb-8abb-33e14a8ca2e3", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "9c40b197-9218-43f3-a7e1-17d301dc6901", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "loginTheme": "keycloak", + "accountTheme": "keycloak.v2", + "adminTheme": "keycloak", + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "94a49785-0241-4e6e-b4f4-a12e671e321d", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "317f2bf6-4e13-45cb-bc0e-96ff59b8d84c", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "be6165bf-87c9-46be-9e59-37222e50caff", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "1a18796f-fa08-43c4-a697-9c3fbed24fac", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "saml-role-list-mapper", + "oidc-address-mapper", + "oidc-usermodel-property-mapper" + ] + } + }, + { + "id": "8ef3e217-7d1d-4822-8512-4d4e60ab9bba", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-property-mapper", + "oidc-address-mapper", + "oidc-usermodel-attribute-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-user-attribute-mapper", + "oidc-full-name-mapper" + ] + } + }, + { + "id": "d1b41e82-d378-42b7-93c3-4f11870dc14f", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "83be8aca-f4a6-4680-9296-8d1a436eea13", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "5c0388fb-fbf0-4b39-a7fb-b27d1d64adc2", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "a548ade8-f810-4dc4-abe1-45efa3059fe7", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "ba5c9f13-f016-4d23-8103-3837f5ea7fd9", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "3f33f8eb-c34a-4220-ae78-ebf3371628bd", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "51311a86-d3fe-4921-b30a-8da2fb890a55", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + }, + { + "id": "df3ad144-f563-4aa8-9724-2f85a73366fa", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [ + "" + ], + "authenticationFlows": [ + { + "id": "9c3feead-3ba8-4537-b305-2b2bbb2d2678", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "549c6436-3f7b-46ff-8ab3-c2489df7a541", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "31cba680-e664-41eb-8d57-86c9b9b88dfb", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "03c3da3d-2bcc-4c7d-9274-69e94f9c2423", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "8f384ddb-4d0b-40c5-8f94-298e7fb0868d", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "49cadd8b-662c-4eee-a171-941c0d4d67a5", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "0da4d6a9-d350-4492-81d4-45fbb0ebc61b", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "f65b50fc-3289-4404-aa5b-6342f7e3b4b7", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "c46843a8-846d-41e0-acf4-7491bb53996f", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "19a10bf2-8a62-44fa-bbd7-6ccbbcd647b9", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "d4ee668d-0d67-46bb-be98-ba6f7b57437d", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "b10f47ad-717e-4319-91b4-da8b39e4b91f", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "d6cd190c-9a68-4a95-892e-c1b364f1fc8d", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "f6e96c5a-0a30-49ef-a6f1-efa47bb7b124", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "34a68bda-b9e8-4499-accf-db69644684cf", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "1cc6168f-f446-4aba-8788-4e3aa9e03b7e", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Authentication Options", + "userSetupAllowed": false + } + ] + }, + { + "id": "09e19635-3288-4cb9-9f95-a5efd96ae729", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "f7eb17f9-e584-4a61-b59f-6499e8434132", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "425cbff0-650b-415e-9cd3-af7ec33f0fd0", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "cd2a5a9a-cf87-4d65-99bb-922a05156474", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "88a20417-2c6e-4919-b9fc-3067ef14215b", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "40e73c4f-d6da-49af-8d37-724a49727f7f", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "clientOfflineSessionMaxLifespan": "0", + "clientSessionIdleTimeout": "0", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5" + }, + "keycloakVersion": "19.0.1", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + }, + "users": [ + { + "username": "l2hectest.1234567890", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "l2hectest" + } + ], + "realmRoles": [ + "cwms_user" + ] + }, + { + "username": "l1hectest", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "l1hectest" + } + ], + "realmRoles": [ + "cwms_user" + ] + }, + { + "username": "m5hectest", + "enabled": true, + "email": "noreply@data.test", + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "m5hectest" + } + ], + "realmRoles": [ + "cwms_user" + ] + }, + { + "username": "q0hecoidc", + "enabled": true, + "email": "noreply-oidc@data.test", + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "q0hecoidc" + } + ], + "realmRoles": [ + "cwms_user", + "new_user" + ] + } + ] +} \ No newline at end of file diff --git a/compose_files/keycloak/server.keystore b/compose_files/keycloak/server.keystore new file mode 100644 index 0000000000000000000000000000000000000000..def845c2b40a4ed10056d081221abc665862caac GIT binary patch literal 3608 zcma)9c{CJ^*Pa>77_x@!*}WRZU~Jj56G=^FDa+Wil-)G83E8rRLY9oIMbt2MAxzXD z`!Zx?iI;@N?&mw-ci#7We|_H{_uTV5&%O8DzwUD`0>{b)0@5LHENx)ur8tu~8Ve8t zEW)wW(BoLD&ge1(4s!CpB*-Q`4zhMeFP{Y!%=|xH=a_(?A{->@jKm_`{^?*~LD(SF z{)OidFnTVS8QUtW6`s1m;O*h#Nq%t0rAiqDl4b`1MG)t}jQ>3m1Z4mqguqb8I1`{N z9X;?8J^M*k8l}VmeO+R3So>2?BIns6?3%KLN0#Baio0;BEv~6@F(rhLZmuPPT+<#A z#7JlL=9*x-5K&HJcH#40ZM1u4SGZUFD2mjJ_cYWNbxYCtEz2O8IbJ}`@88en{wnH+ zY#LtDd1zx(9#GR5)THLLzs?jM$$Fb~2={>gTPGRGL z{Pl(vx?fC3VQNqBS|*Ui#WcUqlF4ql278*%Mx~YyjJInz>Iys*G@qVt7XDQ#0WSnL zb-&K}1sE{R=4IYYt6q-mqJ9%SB=CI;7;Ef%8EgyZ!GZ zPs}xkGw5%YaEmQ?ObJrq)I+=YISz|PW&O<8CTO?cA;XDu5!U^&jN1$FP;)%#ZSpBf zIL@(fk$aF4kQ{Z8hIAzqKAaaUZgikR z!yU&*L2DiDs(iX+|8&1Wy$D7NU0>qJ^O7*Wb4D}837G_mN@2;|x~Ds#Yk6`P*13{Z zCB{A7+|KDk&>B-eZ%f$FdE-SkFrvE2X$+o z-e)SsZdrj+RcE=jpO14)GB)OWPlC-{? zXiX5Lt%OOqf7ne?w0+SC;`x4p3u9v&uPStiKE0W$lM=g~X3PSAD9O9*>%;_AJK6X0-DJG} z+dG~zV6JUc9}_l?=;EVowsYiWSpF2mBMk@mx zBpuMAv8SN8PRs`}`bT^=So=3OE)i-jG?&fJr!O4>sJfokS7Z;(K341UQ4CoFy-) zI_rr&#Xi5m*6`h$=W_gL1yz0Vu~jRbSwk!|srVTebEeG`0iTZtk4$DU_yx4L2?Nax zf}B}PvoJ1fcoB|F;&@PFP~^JLQl;)DS^0h*LmxW6)@m%9;^Ao;TR5Y~DOpX~tX6GJ z;?;iHbxeDwdR$e`IX`phkCm;~%4m)sO>&IWED8Vi8VwkVc2#5DG);${c-*9R)RqL@ zbsD#R+3>CDd%y#TSCkCR8C)MlLc1G9s@k7;aE_D(IB!?w{2;?FnA?c6SJNdZBYL9c zN?7*WBf1%OYXKMQOt_>Jdo?PSQ|@XYCQ~2rz}TMNbpImJjLm6RvT=vUv5$w?i&pBxUvH+P82a5<6)zOCR$kta#RcTr|DFDPjQ{1Gv#z zvh~oOhG8Q6?jL909z2!EMf`*V@GW<)GJK?K-lrkqdW?07@iZcr2CsO^>EAL#Z!yVb zzSHmcbktQ(0Tqjhmqt_4C{TMO$w9^ohH>Y$Zp*9iGq8a{+g{9S=|Pf=#1v-dtitb@ z*l@5*Cay+g@LUs0d2v=P=U1QS#zRwv~)Dx36>?U4@~KqW5s1J_pK`aBTQYHZvbH{8MW)en*Yat5DeV zylVI2Zt)D87LVzUi#EZ@1oy!Tp!2==oJw{1?X9jW>=W_hK- z&Z@mBMYuZsVs0?Wui?}>m`m?-eC<4o3|=N;H&Y(1r(SCIr^UDsb0gFr+lw?|s;qd; zUvST=?nJw#*JDQX=!L}kVBuE1yNjr>rtIP5Dg2X>^=_?#5tz|?KR=Ax(NthN(rvq* zLI*R*e%lA=N|9@;BtYhxzn+CyuZT{srr|dZug=1h}nh)Q1aZf_MVo8*tL1Kq zba1el7XydJzC@?2T{=qmS&y;F`Z69s37%H_%V&=xraa@DIg^T(^pDGb&rBLt{=Q~F zm)Y;~wj)@sjoG(6-|I&4d8`DAvY-^wl)Mg~-uO7r-oRp^ry70_h!~0B@q%^-U@a_D zpOq>3OVCH!v+Rh=)Y6YK&84Xk%_*A1^y`b48%_Cr2jFj|*Mt061R7r@4iD@YXM5_5 z5Ko9X8{*<3_fSwtZx!!o?6<$yxCw_kTJd?EJ+}msDQNia?7GKPsD~2nWq0Ko$rY%f zeM+6XayR2j8vTuGOQhtyzVT70L8U(l`1L)+L?LQ(^+W=lM0~t{^^j0ZaY+P;&51sUP#=S(hnJxe2{Ut(}M48f#c=+3A45l9#IU3U( z;TOrqrx-;VgL!x78vt6P0e}R_sIBEtB7ef-c^`gQs+sboAIEAvMmLWRt{;W3m!3DU zc77Bqn#P$5_KYD$NGb)Jl=qG2HazrY38)>Ewc0{TR)@|}?9HKF+qY%}ZVAh01A~fh zvQLSbh5ZpUK(aE1W_CItHFC6=iyh5u#ZYv(h29T-!oAMPSkRY#Ijm09jZsPOEYpYl zpeG1-JjS47Z8}Ym5?Tk{Rj=SwiDJ#8aHm=_t>J z{PLK=1t%ZoADMusQj)eb+6J|@AG1Jjkk_${P9wa(gm57gIjM%y{b)PgzVTaAhtc=g zsEz(dEsd#O7u9?7{N5-<7O8sWe^4>IY@%|WbIS!qk_KrU_r=dyPE$zS)+oM0o5J#w zSXFC>UdmvNvh|Looi*kKKdI!}CYcSz<}`X{rq(R?7=AnD;_f+LBLtylAASy(GTIrV z3#y{rW=);=RomXh`KR+*fB8$gZgx~YXqWBtRV{C_C@E5=+Te4Jy^{#i@$;i;x0N3{ ztA*4du_g7m(W13fkF1#|B9nBbZmfCl!u`P!%SX-j3A_^aoQBs}c`(NG?yZ@;G!^G_ z;7{11g`iYItlg})kEV(C(!^@i?FywotpI9zbcZF;U8<0*)kBaM0x!8o^a!>bkOJhL z1z-&Zz0(5%<3m%u=i$b+=@jyXH20bE7kyWVe4WP-$g64>=VIRr?=@o}-)%xNChjwi zKfezL=PtX7yuPbH7Ml-xn{h98Q@EY!`zW|XZl)qyLxvi7RCfR^rN(cXs_G!Ckp|+vIgo{W^u^TQ6f1bqE1NOgu+}}TOZ@~C zSdqNCF+LdE+*#Qnl}A24s%Q{37AW{YnD}DIc~_w9>(Da~Hp3TPuKD|sF)D;}5T9e{ zWNRGHi+LQMG53RSnN>r1`n_@ubw3Vb91f}v6F3sy1SvBgxLvg$yb^|vdgST4G6)o@`^Kv%^fv>Kfl^#gpczjwS(JZ~Zg zv$Np7)67|_R>gOk$<*E%5B_qeCZsGaiEnfA;Fr>Ed4vT*0m1Umw*&$*0O&>H)AjXk zk6d`-RMs7Q8M%(X!pSAx0}JN{HH}0HS32S8x#Z)FXx?&P8~Iz&tF5*D7gg&l68`HX F{|BEBnehMs literal 0 HcmV?d00001 diff --git a/compose_files/sql/users.sql b/compose_files/sql/users.sql new file mode 100644 index 00000000..d3693e8e --- /dev/null +++ b/compose_files/sql/users.sql @@ -0,0 +1,32 @@ +set define on +define OFFICE_EROC=&1 +begin + cwms_sec.add_user_to_group('&&OFFICE_EROC.webtest','All Users', 'HQ'); + cwms_sec.add_user_to_group('&&OFFICE_EROC.webtest','All Users', 'SPK'); + cwms_sec.add_user_to_group('&&OFFICE_EROC.webtest','CWMS Users', 'HQ'); + cwms_sec.add_user_to_group('&&OFFICE_EROC.webtest','CWMS User Admins', 'HQ'); + + + cwms_sec.add_cwms_user('l2hectest',NULL,'SPK'); + cwms_sec.update_edipi('l2hectest',1234567890); + cwms_sec.add_user_to_group('l2hectest','All Users', 'SPK'); + cwms_sec.add_user_to_group('l2hectest','CWMS Users', 'SPK'); + cwms_sec.add_user_to_group('l2hectest','TS ID Creator','SPK'); + + cwms_sec.add_cwms_user('l1hectest',NULL,'SPL'); + -- intentionally no extra permissions. + --cwms_sec.add_user_to_group('l2hectest','CWMS Users', 'SPL'); + + cwms_sec.add_cwms_user('m5hectest',NULL,'SWT'); + cwms_sec.add_user_to_group('m5hectest','All Users', 'SWT'); + cwms_sec.add_user_to_group('m5hectest','CWMS Users', 'SWT'); + execute immediate 'grant execute on cwms_20.cwms_upass to web_user'; + + + cwms_sec.add_user_cwms('m5testadmin', NULL, 'LRL'); + cwms_sec.add_user_to_group('m5testadmin','All Users', 'LRL'); + cwms_sec.add_user_to_group('m5testadmin','CWMS Users', 'LRL'); + cwms_sec.add_user_to_group('m5testadmin','CWMS User Admins', 'LRL'); +end; +/ +quit; \ No newline at end of file diff --git a/compose_files/tomcat/logging.properties b/compose_files/tomcat/logging.properties new file mode 100644 index 00000000..9e69faec --- /dev/null +++ b/compose_files/tomcat/logging.properties @@ -0,0 +1,45 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +handlers = java.util.logging.ConsoleHandler + +.handlers = java.util.logging.ConsoleHandler + +############################################################ +# Handler specific properties. +# Describes specific configuration info for Handlers. +############################################################ + +java.util.logging.ConsoleHandler.level = FINE +java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter +java.util.logging.ConsoleHandler.encoding = UTF-8 + + +############################################################ +# Facility specific properties. +# Provides extra control for each logger. +############################################################ + +org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = FINE +org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = java.util.logging.ConsoleHandler + +# For example, set the org.apache.catalina.util.LifecycleBase logger to log +# each component that extends LifecycleBase changing state: +org.apache.catalina.util.LifecycleBase.level = INFO +org.apache.catalina.util.LifecycleBase.handlers = java.util.logging.ConsoleHandler + +org.apache.tomcat.jdbc.level = INFO +org.apache.tomcat.jdbc.handlers = java.util.logging.ConsoleHandler +cwms.cda.security.level = FINE diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..b40d6de9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,141 @@ +volumes: + oracle_data_1: + auth_data: +services: + db: + image: ghcr.io/hydrologicengineeringcenter/cwms-database/cwms/database-ready-ora-23.5:latest-dev + environment: + #- ORACLE_DATABASE=FREEPDB1 + - ORACLE_PASSWORD=badSYSpassword + - CWMS_PASSWORD=simplecwmspasswD1 + - OFFICE_ID=HQ + - OFFICE_EROC=s0 + ports: + - "1525:1521" + healthcheck: + test: ["CMD","tnsping", "FREEPDB1"] + interval: 30s + timeout: 50s + retries: 50 + start_period: 40m + db_webuser_permissions: + image: ${CWMS_SCHEMA_INSTALLER_IMAGE:-registry-public.hecdev.net/cwms/schema_installer:latest-dev} + restart: "no" + environment: + - DB_HOST_PORT=db:1521 + - DB_NAME=/FREEPDB1 + - CWMS_PASSWORD=simplecwmspasswD1 + - SYS_PASSWORD=badSYSpassword + # set to HQ/q0 for any national system work + - OFFICE_ID=HQ + - OFFICE_EROC=s0 + - INSTALLONCE=1 + - QUIET=1 + command: > + sh -xc "sqlplus CWMS_20/$$CWMS_PASSWORD@$$DB_HOST_PORT$$DB_NAME @/setup_sql/users $$OFFICE_EROC" + volumes: + - ./compose_files/sql:/setup_sql:ro + depends_on: + db: + condition: service_healthy + + + data-api: + depends_on: + auth: + condition: service_healthy + db: + condition: service_healthy + db_webuser_permissions: + condition: service_completed_successfully + traefik: + condition: service_healthy + image: ${CWMS_DATA_API_IMAGE:-ghcr.io/usace/cwms-data-api:latest} + restart: unless-stopped + volumes: + - ./compose_files/pki/certs:/conf/ + - ./compose_files/tomcat/logging.properties:/usr/local/tomcat/conf/logging.properties:ro + environment: + - CDA_JDBC_DRIVER=oracle.jdbc.driver.OracleDriver + - CDA_JDBC_URL=jdbc:oracle:thin:@db/FREEPDB1 + - CDA_JDBC_USERNAME=s0webtest + - CDA_JDBC_PASSWORD=simplecwmspasswD1 + - CDA_POOL_INIT_SIZE=5 + - CDA_POOL_MAX_ACTIVE=10 + - CDA_POOL_MAX_IDLE=5 + - CDA_POOL_MIN_IDLE=2 + - cwms.dataapi.access.provider=MultipleAccessManager + - cwms.dataapi.access.providers=KeyAccessManager,OpenID + - cwms.dataapi.access.openid.create_users=true + - cwms.dataapi.access.openid.wellKnownUrl=http://auth:${APP_PORT:-8081}/auth/realms/cwms/.well-known/openid-configuration + - cwms.dataapi.access.openid.altAuthUrl=http://localhost:${APP_PORT:-8081} + - cwms.dataapi.access.openid.useAltWellKnown=true + - cwms.dataapi.access.openid.issuer=http://localhost:${APP_PORT:-8081}/auth/realms/cwms + expose: + - 7000 + healthcheck: + test: ["CMD","/usr/bin/curl", "-I","localhost:7000/cwms-data/offices/HEC"] + interval: 5s + timeout: 1s + retries: 100 + start_period: 2s + labels: + - "traefik.enable=true" + - "traefik.http.routers.data-api.rule=PathPrefix(`/cwms-data`)" + - "traefik.http.routers.data-api.entryPoints=web" + + auth: + image: quay.io/keycloak/keycloak:19.0.1 + command: ["start-dev", "--features-disabled=admin2","--import-realm"] + healthcheck: + test: "/usr/bin/curl -If localhost:${APP_PORT:-8081}/auth/health/ready || exit 1" + interval: 5s + timeout: 1s + retries: 100 + start_period: 2s + environment: + - KEYCLOAK_ADMIN=admin + - KEYCLOAK_ADMIN_PASSWORD=admin + - KC_HEALTH_ENABLED=true + - KC_FEATURES=admin-fine-grained-authz + - KC_HTTP_PORT=${APP_PORT:-8081} + - KEYCLOAK_FRONTEND_URL=http://localhost:${APP_PORT:-8081} + - KC_HOSTNAME_STRICT=false + - KC_PROXY=none + - KC_HTTP_ENABLED=true + - KC_HTTP_RELATIVE_PATH=/auth + volumes: + - ./compose_files/keycloak/realm.json:/opt/keycloak/data/import/realm.json:ro + labels: + - "traefik.enable=true" + - "traefik.http.routers.auth.rule=PathPrefix(`/auth`)" + - "traefik.http.routers.auth.entryPoints=web" + - "traefik.http.services.auth.loadbalancer.server.port=${APP_PORT:-8081}" + depends_on: + traefik: + condition: service_healthy + + + + # Proxy for HTTPS for OpenID + traefik: + image: "traefik:v3.3.3" + ports: + - "${APP_PORT:-8081}:80" + expose: + - "8080" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + healthcheck: + test: traefik healthcheck --ping + command: + - "--entrypoints.web.address=:80" + - "--api.insecure=true" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--ping" + - "--log.level=DEBUG" + labels: + - "traefik.enable=true" + - "traefik.http.routers.traefik.rule=PathPrefix(`/traefik`)" + - "traefik.http.routers.traefik.service=api@internal" \ No newline at end of file From e79d6e77288a7063870508d4292ea998826b4cbb Mon Sep 17 00:00:00 2001 From: oskarhurst <48417811+oskarhurst@users.noreply.github.com> Date: Tue, 24 Jun 2025 13:59:02 -0700 Subject: [PATCH 04/11] Update docker-compose.yml --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index b40d6de9..c008a803 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: retries: 50 start_period: 40m db_webuser_permissions: - image: ${CWMS_SCHEMA_INSTALLER_IMAGE:-registry-public.hecdev.net/cwms/schema_installer:latest-dev} + image: ghcr.io/hydrologicengineeringcenter/cwms-database/cwms/schema_installer:latest-dev restart: "no" environment: - DB_HOST_PORT=db:1521 From aa9aca3662ecf95cdea5647a3088f58aacf965f1 Mon Sep 17 00:00:00 2001 From: oskarhurst <48417811+oskarhurst@users.noreply.github.com> Date: Tue, 24 Jun 2025 14:08:01 -0700 Subject: [PATCH 05/11] Update docker-compose.yml --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index c008a803..4c5c7e4c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,7 +50,7 @@ services: condition: service_completed_successfully traefik: condition: service_healthy - image: ${CWMS_DATA_API_IMAGE:-ghcr.io/usace/cwms-data-api:latest} + image: ${CWMS_DATA_API_IMAGE:-ghcr.io/usace/cwms-data-api:latest-dev} restart: unless-stopped volumes: - ./compose_files/pki/certs:/conf/ @@ -119,7 +119,7 @@ services: # Proxy for HTTPS for OpenID traefik: - image: "traefik:v3.3.3" + image: traefik:v3.3.3 ports: - "${APP_PORT:-8081}:80" expose: From db655f2f8ce867f0d430d92c374abdd15ad2a105 Mon Sep 17 00:00:00 2001 From: oskarhurst <48417811+oskarhurst@users.noreply.github.com> Date: Tue, 24 Jun 2025 14:30:15 -0700 Subject: [PATCH 06/11] add a test to back end --- compose_files/sql/users.sql | 3 +++ tests/locations/location_operations_test.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/locations/location_operations_test.py diff --git a/compose_files/sql/users.sql b/compose_files/sql/users.sql index d3693e8e..1e66eb79 100644 --- a/compose_files/sql/users.sql +++ b/compose_files/sql/users.sql @@ -27,6 +27,9 @@ begin cwms_sec.add_user_to_group('m5testadmin','All Users', 'LRL'); cwms_sec.add_user_to_group('m5testadmin','CWMS Users', 'LRL'); cwms_sec.add_user_to_group('m5testadmin','CWMS User Admins', 'LRL'); + + insert into cwms_20.at_api_keys (userid, key_name, apikey) values ('L2HECTEST', 'testkey', '1234567890abcdef1234567890abcdef'); + end; / quit; \ No newline at end of file diff --git a/tests/locations/location_operations_test.py b/tests/locations/location_operations_test.py new file mode 100644 index 00000000..a8d0ec23 --- /dev/null +++ b/tests/locations/location_operations_test.py @@ -0,0 +1,19 @@ +import pandas as pd +import pytest + + +import cwms +import cwms.api + + +@pytest.fixture(autouse=True) +def init_session(): + cwms.api.init_session(api_root='localhost:8081/cwms-data', api_key='1234567890abcdef1234567890abcdef') + +def test_get_location_operations(): + """ + Test the retrieval of location operations from the CWMS API. + """ + loc_cat = cwms.get_locations_catalog(office_id='SPK') + + assert(len(loc_cat)==0) # Assuming no locations are present for SPK office in the test environment \ No newline at end of file From f0df764489ebe7bd76178b19eeca227a117efbd7 Mon Sep 17 00:00:00 2001 From: Oskar Hurst Date: Wed, 25 Jun 2025 00:16:00 +0000 Subject: [PATCH 07/11] fix location operations test --- tests/locations/location_operations_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/locations/location_operations_test.py b/tests/locations/location_operations_test.py index a8d0ec23..3dddf9bc 100644 --- a/tests/locations/location_operations_test.py +++ b/tests/locations/location_operations_test.py @@ -8,12 +8,12 @@ @pytest.fixture(autouse=True) def init_session(): - cwms.api.init_session(api_root='localhost:8081/cwms-data', api_key='1234567890abcdef1234567890abcdef') + cwms.api.init_session(api_root='http://localhost:8081/cwms-data/', api_key='1234567890abcdef1234567890abcdef') def test_get_location_operations(): """ Test the retrieval of location operations from the CWMS API. """ loc_cat = cwms.get_locations_catalog(office_id='SPK') - - assert(len(loc_cat)==0) # Assuming no locations are present for SPK office in the test environment \ No newline at end of file + + assert(len(loc_cat.df)==0) # Assuming no locations are present for SPK office in the test environment \ No newline at end of file From 84718581d82a3fa8304bd7e4036b07b6176ece6c Mon Sep 17 00:00:00 2001 From: Oskar Hurst Date: Wed, 2 Jul 2025 21:26:01 +0000 Subject: [PATCH 08/11] update to cda testing squite --- CONTRIBUTING.md | 45 +++++++++++++++ compose_files/sql/users.sql | 22 ++++++- cwms/api.py | 5 +- docker-compose.yml | 18 +++--- tests/cda/__init__.py | 0 tests/cda/conftest.py | 57 +++++++++++++++++++ .../cda/locations/location_operations_test.py | 48 ++++++++++++++++ tests/locations/location_operations_test.py | 19 ------- tests/mock/__init__.py | 0 tests/{ => mock}/api_error_test.py | 0 tests/{ => mock}/api_test.py | 2 +- .../forecast/forecast_instance_test.py | 0 .../{ => mock}/forecast/forecast_spec_test.py | 0 .../{ => mock}/levels/location_levels_test.py | 0 .../levels/specified_levels_test.py | 0 .../{ => mock}/locations/gate_changes_test.py | 0 .../locations/physical_locations_test.py | 0 tests/{ => mock}/outlets/outlets_test.py | 0 .../outlets/virtual_outlets_test.py | 0 .../projects/project_lock_rights_test.py | 0 .../{ => mock}/projects/project_locks_test.py | 0 tests/{ => mock}/projects/projects_test.py | 0 tests/{ => mock}/ratings/rating_spec_test.py | 0 .../ratings/rating_template_test.py | 0 tests/{ => mock}/ratings/ratings_test.py | 0 .../standard_text/standard_text_test.py | 0 .../timeseries/timeseries_bin_test.py | 0 .../timeseries_profile_instance_test.py | 2 +- .../timeseries_profile_parser_test.py | 0 .../timeseries/timeseries_profile_test.py | 0 .../{ => mock}/timeseries/timeseries_test.py | 0 .../timeseries/timeseries_txt_test.py | 0 tests/{ => mock}/turbines/turbines_test.py | 0 tests/{ => mock}/types_data_test.py | 0 34 files changed, 184 insertions(+), 34 deletions(-) create mode 100644 tests/cda/__init__.py create mode 100644 tests/cda/conftest.py create mode 100644 tests/cda/locations/location_operations_test.py delete mode 100644 tests/locations/location_operations_test.py create mode 100644 tests/mock/__init__.py rename tests/{ => mock}/api_error_test.py (100%) rename tests/{ => mock}/api_test.py (96%) rename tests/{ => mock}/forecast/forecast_instance_test.py (100%) rename tests/{ => mock}/forecast/forecast_spec_test.py (100%) rename tests/{ => mock}/levels/location_levels_test.py (100%) rename tests/{ => mock}/levels/specified_levels_test.py (100%) rename tests/{ => mock}/locations/gate_changes_test.py (100%) rename tests/{ => mock}/locations/physical_locations_test.py (100%) rename tests/{ => mock}/outlets/outlets_test.py (100%) rename tests/{ => mock}/outlets/virtual_outlets_test.py (100%) rename tests/{ => mock}/projects/project_lock_rights_test.py (100%) rename tests/{ => mock}/projects/project_locks_test.py (100%) rename tests/{ => mock}/projects/projects_test.py (100%) rename tests/{ => mock}/ratings/rating_spec_test.py (100%) rename tests/{ => mock}/ratings/rating_template_test.py (100%) rename tests/{ => mock}/ratings/ratings_test.py (100%) rename tests/{ => mock}/standard_text/standard_text_test.py (100%) rename tests/{ => mock}/timeseries/timeseries_bin_test.py (100%) rename tests/{ => mock}/timeseries/timeseries_profile_instance_test.py (97%) rename tests/{ => mock}/timeseries/timeseries_profile_parser_test.py (100%) rename tests/{ => mock}/timeseries/timeseries_profile_test.py (100%) rename tests/{ => mock}/timeseries/timeseries_test.py (100%) rename tests/{ => mock}/timeseries/timeseries_txt_test.py (100%) rename tests/{ => mock}/turbines/turbines_test.py (100%) rename tests/{ => mock}/types_data_test.py (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4e6da6e..97d91fb6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,6 +47,51 @@ Run poetry against a single file with: poetry run pytest tests/turbines/turbines_test.py ``` +### Local CDA Testing and Development + +To test and develop with a local CDA instance, follow these steps: + +1. **Install Docker** + Download and install Docker from the [official website](https://www.docker.com/get-started/). + +2. **Start and Verify CDA Services** + From the project root, run: + ```sh + docker compose up -d + ``` + This will start the required services: Oracle database instance, CWMS API, Keycloak Auth, and Traefik proxy. + Additionally, there is a one-time service that runs and then shuts down; this service connects to the database instance to set initial conditions and users. + If you want to update the initial data in the database, you can do so by editing the `compose_files/sql/users.sql` file. + + Ensure all these services are running by checking their status: + ```sh + docker compose ps + ``` + All containers should have a `State` of `Up` before proceeding. + + #### Service Ports and Access + + By default, the local CDA instance will be accessible at [http://localhost:8082](http://localhost:8082), and the Oracle database will be available on port `1526`. When developing or running tests, ensure your application or test configuration points to these ports. + + > **Note:** If you are running other instances of CDA or Oracle on your machine, they may use different ports. Always verify which ports are in use and update your configuration files accordingly to avoid conflicts. + +3. **Run Tests Against CDA** + Once the services are running, execute the tests: + ```sh + poetry run pytest tests/ + ``` + This will run all tests, including those that require a CDA connection. + + #### Running CDA Tests Against Other Instances + + Developers can also run the tests in the `tests/cda/` package against other CDA instances by providing the `--api_key` and `--api_root` command line arguments. For example: + + ```sh + poetry run pytest tests/cda/ --api_root=http://localhost:8082/cwms-data/ + ``` + + > **Warning:** The tests in the `tests/cda/` package are destructive and may cause irreversible deletion of data from the targeted CDA instance. Use caution and avoid running these tests against production or important environments. + ### Code Style In order for a pull request to be accepted, python code must be formatted using [black][black] and [isort][isort]. YAML files should also be formatted using either Prettier or the provided pre-commit hook. Developer are encouraged to integrate these tools into their workflow. Pre-commit hooks can be installed to automatically validate any code changes, and reformat if necessary. diff --git a/compose_files/sql/users.sql b/compose_files/sql/users.sql index 1e66eb79..ad23ffd3 100644 --- a/compose_files/sql/users.sql +++ b/compose_files/sql/users.sql @@ -12,6 +12,7 @@ begin cwms_sec.add_user_to_group('l2hectest','All Users', 'SPK'); cwms_sec.add_user_to_group('l2hectest','CWMS Users', 'SPK'); cwms_sec.add_user_to_group('l2hectest','TS ID Creator','SPK'); + cwms_sec.add_user_to_group('l2hectest','CWMS User Admins', 'SPK'); cwms_sec.add_cwms_user('l1hectest',NULL,'SPL'); -- intentionally no extra permissions. @@ -23,12 +24,29 @@ begin execute immediate 'grant execute on cwms_20.cwms_upass to web_user'; - cwms_sec.add_user_cwms('m5testadmin', NULL, 'LRL'); + cwms_sec.add_cwms_user('m5testadmin', NULL, 'LRL'); cwms_sec.add_user_to_group('m5testadmin','All Users', 'LRL'); cwms_sec.add_user_to_group('m5testadmin','CWMS Users', 'LRL'); cwms_sec.add_user_to_group('m5testadmin','CWMS User Admins', 'LRL'); - insert into cwms_20.at_api_keys (userid, key_name, apikey) values ('L2HECTEST', 'testkey', '1234567890abcdef1234567890abcdef'); + cwms_sec.add_cwms_user('q0hectest', NULL, 'LRL'); + cwms_sec.add_user_to_group('q0hectest','All Users', 'LRL'); + cwms_sec.add_user_to_group('q0hectest','CWMS Users', 'LRL'); + cwms_sec.add_user_to_group('q0hectest','TS ID Creator','LRL'); + + cwms_sec.add_cwms_user('q0hectest', NULL, 'SPK'); + cwms_sec.add_user_to_group('q0hectest','All Users', 'SPK'); + cwms_sec.add_user_to_group('q0hectest','CWMS Users', 'SPK'); + cwms_sec.add_user_to_group('q0hectest','TS ID Creator','SPK'); + + cwms_sec.add_cwms_user('q0hectest', NULL, 'MVP'); + cwms_sec.add_user_to_group('q0hectest','All Users', 'MVP'); + cwms_sec.add_user_to_group('q0hectest','CWMS Users', 'MVP'); + cwms_sec.add_user_to_group('q0hectest','TS ID Creator','MVP'); + + insert into cwms_20.at_api_keys (userid, key_name, apikey) values ('Q0HECTEST', 'testkey', '0123456789abcdef0123456789abcdef'); + + insert into cwms_20.at_api_keys (userid, key_name, apikey) values ('L2HECTEST', 'testkey2', '1234567890abcdef1234567890abcdef'); end; / diff --git a/cwms/api.py b/cwms/api.py index c500c6ce..457ad511 100644 --- a/cwms/api.py +++ b/cwms/api.py @@ -131,7 +131,6 @@ def init_session( """ global SESSION - if api_root: logging.debug(f"Initializing root URL: api_root={api_root}") SESSION = sessions.BaseUrlSession(base_url=api_root) @@ -142,8 +141,10 @@ def init_session( ) SESSION.mount("https://", adapter) if api_key: + if(api_key.startswith("apikey ")): + api_key = api_key.replace("apikey ", "") logging.debug(f"Setting authorization key: api_key={api_key}") - SESSION.headers.update({"Authorization": api_key}) + SESSION.headers.update({"Authorization": 'apikey '+ api_key}) return SESSION diff --git a/docker-compose.yml b/docker-compose.yml index 4c5c7e4c..cf0fdcfc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: - OFFICE_ID=HQ - OFFICE_EROC=s0 ports: - - "1525:1521" + - "1526:1521" healthcheck: test: ["CMD","tnsping", "FREEPDB1"] interval: 30s @@ -67,10 +67,10 @@ services: - cwms.dataapi.access.provider=MultipleAccessManager - cwms.dataapi.access.providers=KeyAccessManager,OpenID - cwms.dataapi.access.openid.create_users=true - - cwms.dataapi.access.openid.wellKnownUrl=http://auth:${APP_PORT:-8081}/auth/realms/cwms/.well-known/openid-configuration - - cwms.dataapi.access.openid.altAuthUrl=http://localhost:${APP_PORT:-8081} + - cwms.dataapi.access.openid.wellKnownUrl=http://auth:${APP_PORT:-8082}/auth/realms/cwms/.well-known/openid-configuration + - cwms.dataapi.access.openid.altAuthUrl=http://localhost:${APP_PORT:-8082} - cwms.dataapi.access.openid.useAltWellKnown=true - - cwms.dataapi.access.openid.issuer=http://localhost:${APP_PORT:-8081}/auth/realms/cwms + - cwms.dataapi.access.openid.issuer=http://localhost:${APP_PORT:-8082}/auth/realms/cwms expose: - 7000 healthcheck: @@ -88,7 +88,7 @@ services: image: quay.io/keycloak/keycloak:19.0.1 command: ["start-dev", "--features-disabled=admin2","--import-realm"] healthcheck: - test: "/usr/bin/curl -If localhost:${APP_PORT:-8081}/auth/health/ready || exit 1" + test: "/usr/bin/curl -If localhost:${APP_PORT:-8082}/auth/health/ready || exit 1" interval: 5s timeout: 1s retries: 100 @@ -98,8 +98,8 @@ services: - KEYCLOAK_ADMIN_PASSWORD=admin - KC_HEALTH_ENABLED=true - KC_FEATURES=admin-fine-grained-authz - - KC_HTTP_PORT=${APP_PORT:-8081} - - KEYCLOAK_FRONTEND_URL=http://localhost:${APP_PORT:-8081} + - KC_HTTP_PORT=${APP_PORT:-8082} + - KEYCLOAK_FRONTEND_URL=http://localhost:${APP_PORT:-8082} - KC_HOSTNAME_STRICT=false - KC_PROXY=none - KC_HTTP_ENABLED=true @@ -110,7 +110,7 @@ services: - "traefik.enable=true" - "traefik.http.routers.auth.rule=PathPrefix(`/auth`)" - "traefik.http.routers.auth.entryPoints=web" - - "traefik.http.services.auth.loadbalancer.server.port=${APP_PORT:-8081}" + - "traefik.http.services.auth.loadbalancer.server.port=${APP_PORT:-8082}" depends_on: traefik: condition: service_healthy @@ -121,7 +121,7 @@ services: traefik: image: traefik:v3.3.3 ports: - - "${APP_PORT:-8081}:80" + - "${APP_PORT:-8082}:80" expose: - "8080" volumes: diff --git a/tests/cda/__init__.py b/tests/cda/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/cda/conftest.py b/tests/cda/conftest.py new file mode 100644 index 00000000..5439100d --- /dev/null +++ b/tests/cda/conftest.py @@ -0,0 +1,57 @@ +import pytest +import cwms +from unittest.mock import patch + +def pytest_addoption(parser): + parser.addoption( + "--api_key", + action="store", + default='0123456789abcdef0123456789abcdef', + help="Set a custom API key for the CWMS API" + ) + parser.addoption( + "--api_root", + action="store", + default='http://localhost:8082/cwms-data/', + help="Set a custom API root for the CWMS API" + ) + +@pytest.fixture(scope="package", autouse=True) +def auto_track_locations(request): + api_key = request.config.getoption("api_key") + api_root = request.config.getoption("api_root") + cwms.api.init_session(api_root=api_root, api_key=api_key) + + print(f"Test api_root and api_key: {api_root}, {api_key}") + + created_locations = set() + + original_store_location = cwms.store_location + + def store_location_wrapper(location_dict): + result = original_store_location(location_dict) + location_id = location_dict.get("name") + office_id = location_dict.get("office-id") + if location_id and office_id: + if "-" in location_id: + base_location_id = location_id.split("-")[0] + created_locations.add((base_location_id, office_id)) + else: + created_locations.add((location_id, office_id)) + return result + + patcher = patch.object(cwms, "store_location", store_location_wrapper) + patcher.start() + + def cleanup(): + print("Cleaning up created locations...") + print(len(created_locations), "Base locations created during the test session.") + patcher.stop() + for location_id, office_id in created_locations: + try: + cwms.delete_location(location_id, office_id) + except Exception as e: + print(f"Failed to delete location {location_id}: {e}") + cwms.api.init_session(api_root=cwms.api.API_ROOT) + + request.addfinalizer(cleanup) \ No newline at end of file diff --git a/tests/cda/locations/location_operations_test.py b/tests/cda/locations/location_operations_test.py new file mode 100644 index 00000000..ce80c672 --- /dev/null +++ b/tests/cda/locations/location_operations_test.py @@ -0,0 +1,48 @@ +import pandas as pd +import pytest + + +import cwms +import cwms.api + + +@pytest.fixture(autouse=True) +def init_session(request): + print("Initializing CWMS API session for location operations test...") + +def test_get_location_operations(): + """ + Test the retrieval of location operations from the CWMS API. + """ + TEST_OFFICE = "SPK" + TEST_LOCATION_ID = "pytest-loc-123" + TEST_LATITUDE = 44.0 + TEST_LONGITUDE = -93.0 + + loc_cat = cwms.get_locations_catalog(office_id='SPK') + + assert(len(loc_cat.df)==0) # Assuming no locations are present for SPK office in the test environment + print(len(loc_cat.df)) + print(loc_cat.df) + + cwms.store_location({ + "name": TEST_LOCATION_ID, + "office-id": TEST_OFFICE, + "latitude": TEST_LATITUDE, + "longitude": TEST_LONGITUDE, + "elevation": 250.0, + "horizontal-datum": "NAD83", + "vertical-datum": "NAVD88", + "location-type": "TESTING", + "public-name": "Pytest Location", + "long-name": "A pytest-generated location", + "timezone-name" : "America/Los_Angeles", + "location-kind": "SITE", + "nation": "US", + }) + + loc_cat = cwms.get_locations_catalog(office_id='SPK') + + # assert(len(loc_cat.df)==0) # Assuming no locations are present for SPK office in the test environment + print(len(loc_cat.df)) + print(loc_cat.df) \ No newline at end of file diff --git a/tests/locations/location_operations_test.py b/tests/locations/location_operations_test.py deleted file mode 100644 index 3dddf9bc..00000000 --- a/tests/locations/location_operations_test.py +++ /dev/null @@ -1,19 +0,0 @@ -import pandas as pd -import pytest - - -import cwms -import cwms.api - - -@pytest.fixture(autouse=True) -def init_session(): - cwms.api.init_session(api_root='http://localhost:8081/cwms-data/', api_key='1234567890abcdef1234567890abcdef') - -def test_get_location_operations(): - """ - Test the retrieval of location operations from the CWMS API. - """ - loc_cat = cwms.get_locations_catalog(office_id='SPK') - - assert(len(loc_cat.df)==0) # Assuming no locations are present for SPK office in the test environment \ No newline at end of file diff --git a/tests/mock/__init__.py b/tests/mock/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/api_error_test.py b/tests/mock/api_error_test.py similarity index 100% rename from tests/api_error_test.py rename to tests/mock/api_error_test.py diff --git a/tests/api_test.py b/tests/mock/api_test.py similarity index 96% rename from tests/api_test.py rename to tests/mock/api_test.py index 86d7450d..7063a425 100644 --- a/tests/api_test.py +++ b/tests/mock/api_test.py @@ -37,7 +37,7 @@ def test_session_init_api_key(): # Both the URL and the auth key should be set on the session. assert session.base_url == "https://example.com" - assert session.headers["Authorization"] == "API_AUTH_KEY" + assert session.headers["Authorization"] == "apikey API_AUTH_KEY" def test_api_headers(): diff --git a/tests/forecast/forecast_instance_test.py b/tests/mock/forecast/forecast_instance_test.py similarity index 100% rename from tests/forecast/forecast_instance_test.py rename to tests/mock/forecast/forecast_instance_test.py diff --git a/tests/forecast/forecast_spec_test.py b/tests/mock/forecast/forecast_spec_test.py similarity index 100% rename from tests/forecast/forecast_spec_test.py rename to tests/mock/forecast/forecast_spec_test.py diff --git a/tests/levels/location_levels_test.py b/tests/mock/levels/location_levels_test.py similarity index 100% rename from tests/levels/location_levels_test.py rename to tests/mock/levels/location_levels_test.py diff --git a/tests/levels/specified_levels_test.py b/tests/mock/levels/specified_levels_test.py similarity index 100% rename from tests/levels/specified_levels_test.py rename to tests/mock/levels/specified_levels_test.py diff --git a/tests/locations/gate_changes_test.py b/tests/mock/locations/gate_changes_test.py similarity index 100% rename from tests/locations/gate_changes_test.py rename to tests/mock/locations/gate_changes_test.py diff --git a/tests/locations/physical_locations_test.py b/tests/mock/locations/physical_locations_test.py similarity index 100% rename from tests/locations/physical_locations_test.py rename to tests/mock/locations/physical_locations_test.py diff --git a/tests/outlets/outlets_test.py b/tests/mock/outlets/outlets_test.py similarity index 100% rename from tests/outlets/outlets_test.py rename to tests/mock/outlets/outlets_test.py diff --git a/tests/outlets/virtual_outlets_test.py b/tests/mock/outlets/virtual_outlets_test.py similarity index 100% rename from tests/outlets/virtual_outlets_test.py rename to tests/mock/outlets/virtual_outlets_test.py diff --git a/tests/projects/project_lock_rights_test.py b/tests/mock/projects/project_lock_rights_test.py similarity index 100% rename from tests/projects/project_lock_rights_test.py rename to tests/mock/projects/project_lock_rights_test.py diff --git a/tests/projects/project_locks_test.py b/tests/mock/projects/project_locks_test.py similarity index 100% rename from tests/projects/project_locks_test.py rename to tests/mock/projects/project_locks_test.py diff --git a/tests/projects/projects_test.py b/tests/mock/projects/projects_test.py similarity index 100% rename from tests/projects/projects_test.py rename to tests/mock/projects/projects_test.py diff --git a/tests/ratings/rating_spec_test.py b/tests/mock/ratings/rating_spec_test.py similarity index 100% rename from tests/ratings/rating_spec_test.py rename to tests/mock/ratings/rating_spec_test.py diff --git a/tests/ratings/rating_template_test.py b/tests/mock/ratings/rating_template_test.py similarity index 100% rename from tests/ratings/rating_template_test.py rename to tests/mock/ratings/rating_template_test.py diff --git a/tests/ratings/ratings_test.py b/tests/mock/ratings/ratings_test.py similarity index 100% rename from tests/ratings/ratings_test.py rename to tests/mock/ratings/ratings_test.py diff --git a/tests/standard_text/standard_text_test.py b/tests/mock/standard_text/standard_text_test.py similarity index 100% rename from tests/standard_text/standard_text_test.py rename to tests/mock/standard_text/standard_text_test.py diff --git a/tests/timeseries/timeseries_bin_test.py b/tests/mock/timeseries/timeseries_bin_test.py similarity index 100% rename from tests/timeseries/timeseries_bin_test.py rename to tests/mock/timeseries/timeseries_bin_test.py diff --git a/tests/timeseries/timeseries_profile_instance_test.py b/tests/mock/timeseries/timeseries_profile_instance_test.py similarity index 97% rename from tests/timeseries/timeseries_profile_instance_test.py rename to tests/mock/timeseries/timeseries_profile_instance_test.py index baf68c48..4f711201 100644 --- a/tests/timeseries/timeseries_profile_instance_test.py +++ b/tests/mock/timeseries/timeseries_profile_instance_test.py @@ -18,7 +18,7 @@ _TSP_INST_JSON = read_resource_file("timeseries_profile_instance.json") _TSP_INST_ARRAY_JSON = read_resource_file("timeseries_profile_instances.json") current_path = Path(__file__).resolve().parent.parent.parent -resource_path = current_path / "tests" / "resources" / "timeseries_profile_data.txt" +resource_path = current_path / "resources" / "timeseries_profile_data.txt" with open(resource_path, "r") as file: _TSP_PROFILE_DATA = file.read().strip("\n") diff --git a/tests/timeseries/timeseries_profile_parser_test.py b/tests/mock/timeseries/timeseries_profile_parser_test.py similarity index 100% rename from tests/timeseries/timeseries_profile_parser_test.py rename to tests/mock/timeseries/timeseries_profile_parser_test.py diff --git a/tests/timeseries/timeseries_profile_test.py b/tests/mock/timeseries/timeseries_profile_test.py similarity index 100% rename from tests/timeseries/timeseries_profile_test.py rename to tests/mock/timeseries/timeseries_profile_test.py diff --git a/tests/timeseries/timeseries_test.py b/tests/mock/timeseries/timeseries_test.py similarity index 100% rename from tests/timeseries/timeseries_test.py rename to tests/mock/timeseries/timeseries_test.py diff --git a/tests/timeseries/timeseries_txt_test.py b/tests/mock/timeseries/timeseries_txt_test.py similarity index 100% rename from tests/timeseries/timeseries_txt_test.py rename to tests/mock/timeseries/timeseries_txt_test.py diff --git a/tests/turbines/turbines_test.py b/tests/mock/turbines/turbines_test.py similarity index 100% rename from tests/turbines/turbines_test.py rename to tests/mock/turbines/turbines_test.py diff --git a/tests/types_data_test.py b/tests/mock/types_data_test.py similarity index 100% rename from tests/types_data_test.py rename to tests/mock/types_data_test.py From 9ab721ae86eb17fe1aca314046562628171985c3 Mon Sep 17 00:00:00 2001 From: Oskar Hurst Date: Wed, 2 Jul 2025 22:31:23 +0000 Subject: [PATCH 09/11] style and testing fixes --- .github/workflows/CDA-testing.yml | 6 ++- .github/workflows/testing.yml | 2 +- cwms/api.py | 4 +- tests/cda/conftest.py | 12 +++-- .../cda/locations/location_operations_test.py | 51 ++++++++++--------- 5 files changed, 43 insertions(+), 32 deletions(-) diff --git a/.github/workflows/CDA-testing.yml b/.github/workflows/CDA-testing.yml index eeca3831..b2bca04d 100644 --- a/.github/workflows/CDA-testing.yml +++ b/.github/workflows/CDA-testing.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v4 - name: set up backend - run: docker compose up -d + run: docker compose up --build -d - name: Set Up Python uses: actions/setup-python@v5 @@ -27,6 +27,10 @@ jobs: - name: Install Poetry uses: abatilo/actions-poetry@v4 + - name: Add Poetry to PATH (for act) + if: env.ACT + run: echo "/root/.local/bin" >> $GITHUB_PATH + - name: Cache Virtual Environment uses: actions/cache@v4 with: diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 4bb2c3f3..1f61c874 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -32,7 +32,7 @@ jobs: # Run pytest and generate coverage report data. - name: Run Tests - run: poetry run pytest tests/ --doctest-modules --cov --cov-report=xml:out/coverage.xml + run: poetry run pytest tests/mock/ --doctest-modules --cov --cov-report=xml:out/coverage.xml # Run mypy with strict mode enabled. Only the main source code is type checked (test # and example code is excluded). diff --git a/cwms/api.py b/cwms/api.py index 457ad511..a2367362 100644 --- a/cwms/api.py +++ b/cwms/api.py @@ -141,10 +141,10 @@ def init_session( ) SESSION.mount("https://", adapter) if api_key: - if(api_key.startswith("apikey ")): + if api_key.startswith("apikey "): api_key = api_key.replace("apikey ", "") logging.debug(f"Setting authorization key: api_key={api_key}") - SESSION.headers.update({"Authorization": 'apikey '+ api_key}) + SESSION.headers.update({"Authorization": "apikey " + api_key}) return SESSION diff --git a/tests/cda/conftest.py b/tests/cda/conftest.py index 5439100d..f080770f 100644 --- a/tests/cda/conftest.py +++ b/tests/cda/conftest.py @@ -2,20 +2,22 @@ import cwms from unittest.mock import patch + def pytest_addoption(parser): parser.addoption( "--api_key", action="store", - default='0123456789abcdef0123456789abcdef', - help="Set a custom API key for the CWMS API" + default="0123456789abcdef0123456789abcdef", + help="Set a custom API key for the CWMS API", ) parser.addoption( "--api_root", action="store", - default='http://localhost:8082/cwms-data/', - help="Set a custom API root for the CWMS API" + default="http://localhost:8082/cwms-data/", + help="Set a custom API root for the CWMS API", ) + @pytest.fixture(scope="package", autouse=True) def auto_track_locations(request): api_key = request.config.getoption("api_key") @@ -54,4 +56,4 @@ def cleanup(): print(f"Failed to delete location {location_id}: {e}") cwms.api.init_session(api_root=cwms.api.API_ROOT) - request.addfinalizer(cleanup) \ No newline at end of file + request.addfinalizer(cleanup) diff --git a/tests/cda/locations/location_operations_test.py b/tests/cda/locations/location_operations_test.py index ce80c672..5c97065b 100644 --- a/tests/cda/locations/location_operations_test.py +++ b/tests/cda/locations/location_operations_test.py @@ -10,6 +10,7 @@ def init_session(request): print("Initializing CWMS API session for location operations test...") + def test_get_location_operations(): """ Test the retrieval of location operations from the CWMS API. @@ -19,30 +20,34 @@ def test_get_location_operations(): TEST_LATITUDE = 44.0 TEST_LONGITUDE = -93.0 - loc_cat = cwms.get_locations_catalog(office_id='SPK') - - assert(len(loc_cat.df)==0) # Assuming no locations are present for SPK office in the test environment + loc_cat = cwms.get_locations_catalog(office_id="SPK") + + assert ( + len(loc_cat.df) == 0 + ) # Assuming no locations are present for SPK office in the test environment print(len(loc_cat.df)) print(loc_cat.df) - - cwms.store_location({ - "name": TEST_LOCATION_ID, - "office-id": TEST_OFFICE, - "latitude": TEST_LATITUDE, - "longitude": TEST_LONGITUDE, - "elevation": 250.0, - "horizontal-datum": "NAD83", - "vertical-datum": "NAVD88", - "location-type": "TESTING", - "public-name": "Pytest Location", - "long-name": "A pytest-generated location", - "timezone-name" : "America/Los_Angeles", - "location-kind": "SITE", - "nation": "US", - }) - - loc_cat = cwms.get_locations_catalog(office_id='SPK') - + + cwms.store_location( + { + "name": TEST_LOCATION_ID, + "office-id": TEST_OFFICE, + "latitude": TEST_LATITUDE, + "longitude": TEST_LONGITUDE, + "elevation": 250.0, + "horizontal-datum": "NAD83", + "vertical-datum": "NAVD88", + "location-type": "TESTING", + "public-name": "Pytest Location", + "long-name": "A pytest-generated location", + "timezone-name": "America/Los_Angeles", + "location-kind": "SITE", + "nation": "US", + } + ) + + loc_cat = cwms.get_locations_catalog(office_id="SPK") + # assert(len(loc_cat.df)==0) # Assuming no locations are present for SPK office in the test environment print(len(loc_cat.df)) - print(loc_cat.df) \ No newline at end of file + print(loc_cat.df) From 38af40c688f68aa821e4071f5a0f12d603e2ad2c Mon Sep 17 00:00:00 2001 From: Oskar Hurst Date: Wed, 2 Jul 2025 22:42:42 +0000 Subject: [PATCH 10/11] isort fix --- docker-compose.yml | 4 ++++ tests/cda/conftest.py | 4 +++- tests/cda/locations/location_operations_test.py | 1 - 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index cf0fdcfc..47e1ec10 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,6 +38,10 @@ services: depends_on: db: condition: service_healthy + auth: + condition: service_healthy + traefik: + condition: service_healthy data-api: diff --git a/tests/cda/conftest.py b/tests/cda/conftest.py index f080770f..6d4c3755 100644 --- a/tests/cda/conftest.py +++ b/tests/cda/conftest.py @@ -1,6 +1,8 @@ +from unittest.mock import patch + import pytest + import cwms -from unittest.mock import patch def pytest_addoption(parser): diff --git a/tests/cda/locations/location_operations_test.py b/tests/cda/locations/location_operations_test.py index 5c97065b..622c88d7 100644 --- a/tests/cda/locations/location_operations_test.py +++ b/tests/cda/locations/location_operations_test.py @@ -1,7 +1,6 @@ import pandas as pd import pytest - import cwms import cwms.api From 0cfe732b4dde7684f524f2f70d2ed4bf1a05263d Mon Sep 17 00:00:00 2001 From: Oskar Hurst Date: Thu, 3 Jul 2025 21:14:58 +0000 Subject: [PATCH 11/11] run cda/mock test --- .github/workflows/CDA-testing.yml | 2 +- .github/workflows/pypi-deploy.yml | 2 +- .github/workflows/test-deploy.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CDA-testing.yml b/.github/workflows/CDA-testing.yml index b2bca04d..c1d01fbd 100644 --- a/.github/workflows/CDA-testing.yml +++ b/.github/workflows/CDA-testing.yml @@ -42,7 +42,7 @@ jobs: # Run pytest and generate coverage report data. - name: Run Tests - run: poetry run pytest tests/ --doctest-modules --cov --cov-report=xml:out/coverage.xml + run: poetry run pytest tests/cda/ --doctest-modules --cov --cov-report=xml:out/coverage.xml # Run mypy with strict mode enabled. Only the main source code is type checked (test # and example code is excluded). diff --git a/.github/workflows/pypi-deploy.yml b/.github/workflows/pypi-deploy.yml index 3d34e193..35ac0edb 100644 --- a/.github/workflows/pypi-deploy.yml +++ b/.github/workflows/pypi-deploy.yml @@ -34,7 +34,7 @@ jobs: # The tests should have already been run in the testing workflow, but we run them # again here to make sure that we do not deploy a broken distribution. - name: Run Tests - run: poetry run pytest tests/ + run: poetry run pytest tests/mock/ # Once the tests have passsed we can build the distribution and store it, so it can # be accessed in the jobs below. diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index d5cbe39d..6d545cfa 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -41,7 +41,7 @@ jobs: # The tests should have already been run in the testing workflow, but we run them # again here to make sure that we do not deploy a broken distribution. - name: Run Tests - run: poetry run pytest tests/ + run: poetry run pytest tests/mock/ # Once the tests have passsed we can build the distribution and store it, so it can # be accessed in the jobs below.