diff --git a/.github/workflows/CDA-testing.yml b/.github/workflows/CDA-testing.yml new file mode 100644 index 00000000..c1d01fbd --- /dev/null +++ b/.github/workflows/CDA-testing.yml @@ -0,0 +1,65 @@ +name: CI + +on: + push: + branches: [ main, githubAction-testing ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + integration-tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: set up backend + run: docker compose up --build -d + + - 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: 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: + 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/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). + - 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 \ No newline at end of file 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. 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/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/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 00000000..def845c2 Binary files /dev/null and b/compose_files/keycloak/server.keystore differ diff --git a/compose_files/sql/users.sql b/compose_files/sql/users.sql new file mode 100644 index 00000000..ad23ffd3 --- /dev/null +++ b/compose_files/sql/users.sql @@ -0,0 +1,53 @@ +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_user_to_group('l2hectest','CWMS User Admins', '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_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'); + + 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; +/ +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/cwms/api.py b/cwms/api.py index c500c6ce..a2367362 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 new file mode 100644 index 00000000..47e1ec10 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,145 @@ +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: + - "1526:1521" + healthcheck: + test: ["CMD","tnsping", "FREEPDB1"] + interval: 30s + timeout: 50s + retries: 50 + start_period: 40m + db_webuser_permissions: + image: ghcr.io/hydrologicengineeringcenter/cwms-database/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 + auth: + condition: service_healthy + traefik: + 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-dev} + 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:-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:-8082}/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:-8082}/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:-8082} + - KEYCLOAK_FRONTEND_URL=http://localhost:${APP_PORT:-8082} + - 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:-8082}" + depends_on: + traefik: + condition: service_healthy + + + + # Proxy for HTTPS for OpenID + traefik: + image: traefik:v3.3.3 + ports: + - "${APP_PORT:-8082}: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 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..6d4c3755 --- /dev/null +++ b/tests/cda/conftest.py @@ -0,0 +1,61 @@ +from unittest.mock import patch + +import pytest + +import cwms + + +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) diff --git a/tests/cda/locations/location_operations_test.py b/tests/cda/locations/location_operations_test.py new file mode 100644 index 00000000..622c88d7 --- /dev/null +++ b/tests/cda/locations/location_operations_test.py @@ -0,0 +1,52 @@ +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) 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