diff --git a/.github/workflows/aam-backend-service-analyse.yml b/.github/workflows/aam-backend-service-analyse.yml new file mode 100644 index 0000000..2dbca6b --- /dev/null +++ b/.github/workflows/aam-backend-service-analyse.yml @@ -0,0 +1,62 @@ +name: aam-backend-service-analyse + +on: + push: + branches: + - main + paths: + - '.github/workflows/aam-backend-service-analyse.yml' + - 'application/aam-backend-service/**' + pull_request: + types: [ opened, synchronize, reopened, ready_for_review ] + +jobs: + test_coverage: + name: Run tests and coverage calculation + runs-on: ubuntu-latest + permissions: + packages: read + defaults: + run: + working-directory: application/aam-backend-service + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: 17 + cache: 'gradle' + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GHCR_PULL_SECRET }} + + - name: Run Tests and generate Jacoco Test Report + env: + JACOCO_SOURCE_PATH: application/aam-backend-service/src/main/kotlin + run: | + ./gradlew jacocoTestReport --no-daemon + + - name: Publish code coverage to code climate + uses: paambaati/codeclimate-action@v5.0.0 + env: + CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} + JACOCO_SOURCE_PATH: application/aam-backend-service/src/main/kotlin + with: + coverageLocations: application/aam-backend-service/build/reports/jacoco/test/jacocoTestReport.xml:jacoco + + - name: Add code coverage to PR + uses: madrapps/jacoco-report@v1.6.1 + with: + paths: application/aam-backend-service/build/reports/jacoco/test/jacocoTestReport.xml + token: ${{ secrets.GITHUB_TOKEN }} + min-coverage-overall: 80 + min-coverage-changed-files: 80 + title: Code Coverage Analyse + update-comment: true diff --git a/.github/workflows/aam-backend-service-build-pr.yml b/.github/workflows/aam-backend-service-build-pr.yml new file mode 100644 index 0000000..99ef435 --- /dev/null +++ b/.github/workflows/aam-backend-service-build-pr.yml @@ -0,0 +1,45 @@ +name: aam-backend-service-build-pr + +on: + pull_request: + paths: + - '.github/workflows/aam-backend-service-build-pr.yml' + - 'application/aam-backend-service/**' + workflow_dispatch: + +jobs: + push_pr_build_to_registry: + name: Build aam-backend-service and publish to container registry + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: 17 + cache: 'gradle' + + - name: Build application + working-directory: application/aam-backend-service + run: ./gradlew installDist + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + platforms: linux/amd64 + context: ./application/aam-backend-service + push: true + tags: ghcr.io/aam-digital/aam-backend-service:pr-${{ github.event.number }} diff --git a/.github/workflows/aam-backend-service.yml b/.github/workflows/aam-backend-service-build.yml similarity index 90% rename from .github/workflows/aam-backend-service.yml rename to .github/workflows/aam-backend-service-build.yml index 84d63ed..9605751 100644 --- a/.github/workflows/aam-backend-service.yml +++ b/.github/workflows/aam-backend-service-build.yml @@ -1,12 +1,11 @@ -name: aam-backend-service +name: aam-backend-service-build on: push: branches: - main - - 'tw/feat/query-backend-port' paths: - - '.github/workflows/aam-backend-service.yml' + - '.github/workflows/aam-backend-service-build.yml' - 'application/aam-backend-service/**' workflow_dispatch: diff --git a/README.md b/README.md index 87d56f8..cbbadab 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@ -# Aam Digital Backend Services +# Aam Digital Services + +Collection of aam-digital services and tools + +### aam-backend-service + +[![Maintainability](https://api.codeclimate.com/v1/badges/57213b5887a579196d6d/maintainability)](https://codeclimate.com/github/Aam-Digital/aam-services/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/57213b5887a579196d6d/test_coverage)](https://codeclimate.com/github/Aam-Digital/aam-services/test_coverage) A modularize Spring Boot application that contains API modules for [Aam Digital's case management platform](https://github.com/Aam-Digital/ndb-core). -## API Modules +#### API Modules - **Reporting**: Calculate aggregated reports and run queries on all data, accessible for external services for API integrations of systems diff --git a/application/aam-backend-service/build.gradle.kts b/application/aam-backend-service/build.gradle.kts index 8358bf7..3fe25c5 100644 --- a/application/aam-backend-service/build.gradle.kts +++ b/application/aam-backend-service/build.gradle.kts @@ -58,11 +58,48 @@ dependencies { annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") + testImplementation("io.cucumber:cucumber-java:7.14.0") + testImplementation("io.cucumber:cucumber-junit:7.14.0") + testImplementation("io.cucumber:cucumber-spring:7.14.0") + testImplementation("org.junit.vintage:junit-vintage-engine:5.10.2") + + testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2") testImplementation("io.projectreactor:reactor-test") + + testImplementation("org.testcontainers:junit-jupiter:1.19.7") { + constraints { + testImplementation("org.apache.commons:commons-compress:1.26.1") { + because("previous versions have security issues") + } + } + } + testImplementation("org.testcontainers:rabbitmq:1.19.7") + testImplementation("com.github.dasniko:testcontainers-keycloak:3.3.0") { + constraints { + testImplementation("org.apache.james:apache-mime4j-core:0.8.11") { + because("previous versions have security issues") + } + } + } + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.springframework.boot:spring-boot-testcontainers") testImplementation("org.springframework.security:spring-security-test") } +jacoco { + toolVersion = "0.8.11" +} + +tasks.jacocoTestReport { + dependsOn(tasks.test) + reports { + html.required.set(false) + csv.required.set(true) + xml.required.set(true) + } +} + sentry { // Generates a JVM (Java, Kotlin, etc.) source bundle and uploads your source code to Sentry. // This enables source context, allowing you to see your source @@ -84,4 +121,5 @@ tasks.withType { tasks.withType { useJUnitPlatform() + systemProperty("cucumber.junit-platform.naming-strategy", "long") } diff --git a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/error/AamException.kt b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/error/AamException.kt index 7cbbf25..f64a729 100644 --- a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/error/AamException.kt +++ b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/error/AamException.kt @@ -16,7 +16,6 @@ class InternalServerException( code: String = "INTERNAL_SERVER_ERROR" ) : AamException(message, cause, code) - class ExternalSystemException( message: String = "", cause: Throwable? = null, diff --git a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/domain/ReportCalculation.kt b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/domain/ReportCalculation.kt index b24b959..04b1d51 100644 --- a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/domain/ReportCalculation.kt +++ b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/domain/ReportCalculation.kt @@ -23,6 +23,7 @@ sealed class ReportCalculationOutcome { } data class ReportCalculation( + @JsonProperty("_id") val id: String, val report: DomainReference, var status: ReportCalculationStatus, diff --git a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/domain/ReportData.kt b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/domain/ReportData.kt index 1a59c91..c23ddca 100644 --- a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/domain/ReportData.kt +++ b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/domain/ReportData.kt @@ -1,10 +1,12 @@ package com.aamdigital.aambackendservice.reporting.domain import com.aamdigital.aambackendservice.domain.DomainReference +import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.ObjectMapper import java.security.MessageDigest data class ReportData( + @JsonProperty("_id") val id: String, val report: DomainReference, val calculation: DomainReference, diff --git a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/reportcalculation/controller/ReportController.kt b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/controller/ReportController.kt similarity index 92% rename from application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/reportcalculation/controller/ReportController.kt rename to application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/controller/ReportController.kt index 7ef3015..9a3decf 100644 --- a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/reportcalculation/controller/ReportController.kt +++ b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/controller/ReportController.kt @@ -1,4 +1,4 @@ -package com.aamdigital.aambackendservice.reporting.reportcalculation.controller +package com.aamdigital.aambackendservice.reporting.report.controller import com.aamdigital.aambackendservice.domain.DomainReference import com.aamdigital.aambackendservice.error.NotFoundException @@ -12,12 +12,12 @@ import org.springframework.web.bind.annotation.RestController import reactor.core.publisher.Mono @RestController -@RequestMapping("/v1/reporting") +@RequestMapping("/v1/reporting/report") @Validated class ReportController( private val reportingStorage: ReportingStorage, ) { - @GetMapping("/report") + @GetMapping fun fetchReports(): Mono> { return reportingStorage.fetchAllReports("sql") .zipWith( @@ -38,7 +38,7 @@ class ReportController( } } - @GetMapping("/report/{reportId}") + @GetMapping("/{reportId}") fun fetchReport( @PathVariable reportId: String ): Mono { diff --git a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/sqs/SqsSchemaService.kt b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/sqs/SqsSchemaService.kt index 91a4a1a..2d55f7e 100644 --- a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/sqs/SqsSchemaService.kt +++ b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/sqs/SqsSchemaService.kt @@ -1,7 +1,6 @@ package com.aamdigital.aambackendservice.reporting.report.sqs import com.aamdigital.aambackendservice.couchdb.core.CouchDbStorage -import com.aamdigital.aambackendservice.crypto.core.CryptoService import com.aamdigital.aambackendservice.domain.EntityAttribute import com.aamdigital.aambackendservice.domain.EntityConfig import com.aamdigital.aambackendservice.domain.EntityType @@ -11,6 +10,8 @@ import org.springframework.stereotype.Service import org.springframework.util.LinkedMultiValueMap import reactor.core.publisher.Mono import java.security.MessageDigest +import java.util.* +import kotlin.jvm.optionals.getOrNull data class AppConfigAttribute( val dataType: String, @@ -41,7 +42,7 @@ data class TableName( data class SqlObject( val tables: Map, - val indexes: List, + val indexes: List?, val options: SqlOptions, ) @@ -72,7 +73,6 @@ data class SqsSchema( @Service class SqsSchemaService( private val couchDbStorage: CouchDbStorage, - private val cryptoService: CryptoService, ) { companion object { @@ -110,13 +110,15 @@ class SqsSchemaService( queryParams = LinkedMultiValueMap(), kClass = SqsSchema::class ) + .map { Optional.of(it) } + .onErrorResume { Mono.just(Optional.empty()) } ) .flatMap { val entityConfig = it.t1 - val currentSqsSchema = it.t2 + val currentSqsSchema = it.t2.getOrNull() val newSqsSchema: SqsSchema = mapToSqsSchema(entityConfig) - if (currentSqsSchema.configVersion == newSqsSchema.configVersion) { + if (currentSqsSchema?.configVersion == newSqsSchema.configVersion) { return@flatMap Mono.just(Unit) } diff --git a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/reportcalculation/controller/ReportCalculationController.kt b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/reportcalculation/controller/ReportCalculationController.kt index dba903c..e437a0d 100644 --- a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/reportcalculation/controller/ReportCalculationController.kt +++ b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/reportcalculation/controller/ReportCalculationController.kt @@ -18,13 +18,13 @@ import org.springframework.web.bind.annotation.RestController import reactor.core.publisher.Mono @RestController -@RequestMapping("/v1/reporting") +@RequestMapping("/v1/reporting/report-calculation") @Validated class ReportCalculationController( private val reportingStorage: ReportingStorage, private val createReportCalculationUseCase: CreateReportCalculationUseCase ) { - @PostMapping("/report-calculation/report/{reportId}") + @PostMapping("/report/{reportId}") fun startCalculation( @PathVariable reportId: String ): Mono { @@ -50,14 +50,14 @@ class ReportCalculationController( } } - @GetMapping("/report-calculation/report/{reportId}") + @GetMapping("/report/{reportId}") fun fetchReportCalculations( @PathVariable reportId: String ): Mono> { return reportingStorage.fetchCalculations(DomainReference(id = reportId)) } - @GetMapping("/report-calculation/{calculationId}") + @GetMapping("/{calculationId}") fun fetchReportCalculation( @PathVariable calculationId: String ): Mono { @@ -73,7 +73,7 @@ class ReportCalculationController( } } - @GetMapping("/report-calculation/{calculationId}/data") + @GetMapping("/{calculationId}/data") fun fetchReportCalculationData( @PathVariable calculationId: String ): Mono { diff --git a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/storage/DefaultReportingStorage.kt b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/storage/DefaultReportingStorage.kt similarity index 98% rename from application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/storage/DefaultReportingStorage.kt rename to application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/storage/DefaultReportingStorage.kt index 805fcc8..644da36 100644 --- a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/storage/DefaultReportingStorage.kt +++ b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/storage/DefaultReportingStorage.kt @@ -1,4 +1,4 @@ -package com.aamdigital.aambackendservice.reporting.report.storage +package com.aamdigital.aambackendservice.reporting.storage import com.aamdigital.aambackendservice.couchdb.dto.CouchDbChange import com.aamdigital.aambackendservice.domain.DomainReference diff --git a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/storage/ReportCalculationRepository.kt b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/storage/ReportCalculationRepository.kt similarity index 98% rename from application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/storage/ReportCalculationRepository.kt rename to application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/storage/ReportCalculationRepository.kt index 22bc7c9..c9422df 100644 --- a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/storage/ReportCalculationRepository.kt +++ b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/storage/ReportCalculationRepository.kt @@ -1,4 +1,4 @@ -package com.aamdigital.aambackendservice.reporting.report.storage +package com.aamdigital.aambackendservice.reporting.storage import com.aamdigital.aambackendservice.couchdb.core.CouchDbStorage import com.aamdigital.aambackendservice.couchdb.core.getQueryParamsAllDocs diff --git a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/storage/ReportRepository.kt b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/storage/ReportRepository.kt similarity index 94% rename from application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/storage/ReportRepository.kt rename to application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/storage/ReportRepository.kt index 2c15b62..911186e 100644 --- a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/report/storage/ReportRepository.kt +++ b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/reporting/storage/ReportRepository.kt @@ -1,4 +1,4 @@ -package com.aamdigital.aambackendservice.reporting.report.storage +package com.aamdigital.aambackendservice.reporting.storage import com.aamdigital.aambackendservice.couchdb.core.CouchDbStorage import com.aamdigital.aambackendservice.couchdb.core.getQueryParamsAllDocs diff --git a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/security/SecurityConfiguration.kt b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/security/SecurityConfiguration.kt index 1465863..9310e57 100644 --- a/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/security/SecurityConfiguration.kt +++ b/application/aam-backend-service/src/main/kotlin/com/aamdigital/aambackendservice/security/SecurityConfiguration.kt @@ -31,6 +31,9 @@ class SecurityConfiguration { it.pathMatchers(HttpMethod.GET, "/actuator/**").permitAll() it.anyExchange().authenticated() } + .csrf { + it.disable() + } .exceptionHandling { it.accessDeniedHandler(customServerAccessDeniedHandler()) it.authenticationEntryPoint(CustomAuthenticationEntryPoint()) @@ -52,9 +55,7 @@ class SecurityConfiguration { private class CustomAuthenticationEntryPoint : ServerAuthenticationEntryPoint { override fun commence(exchange: ServerWebExchange, ex: AuthenticationException): Mono { - return Mono.fromRunnable { - throw UnauthorizedAccessException("Access Token invalid or missing") - } + throw UnauthorizedAccessException("Access Token invalid or missing") } } diff --git a/application/aam-backend-service/src/main/resources/application.yaml b/application/aam-backend-service/src/main/resources/application.yaml index fbec386..246edc4 100644 --- a/application/aam-backend-service/src/main/resources/application.yaml +++ b/application/aam-backend-service/src/main/resources/application.yaml @@ -6,8 +6,6 @@ spring: jackson: deserialization: accept-empty-string-as-null-object: true - webflux: - base-path: /api r2dbc: url: r2dbc:h2:file://././data/dbh2;DB_CLOSE_DELAY=-1 username: local @@ -43,6 +41,8 @@ spring: retry: enabled: true max-attempts: 5 + webflux: + base-path: /api server: error: diff --git a/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/ApplicationTests.kt b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/ApplicationTests.kt deleted file mode 100644 index 2286c28..0000000 --- a/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/ApplicationTests.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.aamdigital.aambackendservice - -import org.junit.jupiter.api.Test -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest -class ApplicationTests { - - @Test - fun contextLoads() { - } - -} - diff --git a/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/common/AuthTestingService.kt b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/common/AuthTestingService.kt new file mode 100644 index 0000000..ca96922 --- /dev/null +++ b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/common/AuthTestingService.kt @@ -0,0 +1,36 @@ +package com.aamdigital.aambackendservice.common + +import com.fasterxml.jackson.databind.node.ObjectNode +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import org.springframework.http.MediaType +import org.springframework.util.LinkedMultiValueMap +import org.springframework.util.MultiValueMap +import org.springframework.web.client.RestTemplate + +class AuthTestingService( + private val restTemplate: RestTemplate, +) { + fun fetchToken(client: String, secret: String, realm: String): String? { + val headers = HttpHeaders() + headers.contentType = MediaType.APPLICATION_FORM_URLENCODED + + val body: MultiValueMap = LinkedMultiValueMap() + body.add("client_id", client) + body.add("client_secret", secret) + body.add("grant_type", "client_credentials") + body.add("scope", "reporting_read reporting_write") + + val requestEntity = HttpEntity(body, headers) + + val tokenResponse = restTemplate.exchange( + "/realms/$realm/protocol/openid-connect/token", + HttpMethod.POST, + requestEntity, + ObjectNode::class.java + ) + + return tokenResponse.body?.get("access_token")?.textValue() + } +} diff --git a/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/common/CouchDbTestingService.kt b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/common/CouchDbTestingService.kt new file mode 100644 index 0000000..25b3566 --- /dev/null +++ b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/common/CouchDbTestingService.kt @@ -0,0 +1,71 @@ +package com.aamdigital.aambackendservice.common + +import com.fasterxml.jackson.databind.node.ArrayNode +import com.fasterxml.jackson.databind.node.ObjectNode +import org.slf4j.LoggerFactory +import org.springframework.http.HttpEntity +import org.springframework.http.HttpMethod +import org.springframework.web.client.RestTemplate + +class CouchDbTestingService( + private val restTemplate: RestTemplate, +) { + companion object { + private val DEFAULT_DATABASES = listOf("app", "notification-webhook", "report-calculation") + } + + private val logger = LoggerFactory.getLogger(javaClass) + + fun reset() { + val allDbsResponse = restTemplate + .exchange("/_all_dbs", HttpMethod.GET, HttpEntity.EMPTY, ArrayNode::class.java) + + val dbs = allDbsResponse + .body + ?.filter { + !it.textValue().startsWith("_") + } + ?.map { + it.textValue() + } ?: emptyList() + + dbs.forEach { + deleteDatabase(it) + } + } + + fun initDefaultDatabases() { + DEFAULT_DATABASES.forEach { + createDatabase(it) + } + } + + fun createDatabase(database: String) { + val response = restTemplate + .exchange("/$database", HttpMethod.PUT, HttpEntity.EMPTY, ObjectNode::class.java) + logger.info("[CouchDbSetup] create Database: $database, ${response.statusCode}") + } + + fun createDocument(database: String, documentName: String, documentContent: String) { + val response = restTemplate + .exchange( + "/$database/$documentName", + HttpMethod.PUT, + HttpEntity(documentContent), + ObjectNode::class.java, + ) + logger.info("[CouchDbSetup] create Document: $database, $documentName, ${response.statusCode}") + } + + private fun deleteDatabase(database: String) { + val response = restTemplate + .exchange( + "/$database", + HttpMethod.DELETE, + HttpEntity.EMPTY, + ObjectNode::class.java, + ) + logger.info("[CouchDbSetup] delete Database: $database, ${response.statusCode}") + } + +} diff --git a/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/container/TestContainers.kt b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/container/TestContainers.kt new file mode 100644 index 0000000..915abd2 --- /dev/null +++ b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/container/TestContainers.kt @@ -0,0 +1,97 @@ +package com.aamdigital.aambackendservice.container + +import dasniko.testcontainers.keycloak.KeycloakContainer +import org.springframework.boot.testcontainers.service.connection.ServiceConnection +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource +import org.testcontainers.containers.GenericContainer +import org.testcontainers.containers.Network +import org.testcontainers.containers.RabbitMQContainer +import org.testcontainers.images.PullPolicy +import org.testcontainers.junit.jupiter.Container +import org.testcontainers.junit.jupiter.Testcontainers +import org.testcontainers.utility.DockerImageName + +@Testcontainers +object TestContainers { + + private var network: Network = Network.newNetwork() + + @DynamicPropertySource + @JvmStatic + fun init(registry: DynamicPropertyRegistry) { + CONTAINER_KEYCLOAK.start() + CONTAINER_COUCHDB.start() + CONTAINER_SQS.start() + registry.add( + "spring.security.oauth2.resourceserver.jwt.issuer-uri" + ) { + "http://localhost:${CONTAINER_KEYCLOAK.getMappedPort(8080)}/realms/dummy-realm" + } + registry.add( + "couch-db-client-configuration.base-path", + ) { + "http://localhost:${CONTAINER_COUCHDB.getMappedPort(5984)}" + } + registry.add( + "sqs-client-configuration.base-path", + ) { + "http://localhost:${CONTAINER_SQS.getMappedPort(4984)}" + } + } + + @Container + @JvmStatic + val CONTAINER_KEYCLOAK: KeycloakContainer = KeycloakContainer() + .withRealmImportFile("/e2e-keycloak-realm.json") + .withAdminUsername("admin") + .withAdminPassword("docker") + + @Container + @ServiceConnection + @JvmStatic + val CONTAINER_RABBIT_MQ: RabbitMQContainer = RabbitMQContainer( + DockerImageName + .parse("rabbitmq") + .withTag("3.7.25-management-alpine") + ) + + @Container + @JvmStatic + val CONTAINER_COUCHDB: GenericContainer<*> = + GenericContainer( + DockerImageName + .parse("couchdb") + .withTag("3.3") + ) + .withNetwork(network) + .withNetworkAliases("couchdb") + .withEnv( + mapOf( + Pair("COUCHDB_USER", "admin"), + Pair("COUCHDB_PASSWORD", "docker"), + Pair("COUCHDB_SECRET", "docker"), + ) + ) + .withExposedPorts(5984) + + @Container + @JvmStatic + val CONTAINER_SQS: GenericContainer<*> = + GenericContainer( + DockerImageName + .parse("ghcr.io/aam-digital/aam-sqs-linux") + .asCompatibleSubstituteFor("aam-sqs-linux") + .withTag("latest") + ) + .withImagePullPolicy(PullPolicy.alwaysPull()) + .withNetwork(network) + .withNetworkAliases("sqs") + .withEnv( + mapOf( + Pair("SQS_COUCHDB_URL", "http://couchdb:5984"), + ) + ) + .withExposedPorts(4984) + +} diff --git a/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/e2e/CucumberIntegrationTest.kt b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/e2e/CucumberIntegrationTest.kt new file mode 100644 index 0000000..e6ab4c4 --- /dev/null +++ b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/e2e/CucumberIntegrationTest.kt @@ -0,0 +1,107 @@ +package com.aamdigital.aambackendservice.e2e + +import io.cucumber.java.After +import io.cucumber.java.en.Given +import io.cucumber.java.en.Then +import io.cucumber.java.en.When +import io.cucumber.spring.CucumberContextConfiguration +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.Assert +import org.springframework.http.HttpMethod +import java.io.File + +@CucumberContextConfiguration +class CucumberIntegrationTest : SpringIntegrationTest() { + + @After + fun `reset all databases`() { + couchDbTestingService.reset() + } + + @Given("signed in as client {} with secret {} in realm {}") + fun `sign in as user in realm`(client: String, secret: String, realm: String) { + fetchToken(client, secret, realm) + } + + @Given("all default databases are created") + fun `create default databases`() { + couchDbTestingService.initDefaultDatabases() + } + + @Given("database {word} is created") + fun `create database for `(name: String) { + couchDbTestingService.createDatabase(name) + } + + @Given("document {} is stored in database {}") + fun `store document in database`(document: String, database: String) { + couchDbTestingService.createDocument( + database = database, + documentName = document, + documentContent = File("src/test/resources/database/documents/$document.json").readText() + ) + } + + @When("the client calls GET {word}") + @Throws(Throwable::class) + fun `the client issues GET endpoint`(endpoint: String) { + exchange(endpoint, HttpMethod.GET) + } + + @When("the client calls GET {word} with id from latest response") + @Throws(Throwable::class) + fun `the client issues GET endpoint with id from latest response`(endpoint: String) { + exchange(endpoint + parseBodyToObjectNode()?.get("id")?.textValue(), HttpMethod.GET) + } + + @When("the client calls POST {} without body") + @Throws(Throwable::class) + fun `the client issues POST endpoint without body`(endpoint: String) { + exchange(endpoint, HttpMethod.POST) + } + + + @When("the client calls POST {} with body {}") + @Throws(Throwable::class) + fun `the client issues POST endpoint with body`(endpoint: String, body: String) { + exchange(endpoint, HttpMethod.POST, File("src/test/resources/database/documents/$body.json").readText()) + } + + @Then("the client receives a json array") + @Throws(Throwable::class) + fun `the client receives list of values`() { + Assert.assertEquals(true, parseBodyToArrayNode()?.isArray) + } + + @When("the client receives an json object") + @Throws(Throwable::class) + fun `the client receives an json object`() { + Assert.assertEquals(true, parseBodyToObjectNode()?.isObject) + } + + @Then("the client receives status code of {int}") + @Throws(Throwable::class) + fun `the client receives status code of`(statusCode: Int) { + Assert.assertEquals(statusCode, latestResponseStatus?.value()) + } + + @Then("the client receives value {} for property {}") + @Throws(Throwable::class) + fun `the client receives value for property`(value: String, property: String) { + Assert.assertEquals(true, parseBodyToObjectNode()?.has(property)) + Assert.assertEquals(value, parseBodyToObjectNode()?.get(property)?.textValue()) + } + + @Then("the client receives array with {int} elements") + @Throws(Throwable::class) + fun `the client receives array with n elements`(numberOfElements: Int) { + Assert.assertEquals(numberOfElements, parseBodyToArrayNode()?.size()) + } + + @Then("the client waits for {long} milliseconds") + @Throws(Throwable::class) + fun `the client for n milliseconds`(milliseconds: Long) = runBlocking { + delay(milliseconds) + } +} diff --git a/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/e2e/CucumberTestRunner.kt b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/e2e/CucumberTestRunner.kt new file mode 100644 index 0000000..2a9d703 --- /dev/null +++ b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/e2e/CucumberTestRunner.kt @@ -0,0 +1,9 @@ +package com.aamdigital.aambackendservice.e2e + +import io.cucumber.junit.Cucumber +import io.cucumber.junit.CucumberOptions +import org.junit.runner.RunWith + +@RunWith(Cucumber::class) +@CucumberOptions(features = ["src/test/resources"]) +class CucumberTestRunner diff --git a/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/e2e/SpringIntegrationTest.kt b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/e2e/SpringIntegrationTest.kt new file mode 100644 index 0000000..eb3a4f0 --- /dev/null +++ b/application/aam-backend-service/src/test/kotlin/com/aamdigital/aambackendservice/e2e/SpringIntegrationTest.kt @@ -0,0 +1,100 @@ +package com.aamdigital.aambackendservice.e2e + +import com.aamdigital.aambackendservice.common.AuthTestingService +import com.aamdigital.aambackendservice.common.CouchDbTestingService +import com.aamdigital.aambackendservice.container.TestContainers +import com.aamdigital.aambackendservice.container.TestContainers.CONTAINER_COUCHDB +import com.aamdigital.aambackendservice.container.TestContainers.CONTAINER_KEYCLOAK +import com.fasterxml.jackson.databind.node.ArrayNode +import com.fasterxml.jackson.databind.node.ObjectNode +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.testcontainers.context.ImportTestcontainers +import org.springframework.boot.web.client.RestTemplateBuilder +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import org.springframework.http.HttpStatusCode +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration +import org.springframework.web.client.HttpClientErrorException +import org.springframework.web.client.RestTemplate + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT +) +@ActiveProfiles("e2e") +@ImportTestcontainers(TestContainers::class) +@ContextConfiguration +abstract class SpringIntegrationTest { + + companion object { + private const val APPLICATION_PORT = 9000 + } + + val objectMapper = jacksonObjectMapper() + + val restTemplate: RestTemplate = RestTemplateBuilder() + .rootUri("http://localhost:$APPLICATION_PORT") + .build() + + internal val couchDbTestingService: CouchDbTestingService = CouchDbTestingService( + RestTemplateBuilder() + .rootUri("http://localhost:${CONTAINER_COUCHDB.getMappedPort(5984)}") + .basicAuthentication("admin", "docker") + .build() + ) + + internal val authTestingService: AuthTestingService = AuthTestingService( + RestTemplateBuilder() + .rootUri("http://localhost:${CONTAINER_KEYCLOAK.getMappedPort(8080)}") + .basicAuthentication("admin", "docker") + .build() + ) + + var latestResponseBody: String? = null + var latestResponseStatus: HttpStatusCode? = null + var authToken: String? = null + + fun exchange(url: String, method: HttpMethod, body: String? = null) { + val headers = HttpHeaders() + + if (authToken != null) { + headers.set(HttpHeaders.AUTHORIZATION, "Bearer $authToken") + } + + val requestEntity = HttpEntity(body, headers) + + try { + restTemplate.exchange( + url, + method, + requestEntity, + String::class.java, + ).let { + latestResponseStatus = it.statusCode + latestResponseBody = it.body + } + } catch (ex: HttpClientErrorException) { + latestResponseStatus = ex.statusCode + latestResponseBody = ex.responseBodyAsString + } + } + + fun parseBodyToObjectNode(): ObjectNode? { + return latestResponseBody?.let { + objectMapper.readValue(it) + } + } + + fun parseBodyToArrayNode(): ArrayNode? { + return latestResponseBody?.let { + objectMapper.readValue(it) + } + } + + fun fetchToken(client: String, secret: String, realm: String) { + authToken = authTestingService.fetchToken(client, secret, realm) + } +} diff --git a/application/aam-backend-service/src/test/resources/application-e2e.yaml b/application/aam-backend-service/src/test/resources/application-e2e.yaml new file mode 100644 index 0000000..bbeedca --- /dev/null +++ b/application/aam-backend-service/src/test/resources/application-e2e.yaml @@ -0,0 +1,57 @@ +spring: + application: + name: aam-backend-service + main: + banner-mode: off + jackson: + deserialization: + accept-empty-string-as-null-object: true + r2dbc: + url: r2dbc:h2:file://././data/dbh2;DB_CLOSE_DELAY=-1 + username: local + password: local + rabbitmq: + virtual-host: / + listener: + direct: + retry: + enabled: true + max-attempts: 5 + +server: + port: 9000 + +management: + endpoint: + health: + probes: + enabled: true + endpoints: + web: + exposure: + include: + - info + - health + +couch-db-client-configuration: + base-path: http://localhost:5984 + basic-auth-username: admin + basic-auth-password: docker + +sqs-client-configuration: + base-path: http://localhost:4984 + basic-auth-username: admin + basic-auth-password: docker + +database-change-detection: + enabled: false + +report-calculation-processor: + enabled: true + +crypto-configuration: + secret: super-duper-secret + +sentry: + logging: + enabled: false diff --git a/application/aam-backend-service/src/test/resources/cucumber/features/actuator/actuator.feature b/application/aam-backend-service/src/test/resources/cucumber/features/actuator/actuator.feature new file mode 100644 index 0000000..32d0c24 --- /dev/null +++ b/application/aam-backend-service/src/test/resources/cucumber/features/actuator/actuator.feature @@ -0,0 +1,7 @@ +Feature: the health endpoint can be retrieved + + Scenario: client makes call to GET /actuator/health + When the client calls GET /actuator/health + Then the client receives an json object + Then the client receives status code of 200 + And the client receives value UP for property status diff --git a/application/aam-backend-service/src/test/resources/cucumber/features/reporting/report/report.feature b/application/aam-backend-service/src/test/resources/cucumber/features/reporting/report/report.feature new file mode 100644 index 0000000..b28b637 --- /dev/null +++ b/application/aam-backend-service/src/test/resources/cucumber/features/reporting/report/report.feature @@ -0,0 +1,46 @@ +Feature: the report endpoints persist to database + + Scenario: client makes call to GET /reporting/report and receives empty list + Given database app is created + Given database report-calculation is created + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report + Then the client receives a json array + Then the client receives status code of 200 + Then the client receives array with 0 elements + + Scenario: client makes call to GET /reporting/report without token and receives Unauthorized + Given database app is created + Given database report-calculation is created + When the client calls GET /v1/reporting/report + Then the client receives an json object + Then the client receives status code of 401 + + Scenario: client makes call to GET /reporting/report/foo and receives not found + Given database app is created + Given database report-calculation is created + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report/foo + Then the client receives an json object + Then the client receives status code of 404 + + Scenario: client makes call to GET /reporting/report/ReportConfig:1 and receives document + Given database app is created + Given database report-calculation is created + Given document ReportConfig:1 is stored in database app + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report/ReportConfig:1 + Then the client receives an json object + Then the client receives status code of 200 + Then the client receives value Test Report 1 for property name + + Scenario: client makes call to GET /reporting/report and receives two document + Given database app is created + Given database report-calculation is created + Given document ReportConfig:1 is stored in database app + Given document ReportConfig:2 is stored in database app + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report + Then the client receives status code of 200 + Then the client receives a json array + Then the client receives array with 2 elements diff --git a/application/aam-backend-service/src/test/resources/cucumber/features/reporting/reportcalculation/fetch-calculation-data.feature b/application/aam-backend-service/src/test/resources/cucumber/features/reporting/reportcalculation/fetch-calculation-data.feature new file mode 100644 index 0000000..7e162ec --- /dev/null +++ b/application/aam-backend-service/src/test/resources/cucumber/features/reporting/reportcalculation/fetch-calculation-data.feature @@ -0,0 +1,33 @@ +Feature: the report calculation data endpoint persist to database + + Scenario: client makes call to GET /reporting/report-calculation/ReportCalculation:1/data and receives data + Given database app is created + Given database report-calculation is created + Given document ReportConfig:1 is stored in database app + Given document ReportCalculation:1 is stored in database report-calculation + Given document ReportData:1 is stored in database report-calculation + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report-calculation/ReportCalculation:1/data + Then the client receives an json object + Then the client receives status code of 200 + Then the client receives value 22cfbb91fcbff5a2e0755cc26567a81078898dfe939d07c9745149f45863ca31 for property dataHash + Then the client receives value ReportData:1 for property _id + + # ReportCalculation not available + Scenario: client makes call to GET /reporting/report-calculation/ReportCalculation:42/data and receives NotFound + Given database app is created + Given database report-calculation is created + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report-calculation/ReportCalculation:42/data + Then the client receives an json object + Then the client receives status code of 404 + + # ReportData not available + Scenario: client makes call to GET /reporting/report-calculation/ReportCalculation:42/data and receives NotFound 2 + Given database app is created + Given database report-calculation is created + Given document ReportCalculation:1 is stored in database report-calculation + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report-calculation/ReportCalculation:1/data + Then the client receives an json object + Then the client receives status code of 404 diff --git a/application/aam-backend-service/src/test/resources/cucumber/features/reporting/reportcalculation/fetch-report-calculation.feature b/application/aam-backend-service/src/test/resources/cucumber/features/reporting/reportcalculation/fetch-report-calculation.feature new file mode 100644 index 0000000..105ce3d --- /dev/null +++ b/application/aam-backend-service/src/test/resources/cucumber/features/reporting/reportcalculation/fetch-report-calculation.feature @@ -0,0 +1,50 @@ +Feature: the report calculation endpoint persist to database + + Scenario: client makes call to GET /reporting/report-calculation/report/ReportConfig:1 without token and receives Unauthorized + Given database app is created + Given database report-calculation is created + Given document ReportConfig:1 is stored in database app + When the client calls GET /v1/reporting/report-calculation/report/ReportConfig:1 + Then the client receives an json object + Then the client receives status code of 401 + + Scenario: client makes call to GET /reporting/report-calculation/report/ReportConfig:1 and receives empty list + Given database app is created + Given database report-calculation is created + Given document ReportConfig:1 is stored in database app + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report-calculation/report/ReportConfig:1 + Then the client receives a json array + Then the client receives status code of 200 + Then the client receives array with 0 elements + + Scenario: client makes call to GET /reporting/report-calculation/report/ReportConfig:1 and receives one element + Given database app is created + Given database report-calculation is created + Given document ReportConfig:1 is stored in database app + Given document ReportCalculation:1 is stored in database report-calculation + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report-calculation/report/ReportConfig:1 + Then the client receives a json array + Then the client receives status code of 200 + Then the client receives array with 1 elements + + Scenario: client makes call to GET /reporting/report-calculation/ReportCalculation:1 and receives element + Given database app is created + Given database report-calculation is created + Given document ReportConfig:1 is stored in database app + Given document ReportCalculation:1 is stored in database report-calculation + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report-calculation/ReportCalculation:1 + Then the client receives an json object + Then the client receives status code of 200 + Then the client receives value FINISHED_SUCCESS for property status + Then the client receives value ReportCalculation:1 for property _id + + Scenario: client makes call to GET /reporting/report-calculation/ReportCalculation:42 and receives NotFound + Given database app is created + Given database report-calculation is created + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report-calculation/ReportCalculation:42 + Then the client receives an json object + Then the client receives status code of 404 diff --git a/application/aam-backend-service/src/test/resources/cucumber/features/reporting/reportcalculation/start-report-calculation.feature b/application/aam-backend-service/src/test/resources/cucumber/features/reporting/reportcalculation/start-report-calculation.feature new file mode 100644 index 0000000..e9122a5 --- /dev/null +++ b/application/aam-backend-service/src/test/resources/cucumber/features/reporting/reportcalculation/start-report-calculation.feature @@ -0,0 +1,59 @@ +Feature: the report calculation endpoint persist to database + + Background: + Given database app is created + Given database report-calculation is created + + Scenario: client makes call to POST /reporting/report-calculation/report/ReportConfig:1 without token and receives Unauthorized + When the client calls POST /v1/reporting/report-calculation/report/ReportConfig:1 without body +# Then the client receives an json object # --> todo: some mismatchedInputException is thrown, needs analyse + Then the client receives status code of 401 + + Scenario: client makes call to POST /reporting/report-calculation/report/ReportConfig:1 and receives NotFound + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls POST /v1/reporting/report-calculation/report/ReportConfig:1 without body + Then the client receives an json object + Then the client receives status code of 404 + + Scenario: client makes call to POST /reporting/report-calculation/report/ReportConfig:1 and receives CalculationId + Given document ReportConfig:1 is stored in database app + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls POST /v1/reporting/report-calculation/report/ReportConfig:1 without body + Then the client receives an json object + Then the client receives status code of 200 + When the client calls GET /v1/reporting/report-calculation/ with id from latest response + Then the client receives an json object + Then the client receives status code of 200 + Then the client receives value PENDING for property status + + Scenario: Pending ReportCalculation is processed within 30 seconds and returns error without Config:CONFIG_ENTITY + Given document ReportConfig:1 is stored in database app + Given document ReportCalculation:2 is stored in database report-calculation + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report-calculation/ReportCalculation:2 + Then the client receives an json object + Then the client receives status code of 200 + Then the client receives value PENDING for property status + Then the client waits for 15000 milliseconds + When the client calls GET /v1/reporting/report-calculation/ReportCalculation:2 + Then the client receives an json object + Then the client receives status code of 200 + Then the client receives value FINISHED_ERROR for property status + + Scenario: Pending ReportCalculation is processed within 30 seconds + Given document ReportConfig:1 is stored in database app + Given document Config:CONFIG_ENTITY is stored in database app + Given document ReportCalculation:2 is stored in database report-calculation + Given signed in as client dummy-client with secret client-secret in realm dummy-realm + When the client calls GET /v1/reporting/report-calculation/ReportCalculation:2 + Then the client receives an json object + Then the client receives status code of 200 + Then the client receives value PENDING for property status + Then the client waits for 15000 milliseconds + When the client calls GET /v1/reporting/report-calculation/ReportCalculation:2 + Then the client receives an json object + Then the client receives status code of 200 + Then the client receives value FINISHED_SUCCESS for property status + When the client calls GET /v1/reporting/report-calculation/ReportCalculation:2/data + Then the client receives an json object + Then the client receives status code of 200 diff --git a/application/aam-backend-service/src/test/resources/database/documents/Config:CONFIG_ENTITY.json b/application/aam-backend-service/src/test/resources/database/documents/Config:CONFIG_ENTITY.json new file mode 100644 index 0000000..3f636e6 --- /dev/null +++ b/application/aam-backend-service/src/test/resources/database/documents/Config:CONFIG_ENTITY.json @@ -0,0 +1,107 @@ +{ + "_id": "Config:CONFIG_ENTITY", + "data": { + "entity:Child": { + "label": "Child", + "labelPlural": "Children", + "attributes": { + "address": { + "dataType": "location", + "label": "Address" + }, + "health_bloodGroup": { + "dataType": "string", + "label": "Blood Group" + }, + "religion": { + "dataType": "string", + "label": "Religion" + }, + "gender": { + "dataType": "configurable-enum", + "additional": "genders", + "label": "Gender" + }, + "motherTongue": { + "dataType": "string", + "label": "Mother Tongue", + "description": "The primary language spoken at home", + "searchable": true + }, + "health_lastDentalCheckup": { + "dataType": "date", + "label": "Last Dental Check-Up" + }, + "birth_certificate": { + "dataType": "file", + "label": "Birth certificate" + }, + "dob": { + "dataType": "date-with-age", + "labelShort": "Geb.", + "label": "Custom DoB", + "defaultValue": "NEU" + }, + "category": { + "dataType": "array", + "innerDataType": "configurable-enum", + "additional": "xxx", + "label": "Custom Cat" + }, + "editor": { + "dataType": "entity-array", + "additional": "User", + "label": "Bearbeiter:in(en)", + "defaultValue": "$current_user" + } + } + }, + "entity:School": { + "attributes": { + "name": { + "dataType": "string", + "label": "Name" + }, + "privateSchool": { + "dataType": "boolean", + "label": "Private School" + }, + "language": { + "dataType": "string", + "label": "Language", + "searchable": true + }, + "address": { + "dataType": "location", + "label": "Address" + }, + "phone": { + "dataType": "string", + "label": "Phone Number" + }, + "timing": { + "dataType": "string", + "label": "School Timing" + }, + "remarks": { + "dataType": "string", + "label": "Remarks" + } + } + }, + "entity:User": { + "attributes": { + "phone": { + "dataType": "string", + "label": "Contact" + } + } + } + }, + "created": { + "at": "2024-01-26T08:24:54.866Z" + }, + "updated": { + "at": "2024-01-26T08:24:54.866Z" + } +} diff --git a/application/aam-backend-service/src/test/resources/database/documents/ReportCalculation:1.json b/application/aam-backend-service/src/test/resources/database/documents/ReportCalculation:1.json new file mode 100644 index 0000000..5808fa1 --- /dev/null +++ b/application/aam-backend-service/src/test/resources/database/documents/ReportCalculation:1.json @@ -0,0 +1,12 @@ +{ + "_id": "ReportCalculation:1", + "report": { + "id": "ReportConfig:1" + }, + "status": "FINISHED_SUCCESS", + "startDate": "2024-03-20T11:42:08.571", + "endDate": "2024-03-20T11:42:08.7", + "outcome": { + "result_hash": "000" + } +} diff --git a/application/aam-backend-service/src/test/resources/database/documents/ReportCalculation:2.json b/application/aam-backend-service/src/test/resources/database/documents/ReportCalculation:2.json new file mode 100644 index 0000000..6098676 --- /dev/null +++ b/application/aam-backend-service/src/test/resources/database/documents/ReportCalculation:2.json @@ -0,0 +1,10 @@ +{ + "_id": "ReportCalculation:2", + "report": { + "id": "ReportConfig:1" + }, + "status": "PENDING", + "startDate": null, + "endDate": null, + "outcome": null +} diff --git a/application/aam-backend-service/src/test/resources/database/documents/ReportConfig:1.json b/application/aam-backend-service/src/test/resources/database/documents/ReportConfig:1.json new file mode 100644 index 0000000..b685300 --- /dev/null +++ b/application/aam-backend-service/src/test/resources/database/documents/ReportConfig:1.json @@ -0,0 +1,6 @@ +{ + "_id": "ReportConfig:1", + "title": "Test Report 1", + "mode": "sql", + "aggregationDefinition": "SELECT s.name as name, s.privateSchool as privateSchool FROM School as s" +} diff --git a/application/aam-backend-service/src/test/resources/database/documents/ReportConfig:2.json b/application/aam-backend-service/src/test/resources/database/documents/ReportConfig:2.json new file mode 100644 index 0000000..88f2b9e --- /dev/null +++ b/application/aam-backend-service/src/test/resources/database/documents/ReportConfig:2.json @@ -0,0 +1,6 @@ +{ + "_id": "ReportConfig:2", + "title": "Test Report 2", + "mode": "sql", + "aggregationDefinition": "SELECT s.name as name, s.privateSchool as privateSchool FROM School as s" +} diff --git a/application/aam-backend-service/src/test/resources/database/documents/ReportData:1.json b/application/aam-backend-service/src/test/resources/database/documents/ReportData:1.json new file mode 100644 index 0000000..050bf77 --- /dev/null +++ b/application/aam-backend-service/src/test/resources/database/documents/ReportData:1.json @@ -0,0 +1,16 @@ +{ + "_id": "ReportData:1", + "report": { + "id": "ReportConfig:1" + }, + "calculation": { + "id": "ReportCalculation:1" + }, + "data": [ + { + "foo": "bar", + "bar": "foo" + } + ], + "dataHash": "000" +} diff --git a/application/aam-backend-service/src/test/resources/e2e-keycloak-realm.json b/application/aam-backend-service/src/test/resources/e2e-keycloak-realm.json new file mode 100644 index 0000000..f1f4db5 --- /dev/null +++ b/application/aam-backend-service/src/test/resources/e2e-keycloak-realm.json @@ -0,0 +1,2331 @@ +{ + "id": "70730a8c-c613-4e01-b472-a293b9686749", + "realm": "dummy-realm", + "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": "external", + "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": "951a7505-b768-4447-9164-d6e7aabc656c", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "70730a8c-c613-4e01-b472-a293b9686749", + "attributes": {} + }, + { + "id": "9121d439-c329-4343-b1e8-340fcbb9fea9", + "name": "default-roles-dummy-realm", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "manage-account", + "view-profile" + ] + } + }, + "clientRole": false, + "containerId": "70730a8c-c613-4e01-b472-a293b9686749", + "attributes": {} + }, + { + "id": "35afb9df-b7d5-414b-b605-dde2a9d1c136", + "name": "user_app", + "description": "", + "composite": false, + "clientRole": false, + "containerId": "70730a8c-c613-4e01-b472-a293b9686749", + "attributes": {} + }, + { + "id": "aef2068c-033b-4581-bb65-d576577a7cdb", + "name": "admin_app", + "description": "", + "composite": false, + "clientRole": false, + "containerId": "70730a8c-c613-4e01-b472-a293b9686749", + "attributes": {} + }, + { + "id": "d9e751f6-42c2-499a-abc0-3827b52e3506", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "70730a8c-c613-4e01-b472-a293b9686749", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "5f24a9f4-c92f-48bd-8263-ddefd60ceed5", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "5f535587-3c72-4cb0-8c05-3a22aceb344d", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "8a9ffe6e-f624-411f-b476-ca6e7acff722", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "a0103651-c878-44b6-89d0-95a7436f02a6", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "394fc5e9-c5b9-497b-92b8-9d533d881f37", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "aef1efdd-75ce-4028-9a06-9ccbe8b91576", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "2744690a-5fc7-405c-b581-7b08f38ee846", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "280e35ac-8066-4435-b01b-355f9af519f4", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "45aabc9b-bb7a-4991-bee5-d459eb71f635", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "93377c07-4343-4538-83f2-846614d13897", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "74f866d3-8d00-4c43-a2cf-2bf7e85847ae", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "8e37b6f3-f282-433b-9836-14b1a3597481", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "875cd553-a1bd-48e8-a9d1-e12c04ec603f", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "68164331-6ffd-4d24-82e3-319c375e0f78", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "create-client", + "query-clients", + "view-identity-providers", + "query-groups", + "view-clients", + "view-realm", + "query-realms", + "manage-clients", + "impersonation", + "manage-users", + "view-authorization", + "manage-authorization", + "query-users", + "manage-realm", + "manage-identity-providers", + "view-users", + "view-events", + "manage-events" + ] + } + }, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "7eda1b24-8d15-4484-bd0d-3ba1c1b4723f", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "d5ed1463-8528-4934-9946-a5b73e592bb5", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "1a10d826-f7f8-4210-90fe-46a581445a88", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "0121e2b0-0985-40e6-941d-cdf907a34592", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + }, + { + "id": "50a8771a-edf7-44d2-abee-373288eccf53", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-groups", + "query-users" + ] + } + }, + "clientRole": true, + "containerId": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "10688a65-8eea-49da-9064-cafad74435c9", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "6d44791d-06e9-4efe-82ac-8b1870fa5130", + "attributes": {} + } + ], + "dummy-client": [ + { + "id": "d7f64f81-879c-4a3e-8646-543a88310d9e", + "name": "user_app", + "description": "", + "composite": false, + "clientRole": true, + "containerId": "67433934-c1b4-4247-a249-9ee89fe8b31e", + "attributes": {} + } + ], + "account": [ + { + "id": "e62c5d32-7796-42c4-b762-730874aef2f2", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "0e4d4e50-2a2a-477f-9d4b-e42eaeb75918", + "attributes": {} + }, + { + "id": "271b7678-4345-46f5-badc-1711af97f4b8", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "0e4d4e50-2a2a-477f-9d4b-e42eaeb75918", + "attributes": {} + }, + { + "id": "74d11fba-e269-481d-a56a-8044591f620b", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "0e4d4e50-2a2a-477f-9d4b-e42eaeb75918", + "attributes": {} + }, + { + "id": "377d0718-1121-424e-aae3-f23ccefe7f88", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "0e4d4e50-2a2a-477f-9d4b-e42eaeb75918", + "attributes": {} + }, + { + "id": "b103789e-77cd-42ba-ac82-1639a73e361b", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "0e4d4e50-2a2a-477f-9d4b-e42eaeb75918", + "attributes": {} + }, + { + "id": "2f453558-6186-4f9d-ab61-d55a68715fae", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "0e4d4e50-2a2a-477f-9d4b-e42eaeb75918", + "attributes": {} + }, + { + "id": "9183adb4-394d-475a-a6f9-2fa0069f88d0", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "0e4d4e50-2a2a-477f-9d4b-e42eaeb75918", + "attributes": {} + }, + { + "id": "f542699d-af48-468a-9152-d768c3cb56fd", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "0e4d4e50-2a2a-477f-9d4b-e42eaeb75918", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "9121d439-c329-4343-b1e8-340fcbb9fea9", + "name": "default-roles-dummy-realm", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "70730a8c-c613-4e01-b472-a293b9686749" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppGoogleName", + "totpAppMicrosoftAuthenticatorName" + ], + "localizationTexts": {}, + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyExtraOrigins": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "users": [ + { + "id": "4fd0d6f4-a529-4e69-ae72-4c3d285a884e", + "createdTimestamp": 1707924051229, + "username": "service-account-dummy-client", + "enabled": true, + "totp": false, + "emailVerified": false, + "serviceAccountClientId": "dummy-client", + "attributes": { + "exact_username": [ + "dummy-client" + ] + }, + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "default-roles-dummy-realm", + "user_app", + "admin_app" + ], + "notBefore": 0, + "groups": [] + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "0e4d4e50-2a2a-477f-9d4b-e42eaeb75918", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/dummy-realm/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/dummy-realm/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": "3f263e70-90f5-4b09-91ab-eb172da7aea6", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/dummy-realm/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/dummy-realm/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": "7cc2e3d9-d1f7-43d4-810a-936772b7d902", + "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": "6552b33d-38ad-4d8d-95bb-a822480cede4", + "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": "6d44791d-06e9-4efe-82ac-8b1870fa5130", + "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": "67433934-c1b4-4247-a249-9ee89fe8b31e", + "clientId": "dummy-client", + "name": "", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "client-secret", + "redirectUris": [ + "/*" + ], + "webOrigins": [ + "/*" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": true, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "oauth2.device.authorization.grant.enabled": "false", + "client.secret.creation.time": "1707924061", + "backchannel.logout.session.required": "true", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "a30621b5-80c1-4c1c-8269-1ab3f8225e56", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "introspection.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "bb48d4e6-a716-4c8b-a669-5958ca8faece", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "introspection.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "d23faf48-ecbc-4b8a-9a77-44b7fe5abce3", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "introspection.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + }, + { + "id": "ff8d8cab-d9b7-49a8-9a9d-f8c55c3271f7", + "name": "exact_username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "exact_username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "username", + "jsonType.label": "String" + } + }, + { + "id": "ee424763-157c-4629-a0ee-93606582acca", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "_couchdb\\.roles", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [], + "optionalClientScopes": [ + "roles", + "reporting_read", + "reporting_write" + ] + }, + { + "id": "200aaec6-03bc-4af2-ab2d-c8536812049c", + "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": "70a06a51-3b45-4c57-9479-83b20c40390c", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/dummy-realm/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/dummy-realm/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": "f96c6f36-1045-44b1-88bb-37ff3fef27ab", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "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": "47efbd88-b9b3-4dbd-a7de-ea748fe5e90f", + "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": "67c5e2d6-fe67-4e29-a1a9-da366b79c402", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + }, + { + "id": "24730b19-4ca1-4df8-a621-725ade881234", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "29e6a5e5-c250-4d0b-9dca-31fa63207c75", + "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": "efe119ac-ca21-4292-86b4-b868ee4f05a1", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String" + } + }, + { + "id": "6e94d156-8e71-4db2-8e19-01b080b321d0", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String" + } + }, + { + "id": "f5f04e44-5901-45b3-949d-bedf7447084e", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "3b3ecb8e-a795-43bc-8040-c08e2c999f26", + "name": "reporting_write", + "description": "Access to reporting-api POST endpoints", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false", + "gui.order": "", + "consent.screen.text": "" + } + }, + { + "id": "39539993-78a2-49c0-a524-8f18879fa127", + "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": "67b13a9c-0240-4a68-8f7e-88a2dbb8152f", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "2bb664dc-46ef-47a7-a871-b4120595be25", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "cccb9273-2c2e-4347-9192-ccb746142973", + "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": "83533297-9d45-4921-904e-1ee22c4d3d37", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "introspection.token.claim": "true", + "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": "30d55056-4e4c-4b87-8514-794ae3c3e97f", + "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": "2e4dd893-c86b-44d3-abf5-acdc618af496", + "name": "reporting_read", + "description": "Access to reporting-api GET endpoints ", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false", + "gui.order": "", + "consent.screen.text": "" + } + }, + { + "id": "a5592afe-59a1-455a-be52-28e7ca6c36f2", + "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": "364fda43-29a1-4750-86f8-a2429de20baf", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "fb73ae1d-c173-4a9a-909d-dc6ac708a45c", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "f070c353-3d81-4e76-80c4-608ce94fec9d", + "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": "378b0665-c5f4-460c-8130-eedada28790b", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "3a1e7e69-ea5b-416f-88bc-d741fac12df4", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "9f4df111-ea37-440b-8b4b-68c77e28a098", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "7e23bf26-05b8-422a-986c-701970f607f7", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "1551e2ff-be9c-4d68-8564-bd6ad0904b8c", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "f177e455-84eb-4cc7-acad-7dabfc35dfbe", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "e6885f3a-6de1-485b-935b-46b4a9f3ea84", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "d47bf28a-6d9c-4a29-ae5a-cfa183be7632", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "559e46a7-d515-4f4c-a57b-67db10ee6ab4", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "48d36ccd-c2a3-45ab-bd44-65e543821d61", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "8c879e2c-9066-4675-a8bf-67e57fb5b9d0", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "ece0a065-945b-4861-aef6-4e7564b4f848", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "aba09e02-c198-4928-93e6-99cbc3e63739", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "7a2fec9d-0815-46dc-a34f-2424a11bc15d", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "cf46acc2-95f0-416d-9d6c-74a614ef9e3c", + "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": "66cb3335-5901-4264-ba09-e2489f3d3299", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "3460cb88-b47b-4e33-be50-61c031e7bf24", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "23c9445b-6885-4910-9ac5-5fbe7a3a23ef", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "c29ad764-a9c1-4190-b4fc-f968176c3262", + "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": "ed9bca1d-2dd9-49f3-906a-ca9a7545b633", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt", + "reporting_read", + "reporting_write" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "referrerPolicy": "no-referrer", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "32f7d5c0-e00f-4c7f-ab63-6dc051eca717", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper", + "saml-role-list-mapper" + ] + } + }, + { + "id": "2e1f838c-9b88-4f0c-9bd6-c84d808188b3", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "3ea05a87-d9c5-49db-bad5-4e82adc87795", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "393cf8cf-9742-4bb8-872a-a2aa67e418f5", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "52feff9a-92a8-4b84-b238-47803715a48c", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "ec61187c-e058-40fc-b312-7b7e8ce97c89", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "258eea6c-d0c9-4f7e-8911-c9a2118cb210", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "8bb1095d-ea7b-4e2b-899c-c92a7344e005", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "saml-user-attribute-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-full-name-mapper" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "8cef4e25-9635-471f-b4ac-5e3aefa1c9b9", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "760e59c0-5c42-4748-ae7d-75455b7ccb0a", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + }, + { + "id": "52404456-b011-4615-8d4c-79f0a70e9c67", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "b8e2941c-0b1f-43ab-99c4-f59da43d8a03", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "e5dd97e9-7182-4354-a387-d4b8aaa46fd7", + "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": "93fa97fe-b010-4a42-9fdb-22577cee5ada", + "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": "6df4a523-d77d-4755-bdc5-5e0ad8129a9e", + "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": "c7295fa3-af22-4f77-822e-7e8baee1714b", + "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": "c0c8ecb9-ed30-47b5-85e2-e8886350f63a", + "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": "60525cad-5c69-49d2-8275-5d589203c0e4", + "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": "d5d61c10-46d2-4e29-a734-3e85f2717530", + "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": "8a60e4de-1960-476c-af24-176c4b7ea1d1", + "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": "e7eb6b03-844e-43e9-a70e-50336e4ecc33", + "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": "872f1643-808e-4f71-94e1-e1e4d450b3df", + "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": "3ad4e47f-03e4-4ed8-89f1-ab2c38e4142d", + "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": "f71b17c5-cdad-4cff-a28e-c77e4bbcdcdc", + "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": "dac4fff8-2b4a-4369-bbe8-3cd4acd328cc", + "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": "60d08cbf-b8c4-4736-abbd-0b39401b52e2", + "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": "a062dacc-0ef0-4c8c-b0dc-c75ab3407463", + "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": "e5267b1c-512e-454d-a0b6-160d2bcb173a", + "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-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": "5d30865c-7b53-4a2e-83d3-9da1c63efb1f", + "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": "84fbdaa9-f8da-436b-a82a-5cd6ab822849", + "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": "494f2acc-0714-4da9-9ca2-8ed80eb2c011", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "d40ee9ba-51d4-407f-b287-4f74dec280af", + "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", + "parRequestUriLifespan": "60", + "cibaInterval": "5", + "realmReusableOtpCode": "false" + }, + "keycloakVersion": "23.0.3", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +}